1 /*
2 * Copyright (c) 2019 Georg Brein. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 *
7 * 1. Redistributions of source code must retain the above copyright notice,
8 * this list of conditions and the following disclaimer.
9 *
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * 3. Neither the name of the copyright holder nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 */
30
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <stddef.h>
35 #include <string.h>
36 #include <errno.h>
37 #include <wchar.h>
38 #include <wctype.h>
39 #include <time.h>
40 #include <limits.h>
41
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include <unistd.h>
45 #include <fcntl.h>
46 #include <dirent.h>
47 #include <sys/select.h>
48 #include <sys/time.h>
49
50 #include "tnylpo.h"
51
52
53 /*
54 * memory layout of the emulated CP/M computer
55 */
56 #define ALV_SIZE 64
57 #define ALV (MAGIC_ADDRESS - ALV_SIZE)
58 #define DPB_SIZE 15
59 #define DPB (ALV - DPB_SIZE)
60 #define BIOS_VECTOR ((DPB - BIOS_VECTOR_COUNT * 3) & 0xff00)
61 #define BIOS_START BIOS_VECTOR
62 #define BIOS_SIZE (MEMORY_SIZE - BIOS_START)
63 #define BDOS_SIZE 11
64 #define BDOS_START (BIOS_START - BDOS_SIZE)
65 #define SERIAL_NUMBER (BDOS_START - 6)
66 #define CCP_STACK_COUNT 8
67 #define CCP_STACK (SERIAL_NUMBER - CCP_STACK_COUNT * 2)
68 #define CCP_START CCP_STACK
69 #define CCP_SIZE (BDOS_START - CCP_START)
70 #define TPA_START 0x0100
71 #define BOOT 0x0000
72 #define IOBYTE 0x0003
73 #define DRVUSER 0x0004
74 #define BDOS_ENTRY 0x0005
75 #define DEFAULT_FCB_1 0x005c
76 #define DEFAULT_FCB_2 0x006c
77 #define DEFAULT_DMA 0x0080
78 #define DMA_SIZE 128
79
80
81 /*
82 * current default drive (0=A, 1=B, ..., 15=P)
83 */
84 static int current_drive = 0;
85
86
87 /*
88 * current user number (0...15)
89 */
90 static int current_user = 0;
91
92
93 /*
94 * runtime read-only flags for the drives
95 */
96 static int read_only[16];
97
98
99 /*
100 * current DMA area
101 */
102 static int current_dma = DEFAULT_DMA;
103
104
105 /*
106 * OS serial number
107 */
108 static const unsigned char serial_number[6] = {
109 0x00, 0x16, 0x00, 0xc0, 0xff, 0xee
110 };
111
112
113 /*
114 * extended BDOS functions: program return code
115 */
116 static int program_return_code = 0;
117
118
119 /*
120 * helper function: get word from DE
121 */
122 static inline int
get_de(void)123 get_de(void) { int de = reg_d; de <<= 8; de |= reg_e; return de; }
124
125
126 /*
127 * helper function: get word from HL
128 */
129 static inline int
get_hl(void)130 get_hl(void) { int hl = reg_h; hl <<= 8; hl |= reg_l; return hl; }
131
132
133 /*
134 * helper function: get word from BC
135 */
136 static inline int
get_bc(void)137 get_bc(void) { int bc = reg_b; bc <<= 8; bc |= reg_c; return bc; }
138
139
140 /*
141 * return the highest address of the TPA
142 */
143 int
get_tpa_end(void)144 get_tpa_end(void) { return BDOS_START - 1; }
145
146
147 /*
148 * logging functions and macros for the OS routines
149 */
150 #define REGS_A 0x01
151 #define REGS_C 0x02
152 #define REGS_E 0x04
153 #define REGS_BC 0x08
154 #define REGS_DE 0x10
155 #define REGS_HL 0x20
156
157
158 static const char *
format_regs(int regs)159 format_regs(int regs) {
160 static char buffer[80];
161 char *cp = buffer;
162 if (! regs) {
163 *cp = '\0';
164 } else {
165 cp += sprintf(cp, ":");
166 if (regs & REGS_A) cp += sprintf(cp, " a=0x%02x", reg_a);
167 if (regs & REGS_C) cp += sprintf(cp, " c=0x%02x", reg_c);
168 if (regs & REGS_E) cp += sprintf(cp, " e=0x%02x", reg_e);
169 if (regs & REGS_BC) cp += sprintf(cp, " bc=0x%04x", get_bc());
170 if (regs & REGS_DE) cp += sprintf(cp, " de=0x%04x", get_de());
171 if (regs & REGS_HL) cp += sprintf(cp, " hl=0x%04x", get_hl());
172 }
173 return buffer;
174 }
175
176
177 static void
sys_entry(const char * name,int regs)178 sys_entry(const char *name, int regs) {
179 plog("%s entry%s", name, format_regs(regs));
180 }
181
182
183 static void
sys_exit(const char * name,int regs)184 sys_exit(const char *name, int regs) {
185 plog("%s exit%s", name, format_regs(regs));
186 }
187
188
189 #define FDOS_ENTRY(name, regs) \
190 if (log_level >= LL_FDOS) sys_entry(name, regs)
191 #define FDOS_EXIT(name, regs) \
192 if (log_level >= LL_FDOS) sys_exit(name, regs)
193 #define SYS_ENTRY(name, regs) \
194 if (log_level >= LL_SYSCALL) sys_entry(name, regs)
195 #define SYS_EXIT(name, regs) \
196 if (log_level >= LL_SYSCALL) sys_exit(name, regs)
197
198
199 /*
200 * checks if a base filename is "nice", i. e. acceptable both in CP/M and Unix
201 */
202 static int
is_nice_filename(const char * fn)203 is_nice_filename(const char *fn) {
204 const char *cp;
205 size_t l;
206 /*
207 * valid characters in filename and file name extension
208 */
209 static const char valid[] = "#$-0123456789@abcdefghijklmnopqrstuvwxyz";
210 /*
211 * assumption: no, the file name is not nice
212 */
213 int rc = 0;
214 /*
215 * is there an extension?
216 */
217 cp = strchr(fn, '.');
218 if (! cp) {
219 /*
220 * no extension
221 */
222 /*
223 * name must be 1 to 8 characters
224 */
225 l = strlen(fn);
226 if (l < 1 || l > 8) goto premature_exit;
227 /*
228 * all characters must be valid
229 */
230 for (cp = fn; *cp; cp++) {
231 if (! strchr(valid, *cp)) goto premature_exit;
232 }
233 } else {
234 /*
235 * extension present
236 */
237 /*
238 * name must be 1 to 8 characters
239 */
240 l = cp - fn;
241 if (l < 1 || l > 8) goto premature_exit;
242 /*
243 * extension must be 1 to 3 characters
244 */
245 l = strlen(cp + 1);
246 if (l < 1 || l > 3) goto premature_exit;
247 /*
248 * all name characters must be valid
249 */
250 for (cp = fn; *cp != '.'; cp++) {
251 if (! strchr(valid, *cp)) goto premature_exit;
252 }
253 /*
254 * all extension characters must be valid
255 */
256 for (cp++; *cp; cp++) {
257 if (! strchr(valid, *cp)) goto premature_exit;
258 }
259 }
260 /*
261 * file name is nice
262 */
263 rc = 1;
264 premature_exit:
265 return rc;
266 }
267
268
269 /*
270 * helper function for os_init(): check file name of command file
271 */
272 static int
check_command_name(const char * fn,int * add_com_p)273 check_command_name(const char *fn, int *add_com_p) {
274 int rc = 0;
275 const char *cp;
276 /*
277 * base name must be "nice", i. e. CP/M & Unix conforming
278 */
279 if (! is_nice_filename(fn)) {
280 perr("command file name (%s) not valid", fn);
281 rc = (-1);
282 goto premature_exit;
283 }
284 /*
285 * is there an extension?
286 */
287 cp = strchr(fn, '.');
288 if (cp) {
289 /*
290 * yes: it must be .com
291 */
292 if (strcmp(cp, ".com")) {
293 perr("command file name must end in .com");
294 rc = (-1);
295 goto premature_exit;
296 }
297 *add_com_p = 0;
298 } else {
299 /*
300 * no: append .com
301 */
302 *add_com_p = 1;
303 }
304 premature_exit:
305 return rc;
306 }
307
308
309 /*
310 * helper function for handle_name_part(): check if a CP/M character
311 * is valid in a file name
312 */
313 static int
is_valid_in_cfn(unsigned char c)314 is_valid_in_cfn(unsigned char c) {
315 if (c == 0x23 /* # */) return 1;
316 if (c == 0x24 /* $ */) return 1;
317 if (c == 0x2d /* - */) return 1;
318 if (c == 0x3f /* ? */) return 1;
319 if (c == 0x40 /* @ */) return 1;
320 if (c >= 0x30 /* 0 */ && c <= 0x39 /* 9 */) return 1;
321 if (c >= 0x41 /* A */ && c <= 0x5a /* Z */) return 1;
322 return 0;
323 }
324
325
326 /*
327 * helper function for setup_fcb(): handle file name or extension
328 */
329 static const unsigned char *
handle_name_part(const unsigned char * sp,size_t length,unsigned char * dp)330 handle_name_part(const unsigned char *sp, size_t length, unsigned char *dp) {
331 size_t t;
332 const unsigned char *cp;
333 int star;
334 /*
335 * skip over valid characters
336 */
337 for (t = 0, cp = sp; is_valid_in_cfn(*cp); t++, cp++);
338 /*
339 * ignore valid characters after the maximal length
340 */
341 if (t > length) t = length;
342 /*
343 * does the name part end in '*'?
344 */
345 if (*cp == 0x2a /* * */) {
346 /*
347 * yes: ignore further stars and valid characters
348 */
349 while (*cp == 0x2a /* * */ || is_valid_in_cfn(*cp)) cp++;
350 star = 1;
351 } else {
352 star = 0;
353 }
354 /*
355 * copy name part
356 */
357 memcpy(dp, sp, t);
358 /*
359 * if the name contained a '*', pad name part with '?'
360 */
361 if (star) memset(dp + t, 0x3f /* ? */, length - t);
362 return cp;
363 }
364
365
366 /*
367 * set up the drive and name part of a FCB (i. e. the first twelve
368 * bytes of an FCB) according to a (conforming) Unix file name
369 */
370 static void
setup_fcb(const char * fn,unsigned char * fcb)371 setup_fcb(const char *fn, unsigned char *fcb) {
372 int t, l, i, j;
373 wchar_t wc;
374 unsigned char *cfn = NULL;
375 const unsigned char *cp;
376 /*
377 * initialize drive and name part of the fcb FCB
378 */
379 fcb[0] = 0;
380 memset(fcb + 1, 0x20 /* SPC */, 11);
381 /*
382 * convert file name to upper case CP/M characters
383 */
384 l = strlen(fn) + 1;
385 cfn = alloc(l);
386 mbtowc(NULL, NULL, 0);
387 i = j = 0;
388 do {
389 t = mbtowc(&wc, fn + i, l);
390 if (t == (-1)) goto premature_exit;
391 i += t;
392 l -= t;
393 t = to_cpm(towupper(wc));
394 if (t == (-1)) goto premature_exit;
395 cfn[j++] = t;
396 } while (t);
397 cp = cfn;
398 /*
399 * if the file name starts in a drive specification, set
400 * drive field in FCB, otherwise leave it 0
401 */
402 if (cp[0] >= 0x41 /* A */ && cp[0] <= 0x50 /* P */ &&
403 cp[1] == 0x3a /* : */) {
404 fcb[0] = cp[0] - 0x41 /* A */ + 1;
405 cp += 2;
406 }
407 /*
408 * check the file name and copy it
409 */
410 cp = handle_name_part(cp, 8, fcb + 1);
411 /*
412 * check for '.' and skip it
413 */
414 if (*cp != 0x2e /* . */) goto premature_exit;
415 cp++;
416 /*
417 * check the extension and copy it
418 */
419 cp = handle_name_part(cp, 3, fcb + 9);
420 premature_exit:
421 free(cfn);
422 return;
423 }
424
425
426 /*
427 * type of a file list
428 */
429 struct file_list {
430 /*
431 * size of file *in CP/M records of 128 bytes*
432 */
433 off_t size;
434 /*
435 * file access time
436 */
437 time_t access;
438 /*
439 * file modification time
440 */
441 time_t modify;
442 struct file_list *next_p;
443 char *name;
444 };
445
446
447 /*
448 * deallocate a file list
449 */
450 static void
free_filelist(struct file_list * flp)451 free_filelist(struct file_list *flp) {
452 struct file_list *tp;
453 while (flp) {
454 tp = flp;
455 flp = tp->next_p;
456 free(tp->name);
457 free(tp);
458 }
459 }
460
461
462 /*
463 * helper function for get_filelist(): prepares a CP/M compatible
464 * Unix filename for matching by removing the dot between name and
465 * extension and padding name and extension with blanks
466 */
467 static void
prepare_name(const char * unix_name,char pattern[11])468 prepare_name(const char *unix_name, char pattern[11]) {
469 const char *cp;
470 memset(pattern, ' ', 11);
471 cp = strchr(unix_name, '.');
472 /*
473 * we already know that unix_name is CP/M compatible,
474 * i.e. that the name resp. the extension is at most 8 resp. 3
475 * characters long
476 */
477 if (cp) {
478 memcpy(pattern, unix_name, cp - unix_name);
479 memcpy(pattern + 8, cp + 1, strlen(cp + 1));
480 } else {
481 memcpy(pattern, unix_name, strlen(unix_name));
482 }
483 }
484
485
486 /*
487 * helper function for get_filelist(): check two arrays prepared
488 * by prepare_name() for a match (pattern may contain question marks as
489 * wildcards
490 */
491 static int
match_name(const char name[11],const char pattern[11])492 match_name(const char name[11], const char pattern[11]) {
493 int rc = 1, i;
494 for (i = 0; i < 11; i++) {
495 if (pattern[i] == '?') continue;
496 if (pattern[i] != name[i]) {
497 rc = 0;
498 break;
499 }
500 }
501 return rc;
502 }
503
504
505 /*
506 * gets a listing of all possible CP/M files in a directory which
507 * match a given, possibly ambigous file name (the pattern is expected
508 * in Unix format)
509 */
510 static struct file_list *
get_filelist(const char * directory,const char * name,const char * caller)511 get_filelist(const char *directory, const char *name, const char *caller) {
512 struct file_list *flp = NULL, *tp;
513 DIR *dp = NULL;
514 struct dirent *dep;
515 int t;
516 struct stat s;
517 char *path = NULL;
518 char pattern[11];
519 char temp_name[11];
520 dp = opendir(directory);
521 if (! dp) {
522 plog("%s: opendir(%s) failed: %s", caller,
523 directory, strerror(errno));
524 goto premature_exit;
525 }
526 /*
527 * prepare pattern for matching
528 */
529 prepare_name(name, pattern);
530 while ((dep = readdir(dp))) {
531 /*
532 * skip CP/M incompatible names
533 */
534 if (! is_nice_filename(dep->d_name)) continue;
535 /*
536 * skip names which do not match
537 */
538 prepare_name(dep->d_name, temp_name);
539 if (! match_name(temp_name, pattern)) continue;
540 /*
541 * build path for file
542 */
543 free(path);
544 path = alloc(strlen(directory) + strlen(dep->d_name) + 2);
545 sprintf(path, "%s/%s", directory, dep->d_name);
546 /*
547 * get information for file, skip if unavailable
548 */
549 t = lstat(path, &s);
550 if (t == (-1)) {
551 plog("%s: lstat(%s) failed: %s", caller, path,
552 strerror(errno));
553 continue;
554 }
555 /*
556 * skip all but regular files
557 */
558 if (! S_ISREG(s.st_mode)) continue;
559 /*
560 * skip files greater than 8MB
561 */
562 if (s.st_size > 8 * 1024 * 1024) continue;
563 /*
564 * create list entry and append it to list
565 */
566 tp = alloc(sizeof (struct file_list));
567 tp->next_p = flp;
568 tp->size = (s.st_size + 127) / 128;
569 tp->access = s.st_atime;
570 tp->modify = s.st_mtime;
571 tp->name = alloc(strlen(dep->d_name) + 1);
572 strcpy(tp->name, dep->d_name);
573 flp = tp;
574 }
575 premature_exit:
576 free(path);
577 if (dp) closedir(dp);
578 return flp;
579 }
580
581
582 /*
583 * file data flags
584 */
585 #define FILE_RODISK 0x1 /* opened on a read only disk */
586 #define FILE_ROFILE 0x2 /* file was opened read only */
587 #define FILE_WRITTEN 0x4 /* file has been written to */
588
589 /*
590 * xor value for file ID in FCB
591 */
592 #define FILE_QUUX 0xafcb
593
594
595 /*
596 * element of the file list
597 */
598 struct file_data {
599 struct file_data *next_p;
600 char *path;
601 int id;
602 int flags;
603 int fd;
604 };
605
606
607 /*
608 * head of the file list
609 */
610 static struct file_data *first_file_p = NULL;
611
612
613 /*
614 * delete file list element
615 */
616 static void
free_filedata(struct file_data * fdp)617 free_filedata(struct file_data *fdp) {
618 if (fdp->fd != (-1)) {
619 /*
620 * warn if a program didn't explicitly close an output file
621 */
622 if (fdp->flags & FILE_WRITTEN) {
623 plog("output file %s not explicitly closed by program",
624 fdp->path);
625 }
626 if (close(fdp->fd) == (-1)) {
627 plog("cannot close %s: %s", fdp->path,
628 strerror(errno));
629 }
630 }
631 free(fdp->path);
632 free(fdp);
633 }
634
635
636 /*
637 * create a new entry in the file data list, store its ID in the FCB
638 */
639 static struct file_data *
create_filedata(int fcb,const char * caller)640 create_filedata(int fcb, const char *caller) {
641 /*
642 * current file ID generator; file ID are in the range 1...65535
643 */
644 static int file_id = 1;
645 int start_id, id;
646 struct file_data **fdpp = NULL, *fdp = NULL;
647 /*
648 * start with the current value of file_id
649 */
650 start_id = id = file_id++;
651 file_id &= 0xffff;
652 if (! file_id) file_id++;
653 for (;;) {
654 /*
655 * search if the ID is already in use
656 */
657 for (fdpp = &first_file_p;
658 *fdpp && (*fdpp)->id < id;
659 fdpp = &(*fdpp)->next_p);
660 /*
661 * no: use this ID
662 */
663 if (! *fdpp || (*fdpp)->id > id) break;
664 /*
665 * get next candidate
666 */
667 id = file_id++;
668 file_id &= 0xffff;
669 if (! file_id) file_id++;
670 /*
671 * if all possible file IDs are taken, despair
672 */
673 if (id == start_id) {
674 plog("%s (FCB 0x%04x): more than 65536 open files",
675 caller, fcb);
676 terminate = 1;
677 term_reason = ERR_LOGIC;
678 goto premature_exit;
679 }
680 }
681 /*
682 * allocate and initialize file data structure and enter
683 * it into the file list
684 */
685 fdp = alloc(sizeof (struct file_data));
686 fdp->next_p = *fdpp;
687 fdp->path = NULL;
688 fdp->id = id;
689 fdp->flags = 0;
690 fdp->fd = (-1);
691 *fdpp = fdp;
692 /*
693 * store file ID and file ID xor FILE_QUUX in the FCB
694 */
695 memory[fcb + 16] = (id & 0xff);
696 memory[fcb + 17] = ((id >> 8) & 0xff);
697 id ^= FILE_QUUX;
698 memory[fcb + 18] = (id & 0xff);
699 memory[fcb + 19] = ((id >> 8) & 0xff);
700 premature_exit:
701 return fdp;
702 }
703
704
705 /*
706 * search existing file data structure; returns a pointer to a pointer
707 * to allow removal of the entry
708 */
709 static struct file_data **
get_filedata_pp(int fcb,const char * caller)710 get_filedata_pp(int fcb, const char *caller) {
711 int id, t;
712 struct file_data **fdpp = NULL;
713 /*
714 * get and check file ID from FCB
715 */
716 id = memory[fcb + 17];
717 id <<= 8;
718 id |= memory[fcb + 16];
719 t = memory[fcb + 19];
720 t <<= 8;
721 t |= memory[fcb + 18];
722 if ((id ^ t) != FILE_QUUX) {
723 plog("%s (FCB 0x%04x): invalid file ID in FCB", caller, fcb);
724 terminate = 1;
725 term_reason = ERR_LOGIC;
726 goto premature_exit;
727 }
728 /*
729 * search for file ID in the file list
730 */
731 for (fdpp = &first_file_p;
732 *fdpp && (*fdpp)->id < id;
733 fdpp = &(*fdpp)->next_p);
734 /*
735 * not found: file already closed
736 */
737 if (! *fdpp || (*fdpp)->id != id) {
738 plog("%s (FCB 0x%04x): stale file ID in FCB", caller, fcb);
739 terminate = 1;
740 term_reason = ERR_LOGIC;
741 fdpp = NULL;
742 goto premature_exit;
743 }
744 premature_exit:
745 return fdpp;
746 }
747
748
749 /*
750 * reset disk subsystem: set current drive, reset read only vector,
751 * reset DMA address
752 */
753 static void
disk_reset(void)754 disk_reset(void) {
755 /*
756 * set current drive from configuration
757 */
758 current_drive = default_drive;
759 memory[DRVUSER] = (default_drive | (current_user << 4));
760 /*
761 * initialize readonly drives from configuration
762 */
763 memcpy(read_only, conf_readonly, sizeof read_only);
764 /*
765 * reset DMA address to default
766 */
767 current_dma = DEFAULT_DMA;
768 }
769
770
771 /*
772 * initialize the OS emulation; check command file name,
773 * load command file, and set up the environment
774 */
775 int
os_init(void)776 os_init(void) {
777 int rc = 0, i, t, drive, add_com;
778 size_t l, tpa_free, bfree, n;
779 char *command_file = NULL;
780 const char *fn, *cp;
781 static const char valid_drive[] = "abcdefghijklmnop";
782 FILE *fp = NULL;
783 unsigned char *tpa_p;
784 wchar_t buffer[DMA_SIZE], *bp;
785 /*
786 * reset disk subsystem
787 */
788 disk_reset();
789 /*
790 * find and load executeable
791 */
792 if (strchr(conf_command, '/')) {
793 /*
794 * command name contains a slash --- assume Unix path
795 */
796 fn = base_name(conf_command);
797 /*
798 * check base name
799 */
800 rc = check_command_name(fn, &add_com);
801 if (rc) goto premature_exit;
802 /*
803 * create command path
804 */
805 l = strlen(conf_command) + 1;
806 if (add_com) {
807 command_file = alloc(l + 4);
808 sprintf(command_file, "%s.com", conf_command);
809 } else {
810 command_file = alloc(l);
811 strcpy(command_file, conf_command);
812 }
813 } else {
814 /*
815 * assume CP/M style filename relative to virtual drive
816 */
817 fn = conf_command;
818 /*
819 * is the file name prefixed by a drive name?
820 */
821 cp = strchr(valid_drive, fn[0]);
822 if (cp && fn[1] == ':') {
823 drive = cp - valid_drive;
824 fn += 2;
825 } else {
826 drive = current_drive;
827 }
828 /*
829 * is the drive defined?
830 */
831 if (! conf_drives[drive]) {
832 perr("drive %c: not defined", valid_drive[drive]);
833 rc = (-1);
834 goto premature_exit;
835 }
836 /*
837 * check name
838 */
839 rc = check_command_name(fn, &add_com);
840 if (rc) goto premature_exit;
841 /*
842 * create command path
843 */
844 l = strlen(conf_drives[drive]) + 1 + strlen(fn) +
845 (add_com ? 4 : 0) + 1;
846 command_file = alloc(l);
847 sprintf(command_file, "%s/%s%s", conf_drives[drive],
848 fn, add_com ? ".com" : "");
849 }
850 /*
851 * load command file
852 */
853 fp = fopen(command_file, "rb");
854 if (! fp) {
855 perr("cannot open command file %s. %s", command_file,
856 strerror(errno));
857 rc = (-1);
858 goto premature_exit;
859 }
860 tpa_p = memory + TPA_START;
861 /*
862 * this is deliberately more than CCP_START - TPA_START to
863 * catch a command file which doesn't fit the TPA
864 */
865 tpa_free = BDOS_START - TPA_START;
866 while (tpa_free) {
867 l = fread(tpa_p, 1, tpa_free, fp);
868 if (! l) {
869 if (feof(fp)) break;
870 perr("read error on %s: %s", command_file,
871 strerror(errno));
872 rc = (-1);
873 goto premature_exit;
874 }
875 tpa_p += l;
876 tpa_free -= l;
877 }
878 /*
879 * check for overrun
880 */
881 if (tpa_free < BDOS_START - CCP_START) {
882 perr("command file %s too large", command_file);
883 rc = (-1);
884 goto premature_exit;
885 }
886 /*
887 * set up RET instructions in all magic addresses (to keep
888 * debuggers happy
889 */
890 memset(memory + MAGIC_ADDRESS, 0xc9, MEMORY_SIZE - MAGIC_ADDRESS);
891 /*
892 * set up CCP 8-level stack with a pushed return address to WBOOT
893 */
894 reg_sp = SERIAL_NUMBER;
895 memory[--reg_sp] = (((BIOS_VECTOR + 3) >> 8) & 0xff);
896 memory[--reg_sp] = ((BIOS_VECTOR + 3) & 0xff);
897 /*
898 * set up CP/M serial number
899 */
900 memcpy(memory + SERIAL_NUMBER, serial_number, sizeof serial_number);
901 /*
902 * set up BDOS (jp to MAGIC_ADDRESS + 0)
903 */
904 memory[BDOS_START] = 0xc3 /* jp */;
905 memory[BDOS_START + 1] = (MAGIC_ADDRESS & 0xff);
906 memory[BDOS_START + 2] = ((MAGIC_ADDRESS >> 8) & 0xff);
907 /*
908 * four dummy error vectors all point to WBOOT magic address
909 */
910 memory[BDOS_START + 3] = memory[BDOS_START + 5] =
911 memory[BDOS_START + 7] = memory[BDOS_START + 9] =
912 ((MAGIC_ADDRESS + 2) & 0xff);
913 memory[BDOS_START + 4] = memory[BDOS_START + 6] =
914 memory[BDOS_START + 8] = memory[BDOS_START + 10] =
915 (((MAGIC_ADDRESS + 2) >> 8) & 0xff);
916
917 /*
918 * set up BIOS vector (jps to MAGIC_ADDRESS + 1 ... MAGIC_ADDRESS + 16)
919 */
920 for (i = 0; i < BIOS_VECTOR_COUNT; i++) {
921 t = MAGIC_ADDRESS + 1 + i;
922 memory[BIOS_VECTOR + i * 3] = 0xc3 /* jp */;
923 memory[BIOS_VECTOR + i * 3 + 1] = (t & 0xff);
924 memory[BIOS_VECTOR + i * 3 + 2] = ((t >> 8) & 0xff);
925 }
926 /*
927 * set up fake DPB (used for all drives)
928 */
929 /*
930 * SPT (sectors / track) 32 (randomly selected)
931 */
932 memory[DPB] = (32 & 0xff);
933 memory[DPB + 1] = ((32 >> 8) & 0xff);
934 /*
935 * BSH (block shift) 7 (<-- 16K BLS)
936 */
937 memory[DPB + 2] = 7;
938 /*
939 * BLM (block mask) 127 (<-- 16K BLS)
940 */
941 memory[DPB + 3] = 127;
942 /*
943 * EXM (extent mask) 7 (<-- 16K BLS, DSM 511, i. e. 8MB drive)
944 */
945 memory[DPB + 4] = 7;
946 /*
947 * DSM (# data blocks - 1) 511 (<-- 16K BLS, 8MB drive)
948 */
949 memory[DPB + 5] = (511 & 0xff);
950 memory[DPB + 6] = ((511 >> 8) & 0xff);
951 /*
952 * DSM (# directory entries - 1) 2047 (randomly selected)
953 */
954 memory[DPB + 7] = (2047 & 0xff);
955 memory[DPB + 8] = ((2047 >> 8) & 0xff);
956 /*
957 * AL0, AL1 (directory block vector) 0xf0 0x00 (<-- 2047 DRM, 16K BLS)
958 */
959 memory[DPB + 9] = 0xf0;
960 memory[DPB + 10] = 0x00;
961 /*
962 * CKS (directory check vector) 0 (fixed disk)
963 */
964 memory[DPB + 11] = 0;
965 memory[DPB + 12] = 0;
966 /*
967 * OFF (reserved tracks) 0 (none)
968 */
969 memory[DPB + 13] = 0;
970 memory[DPB + 14] = 0;
971 /*
972 * set up fake ALV (used for all drives)
973 */
974 memcpy(memory + ALV, memory + DPB + 9, 2);
975 memset(memory + ALV + 2, 0, ALV_SIZE - 2);
976 /*
977 * set up zero page
978 */
979 /*
980 * set up WBOOT entry
981 */
982 memory[BOOT] = 0xc3 /* jp */;
983 memory[BOOT + 1] = ((BIOS_VECTOR + 3) & 0xff);
984 memory[BOOT + 2] = (((BIOS_VECTOR + 3) >> 8) & 0xff);
985 /*
986 * set up IOBYTE
987 */
988 memory[IOBYTE] = 0x00;
989 /*
990 * set up DRVUSER
991 */
992 memory[DRVUSER] = (default_drive | (current_user << 4));
993 /*
994 * set up BDOS entry point
995 */
996 memory[BDOS_ENTRY] = 0xc3 /* jp */;
997 memory[BDOS_ENTRY + 1] = (BDOS_START & 0xff);
998 memory[BDOS_ENTRY + 2] = ((BDOS_START >> 8) & 0xff);
999 /*
1000 * convert command line arguments to wchar_t and splice them into
1001 * a DMA_SIZEd buffer
1002 */
1003 buffer[0] = L'\0';
1004 bp = buffer;
1005 bfree = DMA_SIZE;
1006 for (i = 0; bfree && i < conf_argc; i++) {
1007 *bp++ = L' ';
1008 bfree--;
1009 n = mbstowcs(bp, conf_argv[i], bfree);
1010 if (n == (size_t) (-1)) {
1011 perr("invalid character in command line");
1012 rc = (-1);
1013 goto premature_exit;
1014 }
1015 bp += n;
1016 bfree -= n;
1017 }
1018 if (! bfree) {
1019 perr("to many command line arguments");
1020 rc = (-1);
1021 goto premature_exit;
1022 }
1023 /*
1024 * convert command line to CP/M character set and copy it to
1025 * the default DMA area
1026 */
1027 memory[DEFAULT_DMA] = bp - buffer;
1028 for (bp = buffer, i = DEFAULT_DMA + 1; *bp; bp++) {
1029 *bp = towupper(*bp);
1030 t = to_cpm(*bp);
1031 if (t == (-1)) {
1032 perr("invalid character in command line");
1033 rc = (-1);
1034 goto premature_exit;
1035 }
1036 memory[i++] = t;
1037 }
1038 /*
1039 * set up the default FCBs at 0x005c and 0x006c
1040 */
1041 memset(memory + DEFAULT_FCB_1, 0, 36);
1042 setup_fcb(
1043 conf_argc > 0 ? conf_argv[0] : "",
1044 memory + DEFAULT_FCB_1);
1045 setup_fcb(
1046 conf_argc > 1 ? conf_argv[1] : "",
1047 memory + DEFAULT_FCB_2);
1048 /*
1049 * point PC to the start of the TPA
1050 */
1051 reg_pc = TPA_START;
1052 if (log_level > LL_ERRORS) {
1053 plog("starting execution of program %s", command_file);
1054 }
1055 premature_exit:
1056 if (fp) fclose(fp);
1057 free(command_file);
1058 return rc;
1059 }
1060
1061
1062 /*
1063 * terminate program execution
1064 */
1065 static void
bdos_system_reset(void)1066 bdos_system_reset(void) {
1067 static const char func[] = "system reset";
1068 SYS_ENTRY(func, 0);
1069 terminate = 1;
1070 term_reason = OK_TERM;
1071 }
1072
1073
1074 /*
1075 * column the BDOS thinks the cursor is in
1076 */
1077 static int console_col = 0;
1078
1079
1080 /*
1081 * output a newline to the console
1082 */
1083 static void
put_crlf(void)1084 put_crlf(void) {
1085 console_out(0x0d /* CR */);
1086 console_out(0x0a /* LF */);
1087 console_col = 0;
1088 }
1089
1090
1091 /*
1092 * output graphical character to the console
1093 */
1094 static void
put_graph(unsigned char c)1095 put_graph(unsigned char c) {
1096 console_out(c);
1097 console_col++;
1098 }
1099
1100
1101 /*
1102 * output a character to the console, interpret BS, LF, HT, and CR
1103 */
1104 static void
put_char(unsigned char c)1105 put_char(unsigned char c) {
1106 int i;
1107 switch (c) {
1108 case 0x08 /* BS */:
1109 if (! console_col) return;
1110 console_out(c);
1111 console_col--;
1112 return;
1113 case 0x0a /* LF */:
1114 console_out(c);
1115 return;
1116 case 0x09 /* HT */:
1117 i = ((console_col / 8) + 1) * 8 - console_col;
1118 while (i) {
1119 put_graph(0x20 /* SPC */);
1120 i--;
1121 }
1122 return;
1123 case 0x0d /* CR */:
1124 console_out(c);
1125 console_col = 0;
1126 return;
1127 }
1128 put_graph(c);
1129 }
1130
1131
1132 /*
1133 * output control characters < SPC as ^ and an upper case letter
1134 */
1135 static void
put_ctrl(unsigned char c)1136 put_ctrl(unsigned char c) {
1137 if (c < 0x20 /* SPC */) {
1138 put_graph(0x5e /* ^ */);
1139 c += 0x40 /* @ */;
1140 }
1141 put_graph(c);
1142 }
1143
1144
1145 /*
1146 * get a byte from the console; echo graphical characters and some
1147 * control characters
1148 */
1149 static unsigned char
get_char(void)1150 get_char(void) {
1151 unsigned char c;
1152 c = console_in();
1153 if (c < 0x20 /* SPC */ || c == 0x7f /* DEL */) {
1154 if (c == 0x08 /* BS */ || c == 0x09 /* TAB */ ||
1155 c == 0x0a /* LF */ || c == 0x0d /* CR */) {
1156 put_char(c);
1157 }
1158 } else {
1159 put_char(c);
1160 }
1161 return c;
1162 }
1163
1164
1165 /*
1166 * return a byte from the console in register A (waiting if none is ready),
1167 * echo characters, interpret BS, TAB, and CR, and LF
1168 */
1169 static void
bdos_console_input(void)1170 bdos_console_input(void) {
1171 static const char func[] = "console input";
1172 SYS_ENTRY(func, 0);
1173 reg_l = reg_a = get_char();
1174 reg_h = reg_b = 0;
1175 SYS_EXIT(func, REGS_A);
1176 }
1177
1178
1179 /*
1180 * send the byte in register E to the console
1181 */
1182 static void
bdos_console_output(void)1183 bdos_console_output(void) {
1184 static const char func[] = "console output";
1185 SYS_ENTRY(func, REGS_E);
1186 put_char(reg_e);
1187 reg_l = reg_a = 0;
1188 reg_h = reg_b = 0;
1189 SYS_EXIT(func, 0);
1190 }
1191
1192
1193 /*
1194 * return a byte from the reader device in register A
1195 */
1196 static void
bdos_reader_input(void)1197 bdos_reader_input(void) {
1198 static const char func[] = "reader input";
1199 SYS_ENTRY(func, 0);
1200 reg_l = reg_a = reader_in();
1201 reg_h = reg_b = 0;
1202 SYS_EXIT(func, REGS_A);
1203 }
1204
1205
1206 /*
1207 * send the byte in register E to the punch device
1208 */
1209 static void
bdos_punch_output(void)1210 bdos_punch_output(void) {
1211 static const char func[] = "punch output";
1212 SYS_ENTRY(func, REGS_E);
1213 punch_out(reg_e);
1214 reg_l = reg_a = 0;
1215 reg_h = reg_b = 0;
1216 SYS_EXIT(func, 0);
1217 }
1218
1219
1220 /*
1221 * send the byte in register E to the printer device
1222 */
1223 static void
bdos_list_output(void)1224 bdos_list_output(void) {
1225 static const char func[] = "list output";
1226 SYS_ENTRY(func, REGS_E);
1227 printer_out(reg_e);
1228 reg_l = reg_a = 0;
1229 reg_h = reg_b = 0;
1230 SYS_EXIT(func, 0);
1231 }
1232
1233
1234 /*
1235 * If register E contains 0xff, a non-blocking read from the console
1236 * returns a character in register A or 0x00 if none is available;
1237 * otherwise, the character in register E is sent to the console.
1238 * No echoing or interpretation of control characters is performed
1239 * (calls to this function should not be mixed with the other
1240 * BDOS console I/O functions).
1241 */
1242 static void
bdos_direct_console_io(void)1243 bdos_direct_console_io(void) {
1244 static const char func[] = "direct console io";
1245 SYS_ENTRY(func, REGS_E);
1246 if (reg_e == 0xff) {
1247 if (console_status()) {
1248 reg_l = reg_a = console_in();
1249 } else {
1250 reg_l = reg_a = 0x00;
1251 }
1252 } else {
1253 console_out(reg_e);
1254 reg_l = reg_a = 0;
1255 }
1256 reg_h = reg_b = 0;
1257 SYS_EXIT(func, REGS_A);
1258 }
1259
1260
1261 /*
1262 * return the value of the I/O byte (stored in location 0x0003) in
1263 * register A; the I/O byte functionality proper is not implemented.
1264 */
1265 static void
bdos_get_io_byte(void)1266 bdos_get_io_byte(void) {
1267 static const char func[] = "get io byte";
1268 SYS_ENTRY(func, 0);
1269 reg_l = reg_a = memory[IOBYTE];
1270 reg_h = reg_b = 0;
1271 SYS_EXIT(func, REGS_A);
1272 }
1273
1274
1275 /*
1276 * set the value of the I/O byte from register E; see above.
1277 */
1278 static void
bdos_set_io_byte(void)1279 bdos_set_io_byte(void) {
1280 static const char func[] = "set io byte";
1281 SYS_ENTRY(func, REGS_E);
1282 memory[IOBYTE] = reg_e;
1283 reg_l = reg_a = 0;
1284 reg_h = reg_b = 0;
1285 SYS_EXIT(func, 0);
1286 }
1287
1288
1289 /*
1290 * output $-terminated text string from memory pointed to by DE
1291 */
1292 static void
bdos_print_string(void)1293 bdos_print_string(void) {
1294 int start, addr;
1295 addr = start = get_de();
1296 unsigned char byte;
1297 static const char func[] = "print string";
1298 SYS_ENTRY(func, REGS_DE);
1299 for (;;) {
1300 byte = memory[addr++];
1301 if (byte == 0x24 /* $ */) break;
1302 put_char(byte);
1303 if (addr == MEMORY_SIZE) {
1304 plog("print string: invalid string at 0x%04x", start);
1305 terminate = 1;
1306 term_reason = ERR_BDOSARG;
1307 break;
1308 }
1309 }
1310 reg_l = reg_a = 0;
1311 reg_h = reg_b = 0;
1312 SYS_EXIT(func, 0);
1313 }
1314
1315
1316 /*
1317 * read data from console to a memory buffer pointed to by register DE
1318 */
1319 static void
bdos_read_console_buffer(void)1320 bdos_read_console_buffer(void) {
1321 int start_col, addr, size, curr, free, i;
1322 unsigned char c;
1323 static const char func[] = "read console buffer";
1324 SYS_ENTRY(func, REGS_DE);
1325 /*
1326 * get and check buffer address
1327 */
1328 addr = get_de();
1329 free = size = memory[addr];
1330 if (MEMORY_SIZE - addr < size + 2) {
1331 plog("read console buffer: invalid buffer 0x%04x", addr);
1332 terminate = 1;
1333 term_reason = ERR_BDOSARG;
1334 goto premature_exit;
1335 }
1336 curr = addr + 2;
1337 /*
1338 * remember starting column (for retype or discard input)
1339 */
1340 start_col = console_col;
1341 /*
1342 * proceed as long there is room in the buffer
1343 */
1344 while (free) {
1345 c = console_in();
1346 /*
1347 * at the start of a line, ^C terminates the program,
1348 * otherwise it is an input character
1349 */
1350 if (c == 0x03 /* ETX, ^C */) {
1351 if (free == size) {
1352 put_ctrl(c);
1353 put_crlf();
1354 terminate = 1;
1355 term_reason = OK_CTRLC;
1356 if (log_level >= LL_SYSCALL) {
1357 plog("program terminated by ^C");
1358 }
1359 goto premature_exit;
1360 }
1361 }
1362 /*
1363 * output physical end of line
1364 */
1365 if (c == 0x05 /* ENQ, ^E */) {
1366 put_crlf();
1367 continue;
1368 }
1369 /*
1370 * delete last character by overtyping
1371 */
1372 if (c == 0x08 /* BS */ || c == 0x7f /* DEL */) {
1373 if (free < size) {
1374 curr--;
1375 free++;
1376 put_char(0x08 /* BS */);
1377 put_graph(0x20 /* SPC */);
1378 put_char(0x08 /* BS */);
1379 if (memory[curr] < 0x20) {
1380 /*
1381 * if the deleted character was
1382 * a control character, overtype
1383 * two characters
1384 */
1385 put_char(0x08 /* BS */);
1386 put_graph(0x20 /* SPC */);
1387 put_char(0x08 /* BS */);
1388 }
1389 }
1390 continue;
1391 }
1392 /*
1393 * regular end of input
1394 */
1395 if (c == 0x0a /* LF */ || c == 0x0d /* CR */) break;
1396 /*
1397 * retype the line
1398 */
1399 if (c == 0x12 /* DC2, ^R */) {
1400 put_crlf();
1401 for (i = 0; i < start_col; i++) {
1402 put_graph(0x20 /* SPC */);
1403 }
1404 for (i = addr + 2; i < curr; i++) {
1405 put_ctrl(memory[i]);
1406 }
1407 continue;
1408 }
1409 /*
1410 * discard all previous input
1411 */
1412 if (c == 0x15 /* NAK, ^U */ || c == 0x18 /* CAN, ^X */) {
1413 put_crlf();
1414 for (i = 0; i < start_col; i++) {
1415 put_graph(0x20 /* SPC */);
1416 }
1417 curr = addr + 2;
1418 free = size;
1419 continue;
1420 }
1421 /*
1422 * echo character and store into buffer
1423 */
1424 put_ctrl(c);
1425 memory[curr] = c;
1426 curr++;
1427 free--;
1428 }
1429 /*
1430 * store number of bytes read to second byte of buffer
1431 */
1432 memory[addr + 1] = size - free;
1433 /*
1434 * emit a singe CR
1435 */
1436 put_char(0x0d /* CR */);
1437 /*
1438 * dump valid part of input buffer
1439 */
1440 if (log_level >= LL_SYSCALL) {
1441 plog("dump of input buffer(0x%04x):", addr);
1442 plog_dump(addr, 2 + size - free);
1443 }
1444 premature_exit:
1445 reg_l = reg_a = 0;
1446 reg_h = reg_b = 0;
1447 SYS_EXIT(func, 0);
1448 }
1449
1450
1451 /*
1452 * return 0xff in register A if a character is ready from the console,
1453 * otherwise return 0x00
1454 */
1455 static void
bdos_get_console_status(void)1456 bdos_get_console_status(void) {
1457 static const char func[] = "get console status";
1458 SYS_ENTRY(func, 0);
1459 reg_l = reg_a = console_status() ? 0xff : 0x00;
1460 reg_h = reg_b = 0;
1461 SYS_ENTRY(func, REGS_A);
1462 }
1463
1464
1465 /*
1466 * return system version number in register A (we emulate CP/M 2.2, so
1467 * we have to return 0x22)
1468 */
1469 static void
bdos_return_version_number(void)1470 bdos_return_version_number(void) {
1471 static const char func[] = "return version number";
1472 SYS_ENTRY(func, 0);
1473 reg_l = reg_a = 0x22;
1474 reg_h = reg_b = 0;
1475 SYS_ENTRY(func, REGS_A);
1476 }
1477
1478
1479 /*
1480 * reset disk system: more or less a dummy, but resets the default drive to
1481 * the configuration default (as opposed to CP/M, which sets drive A) and the
1482 * DMA address to 0x0080
1483 */
1484 static void
bdos_reset_disk_system(void)1485 bdos_reset_disk_system(void) {
1486 static const char func[] = "reset disk system";
1487 FDOS_ENTRY(func, 0);
1488 disk_reset();
1489 reg_l = reg_a = 0;
1490 reg_h = reg_b = 0;
1491 FDOS_EXIT(func, 0);
1492 }
1493
1494
1495 /*
1496 * checks a drive number 0..15 if it is a valid and configured drive
1497 * 0 corresponds to drive A, 15 to drive P, i.e. there is no
1498 * default drive!
1499 */
1500 static int
check_drive(int drive,const char * caller)1501 check_drive(int drive, const char *caller) {
1502 int rc = 0;
1503 if (drive > 15 || ! conf_drives[drive]) {
1504 plog("%s: illegal/unconfigured drive", caller);
1505 terminate = 1;
1506 term_reason = ERR_SELECT;
1507 rc = (-1);
1508 }
1509 return rc;
1510 }
1511
1512
1513 /*
1514 * set current drive to the value in register E
1515 */
1516 static void
bdos_select_disk(void)1517 bdos_select_disk(void) {
1518 static const char func[] = "select disk";
1519 FDOS_ENTRY(func, REGS_E);
1520 /*
1521 * drive number must be valid
1522 */
1523 if (! check_drive(reg_e, func)) {
1524 current_drive = reg_e;
1525 memory[DRVUSER] = (current_drive | (current_user << 4));
1526 }
1527 reg_l = reg_a = 0;
1528 reg_h = reg_b = 0;
1529 FDOS_EXIT(func, 0);
1530 }
1531
1532
1533 #define L_UNIX_NAME (MB_LEN_MAX * 12 + 1)
1534 /*
1535 * extract name from FCB and check for validity (ambiguity is o.k.),
1536 * and return it as Unix file name
1537 */
1538 static int
get_unix_name(int fcb,char unix_name[L_UNIX_NAME],const char * caller)1539 get_unix_name(int fcb, char unix_name[L_UNIX_NAME], const char *caller) {
1540 int rc = (-1), i, t, lfn, lext;
1541 unsigned char fn[8], ext[3];
1542 wint_t wc;
1543 char *up;
1544 /*
1545 * copy file name, clearing high bits; calculate length of file name
1546 */
1547 for (i = 0; i < 8; i++) fn[i] = (memory[fcb + 1 + i] & 0x7f);
1548 for (lfn = 8; lfn && fn[lfn - 1] == 0x20 /* SPC */; lfn--);
1549 /*
1550 * file name must contain at least one character
1551 */
1552 if (! lfn) goto premature_exit;
1553 /*
1554 * check file name for valid characters
1555 */
1556 for (i = 0; i < lfn; i++) {
1557 if (! is_valid_in_cfn(fn[i])) goto premature_exit;
1558 }
1559 /*
1560 * copy extension, clearing high bits; calculate length of extension
1561 */
1562 for (i = 0; i < 3; i++) ext[i] = (memory[fcb + 9 + i] & 0x7f);
1563 for (lext = 3; lext && ext[lext - 1] == 0x20 /* SPC */; lext--);
1564 /*
1565 * check extension for valid characters
1566 */
1567 for (i = 0; i < lext; i++) {
1568 if (! is_valid_in_cfn(ext[i])) goto premature_exit;
1569 }
1570 /*
1571 * convert to Unix charset and fold uppercase letters to
1572 * lowercase letters
1573 */
1574 /*
1575 * The assignment to i has the purpose of keeping the
1576 * C compiler in Ubuntu from complaining; we are only
1577 * interested in resetting the internal state of wctoms().
1578 */
1579 i = wctomb(NULL, 0);
1580 up = unix_name;
1581 /*
1582 * convert the file name
1583 */
1584 for (i = 0; i < lfn; i++) {
1585 wc = from_cpm(fn[i]);
1586 if (wc == (-1)) goto premature_exit;
1587 wc = towlower(wc);
1588 t = wctomb(up, wc);
1589 if (t == (-1)) goto premature_exit;
1590 up += t;
1591 }
1592 /*
1593 * the extension may be missing; in that case, no dot is
1594 * appended to the file name
1595 */
1596 if (lext) {
1597 /*
1598 * append dot
1599 */
1600 wc = from_cpm(0x2e /* . */);
1601 if (wc == (-1)) goto premature_exit;
1602 t = wctomb(up, wc);
1603 if (t == (-1)) goto premature_exit;
1604 up += t;
1605 /*
1606 * convert extension
1607 */
1608 for (i = 0; i < lext; i++) {
1609 wc = from_cpm(ext[i]);
1610 if (wc == (-1)) goto premature_exit;
1611 wc = towlower(wc);
1612 t = wctomb(up, wc);
1613 if (t == (-1)) goto premature_exit;
1614 up += t;
1615 }
1616 }
1617 /*
1618 * terminate the result
1619 */
1620 *up = '\0';
1621 rc = 0;
1622 premature_exit:
1623 if (rc) plog("%s (FCB 0x%04x): illegal file name", caller, fcb);
1624 return rc;
1625 }
1626
1627
1628 /*
1629 * check if filename is ambigous (i. e., contains question marks)
1630 */
1631 static int
is_ambigous(const char * name)1632 is_ambigous(const char *name) {
1633 return (strchr(name, '?') != NULL);
1634 }
1635
1636
1637 /*
1638 * get and check FCB address
1639 */
1640 static int
get_fcb(int fcb_size,const char * caller)1641 get_fcb(int fcb_size, const char *caller) {
1642 int fcb = get_de();
1643 if (MEMORY_SIZE - fcb < fcb_size) {
1644 plog("%s (FCB 0x%04x): invalid address", caller, fcb);
1645 terminate = 1;
1646 term_reason = ERR_BDOSARG;
1647 fcb = (-1);
1648 } else {
1649 if (log_level >= LL_FCBS) {
1650 /*
1651 * dump FCB
1652 */
1653 plog("dump of FCB(0x%04x):", fcb);
1654 plog_dump(fcb, fcb_size);
1655 }
1656 }
1657 return fcb;
1658 }
1659
1660
1661 /*
1662 * get and check from fcb drive
1663 */
1664 static int
get_drive(int fcb,const char * caller)1665 get_drive(int fcb, const char *caller) {
1666 int drive;
1667 drive = memory[fcb];
1668 if (! drive) {
1669 drive = current_drive;
1670 } else {
1671 drive--;
1672 }
1673 if (drive > 15 || ! conf_drives[drive]) {
1674 plog("%s (FCB 0x%04x): illegal/unconfigured drive",
1675 caller, fcb);
1676 terminate = 1;
1677 term_reason = ERR_SELECT;
1678 drive = (-1);
1679 }
1680 return drive;
1681 }
1682
1683
1684 /*
1685 * open FCB pointed to by register DE
1686 */
1687 static void
bdos_open_file(void)1688 bdos_open_file(void) {
1689 int fcb, extent, drive, fd = (-1), ambigous = 0, flags = 0;
1690 unsigned char temp_fcb[12];
1691 char unix_name[L_UNIX_NAME];
1692 struct file_list *flp = NULL, *tp;
1693 char *path = NULL;
1694 struct file_data *fdp;
1695 static const char func[] = "open file";
1696 FDOS_ENTRY(func, REGS_DE);
1697 /*
1698 * assume the operation fails
1699 */
1700 reg_a = 0xff;
1701 /*
1702 * get and check FCB address
1703 */
1704 fcb = get_fcb(33, func);
1705 if (fcb == (-1)) goto premature_exit;
1706 /*
1707 * get and check ex (0...31), clear s2
1708 */
1709 extent = memory[fcb + 12];
1710 if (extent > 31) {
1711 plog("%s (FCB 0x%04x): illegal extent number", func, fcb);
1712 goto premature_exit;
1713 }
1714 memory[fcb + 14] = 0x00;
1715 /*
1716 * get and check drive
1717 */
1718 drive = get_drive(fcb, func);
1719 if (drive == (-1)) goto premature_exit;
1720 if (read_only[drive]) flags |= FILE_RODISK;
1721 /*
1722 * extract name from FCB and check it
1723 */
1724 if (get_unix_name(fcb, unix_name, func) == (-1)) goto premature_exit;
1725 /*
1726 * file name may be ambigous
1727 */
1728 ambigous = is_ambigous(unix_name);
1729 /*
1730 * get a list of regular files matching the name in the FCB
1731 * (usually, this will be a one-element list)
1732 */
1733 flp = get_filelist(conf_drives[drive], unix_name, func);
1734 for (tp = flp; tp; tp = tp->next_p) {
1735 /*
1736 * skip files to small for the given extent
1737 */
1738 if (tp->size < ((off_t) extent) * 128) continue;
1739 /*
1740 * matching file found: take name
1741 */
1742 strcpy(unix_name, tp->name);
1743 break;
1744 }
1745 if (! tp) goto premature_exit;
1746 /*
1747 * allocate and assemble file path
1748 */
1749 path = alloc(strlen(conf_drives[drive]) + strlen(unix_name) + 2);
1750 sprintf(path, "%s/%s", conf_drives[drive], unix_name);
1751 /*
1752 * open existing file
1753 */
1754 if (flags) {
1755 /*
1756 * disk r/o: file only can be read
1757 */
1758 fd = open(path, O_RDONLY);
1759 } else {
1760 /*
1761 * try to open r/w
1762 */
1763 fd = open(path, O_RDWR);
1764 if (fd == (-1) && errno == EACCES) {
1765 /*
1766 * if there is a problem with file access,
1767 * assume that the file is r/o
1768 */
1769 flags |= FILE_ROFILE;
1770 fd = open(path, O_RDONLY);
1771 }
1772 }
1773 if (fd == (-1)) {
1774 /*
1775 * there is a file, but we cannot open it
1776 */
1777 plog("%s (FCB 0x%04x): could not open %s: %s", func,
1778 fcb, path, strerror(errno));
1779 terminate = 1;
1780 term_reason = ERR_HOST;
1781 goto premature_exit;
1782 }
1783 /*
1784 * if the file name in the FCB was ambigous, update it
1785 */
1786 if (ambigous) {
1787 setup_fcb(unix_name, temp_fcb);
1788 memcpy(memory + fcb + 1, temp_fcb + 1, 11);
1789 }
1790 /*
1791 * create file structure
1792 */
1793 fdp = create_filedata(fcb, func);
1794 if (! fdp) goto premature_exit;
1795 fdp->path = path;
1796 path = NULL;
1797 fdp->fd = fd;
1798 fd = (-1);
1799 fdp->flags = flags;
1800 /*
1801 * success: always return directory code 0
1802 */
1803 reg_a = 0x00;
1804 premature_exit:
1805 /*
1806 * clean up
1807 */
1808 if (fd != (-1)) close(fd);
1809 free_filelist(flp);
1810 free(path);
1811 reg_l = reg_a;
1812 reg_h = reg_b = 0;
1813 FDOS_EXIT(func, REGS_A);
1814 }
1815
1816
1817 /*
1818 * close FCB pointed to by register DE
1819 */
1820 static void
bdos_close_file(void)1821 bdos_close_file(void) {
1822 int fcb;
1823 struct file_data **fdpp, *fdp;
1824 static const char func[] = "close file";
1825 FDOS_ENTRY(func, REGS_DE);
1826 /*
1827 * assume the operation fails
1828 */
1829 reg_a = 0xff;
1830 /*
1831 * get and check FCB address
1832 */
1833 fcb = get_fcb(33, func);
1834 if (fcb == (-1)) goto premature_exit;
1835 /*
1836 * get and verify FCB data structure
1837 */
1838 fdpp = get_filedata_pp(fcb, func);
1839 if (! fdpp) goto premature_exit;
1840 fdp = *fdpp;
1841 /*
1842 * some programs (e. g. dBase II) continue to use FCBs after
1843 * a call to close; therefore, there is a option to support
1844 * this
1845 * */
1846 if (dont_close) {
1847 /*
1848 * just mark the file as flushed
1849 */
1850 fdp->flags &= ~FILE_WRITTEN;
1851 reg_a = 0x00;
1852 goto premature_exit;
1853 }
1854 /*
1855 * remove file structure from the list
1856 */
1857 *fdpp = fdp->next_p;
1858 /*
1859 * remove the file reference from the FCB
1860 */
1861 memory[fcb + 16] = memory[fcb + 17] =
1862 memory[fcb + 18] = memory[fcb + 19] = 0x00;
1863 /*
1864 * close the associated Unix file
1865 */
1866 if (close(fdp->fd) == (-1)) {
1867 /*
1868 * close failed: something is cleatly amiss
1869 */
1870 plog("%s (FCB 0x%04x): close(%s) failed: %s", func, fcb,
1871 fdp->path, strerror(errno));
1872 terminate = 1;
1873 term_reason = ERR_HOST;
1874 } else {
1875 /*
1876 * success: always return directory code 0
1877 */
1878 reg_a = 0x00;
1879 }
1880 /*
1881 * mark the file descriptor as closed to avoid an error from
1882 * free_fcb_data()
1883 */
1884 fdp->fd = (-1);
1885 /*
1886 * destroy file structure
1887 */
1888 free_filedata(fdp);
1889 premature_exit:
1890 reg_l = reg_a;
1891 reg_h = reg_b = 0;
1892 FDOS_EXIT(func, REGS_A);
1893 return;
1894 }
1895
1896
1897 static struct file_list *search_list_p = NULL;
1898
1899
1900 /*
1901 * common end to bdos_search_for_first() and bdos_search_for_next():
1902 * return the first entry from the file list if there is one left
1903 */
1904 static void
return_direntry(void)1905 return_direntry(void) {
1906 unsigned char temp_fcb[12];
1907 struct file_list *flp;
1908 /*
1909 * default: no more entries in the list
1910 */
1911 reg_a = 0xff;
1912 /*
1913 * get first entry from the list
1914 */
1915 flp = search_list_p;
1916 if (! flp) goto premature_exit;
1917 /*
1918 * construct a directory entry in the DMA area from the
1919 * first entry in the list
1920 */
1921 setup_fcb(flp->name, temp_fcb);
1922 memset(memory + current_dma, 0, 32);
1923 memset(memory + current_dma + 32, 0xe5, 96);
1924 memcpy(memory + current_dma + 1, temp_fcb + 1, 11);
1925 /*
1926 * remove the first entry from the list
1927 */
1928 search_list_p = flp->next_p;
1929 free(flp->name);
1930 free(flp);
1931 /*
1932 * always return directory code 0, since the entry is
1933 * always in the first 32 bytes of the DMA area
1934 */
1935 reg_a = 0x00;
1936 premature_exit:
1937 return;
1938 }
1939
1940
1941 /*
1942 * search for the first file matching the FCB pointed to by DE
1943 */
1944 static void
bdos_search_for_first(void)1945 bdos_search_for_first(void) {
1946 int fcb, drive;
1947 char unix_name[L_UNIX_NAME];
1948 static const char func[] = "search for first";
1949 FDOS_ENTRY(func, REGS_DE);
1950 /*
1951 * assume the operation fails
1952 */
1953 reg_a = 0xff;
1954 /*
1955 * get and check FCB address
1956 */
1957 fcb = get_fcb(32, func);
1958 if (fcb == (-1)) goto premature_exit;
1959 /*
1960 * get and check drive
1961 */
1962 drive = memory[fcb];
1963 if (drive == 0x3f /* ? */) {
1964 /*
1965 * wildcard drive : since user areas are not supported,
1966 * this simply specifies the current drive
1967 */
1968 drive = current_drive;
1969 } else {
1970 drive = get_drive(fcb, func);
1971 }
1972 if (drive == (-1)) goto premature_exit;
1973 /*
1974 * extract name from FCB and check it
1975 */
1976 if (get_unix_name(fcb, unix_name, func) == (-1)) goto premature_exit;
1977 /*
1978 * free old file list
1979 */
1980 free_filelist(search_list_p);
1981 /*
1982 * get a list of all matching file names
1983 */
1984 search_list_p = get_filelist(conf_drives[drive], unix_name, func);
1985 /*
1986 * return entry from the file list
1987 */
1988 return_direntry();
1989 premature_exit:
1990 reg_l = reg_a;
1991 reg_h = reg_b = 0;
1992 FDOS_EXIT(func, REGS_A);
1993 return;
1994 }
1995
1996
1997 /*
1998 * return the next file name from the list established by
1999 * bdos_search_for_first()
2000 */
2001 static void
bdos_search_for_next(void)2002 bdos_search_for_next(void) {
2003 static const char func[] = "search for next";
2004 FDOS_ENTRY(func, 0);
2005 /*
2006 * return entry from the file list
2007 */
2008 return_direntry();
2009 reg_l = reg_a;
2010 reg_h = reg_b = 0;
2011 FDOS_EXIT(func, REGS_A);
2012 return;
2013 }
2014
2015
2016 /*
2017 * delete files described by FCB pointed to by register DE
2018 */
2019 static void
bdos_delete_file(void)2020 bdos_delete_file(void) {
2021 int fcb, drive, t;
2022 char unix_name[L_UNIX_NAME];
2023 struct file_list *flp = NULL, *tp;
2024 char *path = NULL;
2025 static const char func[] = "delete file";
2026 FDOS_ENTRY(func, REGS_DE);
2027 /*
2028 * assume the operation fails
2029 */
2030 reg_a = 0xff;
2031 /*
2032 * get and check FCB address
2033 */
2034 fcb = get_fcb(32, func);
2035 if (fcb == (-1)) goto premature_exit;
2036 /*
2037 * get and check drive
2038 */
2039 drive = get_drive(fcb, func);
2040 if (drive == (-1)) goto premature_exit;
2041 /*
2042 * extract name from FCB and check it
2043 */
2044 if (get_unix_name(fcb, unix_name, func) == (-1)) goto premature_exit;
2045 /*
2046 * get a list of matching files
2047 */
2048 flp = get_filelist(conf_drives[drive], unix_name, func);
2049 if (! flp) goto premature_exit;
2050 /*
2051 * if the disk is write only, scream and die
2052 */
2053 if (read_only[drive]) {
2054 plog("%s (FCB 0x%04x): write protected disk", func, fcb);
2055 terminate = 1;
2056 term_reason = ERR_RODISK;
2057 goto premature_exit;
2058 }
2059 /*
2060 * traverse file list, deleting files
2061 */
2062 for (tp = flp; tp; tp = tp->next_p) {
2063 /*
2064 * build path
2065 */
2066 path = alloc(sizeof conf_drives[drive] + strlen(tp->name) + 2);
2067 sprintf(path, "%s/%s", conf_drives[drive], tp->name);
2068 /*
2069 * delete file
2070 */
2071 t = unlink(path);
2072 if (t == (-1)) {
2073 /*
2074 * failed: assume write protected file
2075 */
2076 plog("%s (FCB 0x%04x): unlink(%s) failed: %s", func,
2077 fcb, path, strerror(errno));
2078 terminate = 1;
2079 term_reason = ERR_ROFILE;
2080 goto premature_exit;
2081 }
2082 free(path);
2083 path = NULL;
2084 }
2085 /*
2086 * success: always return directory code 0
2087 */
2088 reg_a = 0x00;
2089 premature_exit:
2090 /*
2091 * clean up
2092 */
2093 free_filelist(flp);
2094 free(path);
2095 reg_l = reg_a;
2096 reg_h = reg_b = 0;
2097 FDOS_EXIT(func, REGS_A);
2098 }
2099
2100
2101 /*
2102 * get and check FCB data structure: there must be a file data structure
2103 * referenced by the FCB address in DE
2104 */
2105 static struct file_data *
file_fcb(int fcb,const char * caller)2106 file_fcb(int fcb, const char *caller) {
2107 struct file_data **fdpp = get_filedata_pp(fcb, caller);
2108 return fdpp ? *fdpp : NULL;
2109 }
2110
2111
2112 /*
2113 * get current sequential record offset from FCB
2114 */
2115 static int
get_offset(int fcb,const char * caller)2116 get_offset(int fcb, const char *caller) {
2117 unsigned char ex, s2, cr;
2118 int offset;
2119 /*
2120 * get offset components
2121 */
2122 s2 = memory[fcb + 14];
2123 ex = memory[fcb + 12];
2124 cr = memory[fcb + 32];
2125 /*
2126 * offset must be in the range of 0 (start of file) to 65536
2127 * (the record after the maximal record number)
2128 */
2129 if (cr > 127 || ex > 31 || s2 > 16 || (s2 == 16 && (cr || ex))) {
2130 plog("%s (FCB 0x%04x): invalid file offset", caller, fcb);
2131 offset = (-1);
2132 } else {
2133 offset = s2;
2134 offset <<= 5;
2135 offset |= ex;
2136 offset <<= 7;
2137 offset |= cr;
2138 }
2139 return offset;
2140 }
2141
2142
2143 /*
2144 * store offset in FCB (offset is in the range 0...65536)
2145 */
2146 static void
set_offset(int fcb,int offset)2147 set_offset(int fcb, int offset) {
2148 memory[fcb + 32] = offset & 0x007f;
2149 memory[fcb + 12] = (offset >> 7) & 0x001f;
2150 memory[fcb + 14] = offset >> 12;
2151 }
2152
2153
2154 /*
2155 * dump current DMA area
2156 */
2157 static void
dump_record(void)2158 dump_record(void) {
2159 plog("dump of record(0x%04x):", current_dma);
2160 plog_dump(current_dma, 128);
2161 }
2162
2163
2164 /*
2165 * read a 128 byte record from a file to the current DMA area;
2166 * return (-1) on EOF; incomplete records are filled with 0x1a (SUB/^Z)
2167 */
2168 static int
read_record(int fcb,struct file_data * fdp,const char * caller)2169 read_record(int fcb, struct file_data *fdp, const char *caller) {
2170 unsigned char *bp = memory + current_dma;
2171 size_t n = 128;
2172 ssize_t t;
2173 while (n) {
2174 t = read(fdp->fd, bp, n);
2175 if (! t) break;
2176 if (t == (-1)) {
2177 plog("%s (FCB 0x%04x): read(%s) failed: %s", caller,
2178 fcb, fdp->path, strerror(errno));
2179 terminate = 1;
2180 term_reason = ERR_HOST;
2181 break;
2182 }
2183 bp += t;
2184 n -=t;
2185 }
2186 if (n < 128) memset(bp, 0x1a /* SUB */, n);
2187 if (log_level >= LL_RECORDS && n < 128) dump_record();
2188 return (n == 128) ? (-1) : 0;
2189 }
2190
2191
2192 /*
2193 * write a 128 byte record from the current DMA area to a file;
2194 * return (-1) on error
2195 */
2196 static int
write_record(int fcb,struct file_data * fdp,const char * caller)2197 write_record(int fcb, struct file_data *fdp, const char *caller) {
2198 unsigned char *bp = memory + current_dma;
2199 size_t n = 128;
2200 ssize_t t;
2201 while (n) {
2202 t = write(fdp->fd, bp, n);
2203 if (t == (-1)) {
2204 plog("%s (FCB 0x%04x): write(%s) failed: %s", caller,
2205 fcb, fdp->path, strerror(errno));
2206 terminate = 1;
2207 term_reason = ERR_HOST;
2208 break;
2209 }
2210 bp += t;
2211 n -=t;
2212 }
2213 fdp->flags |= FILE_WRITTEN;
2214 if (log_level >= LL_RECORDS) dump_record();
2215 return n ? (-1) : 0;
2216 }
2217
2218
2219 /*
2220 * seek to a particular offset in a Unix file (the offset is given in
2221 * CP/M records of 128 bytes
2222 */
2223 static int
seek(int fcb,struct file_data * fdp,int offset,const char * caller)2224 seek(int fcb, struct file_data *fdp, int offset, const char *caller) {
2225 int rc = 0;
2226 off_t unix_offset;
2227 /*
2228 * calculate unix file offset and set file position
2229 */
2230 unix_offset = offset;
2231 unix_offset *= 128;
2232 if (lseek(fdp->fd, unix_offset, SEEK_SET) == (off_t) (-1)) {
2233 plog("%s (FCB 0x%04x): lseek(%s) failed: %s", caller, fcb,
2234 fdp->path, strerror(errno));
2235 terminate = 1;
2236 term_reason = ERR_HOST;
2237 rc = (-1);
2238 }
2239 return rc;
2240 }
2241
2242
2243 /*
2244 * read next sequential record from the FCB pointed to by DE
2245 */
2246 static void
bdos_read_sequential(void)2247 bdos_read_sequential(void) {
2248 int fcb, offset;
2249 struct file_data *fdp = NULL;
2250 static const char func[] = "read sequential";
2251 FDOS_ENTRY(func, REGS_DE);
2252 /*
2253 * assume the operation fails (error code 0x01 is
2254 * "reading unwritten data", i. e. EOF)
2255 */
2256 reg_a = 0x01;
2257 /*
2258 * get and check FCB address
2259 */
2260 fcb = get_fcb(33, func);
2261 if (fcb == (-1)) goto premature_exit;
2262 /*
2263 * get and check corresponding FCB structure
2264 */
2265 fdp = file_fcb(fcb, func);
2266 if (! fdp) goto premature_exit;
2267 /*
2268 * get and check current file offset;
2269 */
2270 offset = get_offset(fcb, func);
2271 if (offset == (-1) || offset == 65536) {
2272 /*
2273 * error code 0x06 for "record out of range"
2274 */
2275 reg_a = 0x06;
2276 goto premature_exit;
2277 }
2278 /*
2279 * set file position
2280 */
2281 if (seek(fcb, fdp, offset, func) == (-1)) goto premature_exit;
2282 /*
2283 * read 128 bytes to the current DMA buffer
2284 */
2285 if (read_record(fcb, fdp, func) == (-1)) goto premature_exit;
2286 /*
2287 * advance offset in FCB
2288 */
2289 set_offset(fcb, offset + 1);
2290 /*
2291 * success
2292 */
2293 reg_a = 0x00;
2294 premature_exit:
2295 reg_l = reg_a;
2296 reg_h = reg_b = 0;
2297 FDOS_EXIT(func, REGS_A);
2298 return;
2299 }
2300
2301
2302 /*
2303 * check if disk or file are writeable
2304 */
2305 static int
check_writeable(int fcb,struct file_data * fdp,const char * caller)2306 check_writeable(int fcb, struct file_data *fdp, const char *caller) {
2307 int rc = (-1);
2308 /*
2309 * disk read only?
2310 */
2311 if (fdp->flags & FILE_RODISK) {
2312 plog("%s (FCB 0x%04x): %s: write protected disk", caller, fcb,
2313 fdp->path);
2314 terminate = 1;
2315 term_reason = ERR_RODISK;
2316 goto premature_exit;
2317 }
2318 /*
2319 * file read only?
2320 */
2321 if (fdp->flags & FILE_ROFILE) {
2322 plog("%s (FCB 0x%04x): %s is write protected", caller, fcb,
2323 fdp->path);
2324 terminate = 1;
2325 term_reason = ERR_ROFILE;
2326 goto premature_exit;
2327 }
2328 rc = 0;
2329 premature_exit:
2330 return rc;
2331 }
2332
2333
2334 /*
2335 * write next sequential record to the FCB pointed to by DE
2336 */
2337 static void
bdos_write_sequential(void)2338 bdos_write_sequential(void) {
2339 int fcb, offset;
2340 struct file_data *fdp;
2341 static const char func[] = "write sequential";
2342 FDOS_ENTRY(func, REGS_DE);
2343 /*
2344 * assume the operation fails (0x02 means
2345 * "no available data block", i. e. "disk full")
2346 */
2347 reg_a = 0x02;
2348 /*
2349 * get and check FCB address
2350 */
2351 fcb = get_fcb(33, func);
2352 if (fcb == (-1)) goto premature_exit;
2353 /*
2354 * get and check corresponding FCB structure
2355 */
2356 fdp = file_fcb(fcb, func);
2357 if (! fdp) goto premature_exit;
2358 /*
2359 * check for write protection
2360 */
2361 if (check_writeable(fcb, fdp, func) == (-1)) goto premature_exit;
2362 /*
2363 * get and check current file offset;
2364 */
2365 offset = get_offset(fcb, func);
2366 if (offset == (-1) || offset == 65536) {
2367 /*
2368 * record out of range
2369 */
2370 reg_a = 0x06;
2371 goto premature_exit;
2372 }
2373 /*
2374 * set file position
2375 */
2376 if (seek(fcb, fdp, offset, func) == (-1)) goto premature_exit;
2377 /*
2378 * write 128 bytes from the current DMA buffer
2379 */
2380 if (write_record(fcb, fdp, func) == (-1)) goto premature_exit;
2381 /*
2382 * advance offset in FCB
2383 */
2384 set_offset(fcb, offset + 1);
2385 /*
2386 * success
2387 */
2388 reg_a = 0;
2389 premature_exit:
2390 reg_l = reg_a;
2391 reg_h = reg_b = 0;
2392 FDOS_EXIT(func, REGS_A);
2393 return;
2394 }
2395
2396
2397 /*
2398 * create file described by FCB pointed to by register DE and open it
2399 */
2400 static void
bdos_make_file(void)2401 bdos_make_file(void) {
2402 int fcb, drive, fd = (-1);
2403 char unix_name[L_UNIX_NAME];
2404 char *path = NULL;
2405 struct file_data *fdp;
2406 static const char func[] = "make file";
2407 FDOS_ENTRY(func, REGS_DE);
2408 /*
2409 * assume the operation fails
2410 */
2411 reg_a = 0xff;
2412 /*
2413 * get and check FCB address
2414 */
2415 fcb = get_fcb(33, func);
2416 if (fcb == (-1)) goto premature_exit;
2417 /*
2418 * check ex (0...31), clear s2
2419 */
2420 if (memory[fcb + 12] > 31) {
2421 plog("%s (FCB 0x%04x): illegal extent number", func, fcb);
2422 goto premature_exit;
2423 }
2424 memory[fcb + 14] = 0x00;
2425 /*
2426 * get and check drive
2427 */
2428 drive = get_drive(fcb, func);
2429 if (drive == (-1)) goto premature_exit;
2430 /*
2431 * drive must not be read only
2432 */
2433 if (read_only[drive]) {
2434 plog("%s (FCB 0x%04x): disk write protected", func, fcb);
2435 terminate = 1;
2436 term_reason = ERR_RODISK;
2437 goto premature_exit;
2438 }
2439 /*
2440 * extract name from FCB and check it
2441 */
2442 if (get_unix_name(fcb, unix_name, func) == (-1)) goto premature_exit;
2443 /*
2444 * file name may not be ambigous
2445 */
2446 if (is_ambigous(unix_name)) {
2447 plog("%s (FCB 0x%04x): ambigous file name %s", func,
2448 fcb, unix_name);
2449 goto premature_exit;
2450 }
2451 /*
2452 * allocate and assemble file path
2453 */
2454 path = alloc(strlen(conf_drives[drive]) + strlen(unix_name) + 2);
2455 sprintf(path, "%s/%s", conf_drives[drive], unix_name);
2456 /*
2457 * create new file
2458 */
2459 fd = open(path, O_CREAT|O_EXCL|O_RDWR, 0666);
2460 if (fd == (-1)) {
2461 plog("%s (FCB 0x%04x): could not create %s: %s", func, fcb,
2462 path, strerror(errno));
2463 terminate = 1;
2464 term_reason = ERR_HOST;
2465 goto premature_exit;
2466 }
2467 /*
2468 * create file structure
2469 */
2470 fdp = create_filedata(fcb, func);
2471 fdp->path = path;
2472 path = NULL;
2473 fdp->fd = fd;
2474 fd = (-1);
2475 fdp->flags = 0;
2476 /*
2477 * success: always return directory code 0
2478 */
2479 reg_a = 0x00;
2480 premature_exit:
2481 /*
2482 * clean up
2483 */
2484 if (fd != (-1)) close(fd);
2485 free(path);
2486 reg_l = reg_a;
2487 reg_h = reg_b = 0;
2488 FDOS_EXIT(func, REGS_A);
2489 }
2490
2491
2492 /*
2493 * rename file described by FCB pointed to by register DE
2494 */
2495 static void
bdos_rename_file(void)2496 bdos_rename_file(void) {
2497 int fcb, drive;
2498 char unix_name_old[L_UNIX_NAME], unix_name_new[L_UNIX_NAME],
2499 *path_old = NULL, *path_new = NULL;
2500 static const char func[] = "rename file";
2501 size_t l;
2502 FDOS_ENTRY(func, REGS_DE);
2503 /*
2504 * assume the operation fails
2505 */
2506 reg_a = 0xff;
2507 /*
2508 * get and check FCB address
2509 */
2510 fcb = get_fcb(32, func);
2511 if (fcb == (-1)) goto premature_exit;
2512 /*
2513 * get and check drive
2514 */
2515 drive = get_drive(fcb, func);
2516 if (drive == (-1)) goto premature_exit;
2517 /*
2518 * drive must not be read only
2519 */
2520 if (read_only[drive]) {
2521 plog("%s (FCB 0x%04x): disk write protected", func, fcb);
2522 terminate = 1;
2523 term_reason = ERR_RODISK;
2524 goto premature_exit;
2525 }
2526 /*
2527 * extract old and new names from FCB and check them
2528 */
2529 if (get_unix_name(fcb, unix_name_old, func) == (-1)) {
2530 goto premature_exit;
2531 }
2532 if (get_unix_name(fcb + 16, unix_name_new, func) == (-1)) {
2533 goto premature_exit;
2534 }
2535 /*
2536 * file names may not be ambigous
2537 */
2538 if (is_ambigous(unix_name_old)) {
2539 plog("%s (FCB 0x%04x): ambigous old file name %s", func,
2540 fcb, unix_name_old);
2541 goto premature_exit;
2542 }
2543 if (is_ambigous(unix_name_new)) {
2544 plog("%s (FCB 0x%04x): ambigous new file name %s", func,
2545 fcb, unix_name_new);
2546 goto premature_exit;
2547 }
2548 /*
2549 * allocate and assemble old and new file paths
2550 */
2551 l = strlen(conf_drives[drive]);
2552 path_old = alloc(l + strlen(unix_name_old) + 2);
2553 sprintf(path_old, "%s/%s", conf_drives[drive], unix_name_old);
2554 path_new = alloc(l + strlen(unix_name_new) + 2);
2555 sprintf(path_new, "%s/%s", conf_drives[drive], unix_name_new);
2556 /*
2557 * create new link
2558 */
2559 if (link(path_old, path_new) == (-1)) {
2560 plog("%s (FCB 0x%04x): link(%s, %s) failed: %s", func,
2561 fcb, path_old, path_new, strerror(errno));
2562 switch (errno) {
2563 case ENOENT:
2564 case EEXIST:
2565 break;
2566 case EACCES:
2567 terminate = 1;
2568 term_reason = ERR_ROFILE;
2569 break;
2570 default:
2571 terminate = 1;
2572 term_reason = ERR_HOST;
2573 break;
2574 }
2575 goto premature_exit;
2576 }
2577 /*
2578 * delete old link
2579 */
2580 if (unlink(path_old) == (-1)) {
2581 plog("%s (FCB 0x%04x): unlink(%s) failed: %s", func,
2582 fcb, path_old, strerror(errno));
2583 terminate = 1;
2584 term_reason = ERR_HOST;
2585 unlink(path_new);
2586 goto premature_exit;
2587 }
2588 /*
2589 * success: always return directory code 0
2590 */
2591 reg_a = 0x00;
2592 premature_exit:
2593 /*
2594 * clean up
2595 */
2596 free(path_old);
2597 free(path_new);
2598 reg_l = reg_a;
2599 reg_h = reg_b = 0;
2600 FDOS_EXIT(func, REGS_A);
2601 }
2602
2603
2604 /*
2605 * all configured drives are always logged in
2606 */
2607 static void
bdos_return_log_in_vector(void)2608 bdos_return_log_in_vector(void) {
2609 static const char func[] = "return log in vector";
2610 int vector = 0, i;
2611 FDOS_ENTRY(func, 0);
2612 for (i = 15; i >= 0; i--) {
2613 vector <<= 1;
2614 vector |= (conf_drives[i] != NULL);
2615 }
2616 reg_l = reg_a = (vector & 0xff);
2617 reg_h = reg_b = ((vector >> 8) & 0xff);
2618 FDOS_EXIT(func, REGS_HL);
2619 }
2620
2621
2622 /*
2623 * return the current disk number
2624 */
2625 static void
bdos_return_current_disk(void)2626 bdos_return_current_disk(void) {
2627 static const char func[] = "return current disk";
2628 FDOS_ENTRY(func, 0);
2629 reg_l = reg_a = current_drive;
2630 reg_h = reg_b = 0;
2631 FDOS_EXIT(func, REGS_A);
2632 }
2633
2634
2635 /*
2636 * check and set new DMA address
2637 */
2638 static void
bdos_set_dma_address(void)2639 bdos_set_dma_address(void) {
2640 static const char func[] = "set dma address";
2641 int addr = get_de();
2642 FDOS_ENTRY(func, REGS_DE);
2643 if (MEMORY_SIZE - addr < 128) {
2644 plog("set dma address: illegal address 0x%04x", addr);
2645 terminate = 1;
2646 term_reason = ERR_BDOSARG;
2647 } else {
2648 current_dma = addr;
2649 }
2650 reg_l = reg_a = 0;
2651 reg_h = reg_b = 0;
2652 FDOS_EXIT(func, 0);
2653 }
2654
2655
2656 /*
2657 * the allocation vector is a dummy in this implementation; all drives
2658 * share the same allocation vector to save memory space
2659 */
2660 static void
bdos_get_addr_alloc(void)2661 bdos_get_addr_alloc(void) {
2662 static const char func[] = "get addr alloc";
2663 FDOS_ENTRY(func, 0);
2664 reg_l = reg_a = (ALV & 0xff);
2665 reg_h = reg_b = ((ALV >> 8) & 0xff);
2666 FDOS_EXIT(func, REGS_HL);
2667 }
2668
2669
2670 /*
2671 * mark the current disk as write protected
2672 */
2673 static void
bdos_write_protect_disk(void)2674 bdos_write_protect_disk(void) {
2675 static const char func[] = "write protect disk";
2676 FDOS_ENTRY(func, 0);
2677 read_only[current_drive] = 1;
2678 reg_l = reg_a = 0;
2679 reg_h = reg_b = 0;
2680 FDOS_EXIT(func, 0);
2681 }
2682
2683
2684 /*
2685 * return read-only array as bit vector
2686 */
2687 static void
bdos_get_read_only_vector(void)2688 bdos_get_read_only_vector(void) {
2689 int vector = 0, i;
2690 static const char func[] = "get read only vector";
2691 FDOS_ENTRY(func, 0);
2692 for (i = 15; i >= 0; i--) {
2693 vector <<= 1;
2694 vector |= read_only[i];
2695 }
2696 reg_l = reg_a = (vector & 0xff);
2697 reg_h = reg_b = ((vector >> 8) & 0xff);
2698 FDOS_EXIT(func, REGS_HL);
2699 }
2700
2701
2702 /*
2703 * set file attributes is just a dummy, since file attributes are not
2704 * implemented; there is just some error checking
2705 */
2706 static void
bdos_set_file_attributes(void)2707 bdos_set_file_attributes(void) {
2708 int fcb, drive;
2709 char unix_name[L_UNIX_NAME], *path = NULL;
2710 static const char func[] = "set file attributes";
2711 FDOS_ENTRY(func, REGS_DE);
2712 /*
2713 * assume the operation fails
2714 */
2715 reg_a = 0xff;
2716 /*
2717 * get and check FCB address
2718 */
2719 fcb = get_fcb(32, func);
2720 if (fcb == (-1)) goto premature_exit;
2721 /*
2722 * get and check drive
2723 */
2724 drive = get_drive(fcb, func);
2725 if (drive == (-1)) goto premature_exit;
2726 /*
2727 * drive must not be read only
2728 */
2729 if (read_only[drive]) {
2730 plog("%s (FCB 0x%04x): disk write protected", func, fcb);
2731 terminate = 1;
2732 term_reason = ERR_RODISK;
2733 goto premature_exit;
2734 }
2735 if (get_unix_name(fcb, unix_name, func) == (-1)) goto premature_exit;
2736 /*
2737 * file name may not be ambigous
2738 */
2739 if (is_ambigous(unix_name)) {
2740 plog("%s (FCB 0x%04x): ambigous file name %s", func,
2741 fcb, unix_name);
2742 goto premature_exit;
2743 }
2744 /*
2745 * success: always return directory code 0
2746 */
2747 reg_a = 0x00;
2748 premature_exit:
2749 /*
2750 * clean up
2751 */
2752 free(path);
2753 reg_l = reg_a;
2754 reg_h = reg_b = 0;
2755 FDOS_EXIT(func, REGS_A);
2756 }
2757
2758
2759 /*
2760 * the disk parameters are dummy values (but consistent with the size
2761 * of the allocation vector); all drives share the same disk parameter block
2762 */
2763 static void
bdos_get_addr_diskparams(void)2764 bdos_get_addr_diskparams(void) {
2765 static const char func[] = "get addr diskparams";
2766 FDOS_ENTRY(func, 0);
2767 reg_l = reg_a = (DPB & 0xff);
2768 reg_h = reg_b = ((DPB >> 8) & 0xff);
2769 FDOS_EXIT(func, REGS_HL);
2770 }
2771
2772
2773 /*
2774 * user code manipulation is not supported, but tolerated
2775 */
2776 static void
bdos_set_get_user_code(void)2777 bdos_set_get_user_code(void) {
2778 static const char func[] = "get set user code";
2779 FDOS_ENTRY(func, REGS_E);
2780 if (reg_e == 0xff) {
2781 reg_l = reg_a = current_user;
2782 } else {
2783 current_user = (reg_e & 0x0f);
2784 memory[DRVUSER] = (current_drive | (current_user << 4));
2785 reg_l = reg_a = 0;
2786 }
2787 reg_h = reg_b = 0;
2788 FDOS_EXIT(func, REGS_A);
2789 }
2790
2791
2792 /*
2793 * get random record number from FCB
2794 */
2795 static int
get_random(int fcb,const char * caller)2796 get_random(int fcb, const char *caller) {
2797 unsigned char r0, r1, r2;
2798 int random;
2799 /*
2800 * get random record number bytes
2801 */
2802 r0 = memory[fcb + 33];
2803 r1 = memory[fcb + 34];
2804 r2 = memory[fcb + 35];
2805 /*
2806 * random record number must be in the range of 0 (start of file)
2807 * to 65536 (the record after the maximal record number)
2808 */
2809 if (r2 > 1 || (r2 == 1 && (r0 || r1))) {
2810 plog("%s (FCB 0x%04x): invalid random record number",
2811 caller, fcb);
2812 random = (-1);
2813 } else {
2814 random = r2;
2815 random <<= 8;
2816 random |= r1;
2817 random <<= 8;
2818 random |= r0;
2819 }
2820 return random;
2821 }
2822
2823
2824 /*
2825 * read random record from the FCB pointed to by DE
2826 */
2827 static void
bdos_read_random(void)2828 bdos_read_random(void) {
2829 int fcb, offset;
2830 struct file_data *fdp;
2831 static const char func[] = "read random";
2832 FDOS_ENTRY(func, REGS_DE);
2833 /*
2834 * assume the operation fails (0x01 is "reading unwritten data");
2835 */
2836 reg_a = 0x01;
2837 /*
2838 * get and check FCB address
2839 */
2840 fcb = get_fcb(36, func);
2841 if (fcb == (-1)) goto premature_exit;
2842 /*
2843 * get and check corresponding file structure
2844 */
2845 fdp = file_fcb(fcb, func);
2846 if (! fdp) goto premature_exit;
2847 /*
2848 * get and check current random record number;
2849 */
2850 offset = get_random(fcb, func);
2851 if (offset == (-1) || offset == 65536) {
2852 /*
2853 * record out of range
2854 */
2855 reg_a = 0x06;
2856 goto premature_exit;
2857 }
2858 /*
2859 * set file position
2860 */
2861 if (seek(fcb, fdp, offset, func) == (-1)) goto premature_exit;
2862 /*
2863 * read 128 bytes to the current DMA buffer
2864 */
2865 if (read_record(fcb, fdp, func) == (-1)) {
2866 /*
2867 * report "reading unwritten data", i. e. EOF
2868 */
2869 reg_a = 0x01;
2870 goto premature_exit;
2871 }
2872 /*
2873 * set sequential offset in FCB
2874 */
2875 set_offset(fcb, offset);
2876 /*
2877 * report success
2878 */
2879 reg_a = 0x00;
2880 premature_exit:
2881 reg_l = reg_a;
2882 reg_h = reg_b = 0;
2883 FDOS_EXIT(func, REGS_A);
2884 return;
2885 }
2886
2887
2888 /*
2889 * common functionality of bdos_write_random() and
2890 * bdos_write_random_with_zero_fill() (in fact, both functions are identical
2891 * but for their name, since unwritten records read as zero under Unix
2892 * anyway)
2893 */
2894 static void
write_random(const char * caller)2895 write_random(const char *caller) {
2896 int fcb, offset;
2897 struct file_data *fdp;
2898 /*
2899 * assume the operation fails (0x05 means "no available directory
2900 * space"; 0x02 "no available data block" would seem more adequate,
2901 * but according to the CP/M documentation, it is not reported in
2902 * random mode)
2903 */
2904 reg_a = 0x05;
2905 /*
2906 * get and check FCB address
2907 */
2908 fcb = get_fcb(36, caller);
2909 if (fcb == (-1)) goto premature_exit;
2910 /*
2911 * get and check corresponding FCB structure
2912 */
2913 fdp = file_fcb(fcb, caller);
2914 if (! fdp) goto premature_exit;
2915 /*
2916 * check for write protection
2917 */
2918 if (check_writeable(fcb, fdp, caller) == (-1)) goto premature_exit;
2919 /*
2920 * get and check current random record number;
2921 */
2922 offset = get_random(fcb, caller);
2923 if (offset == (-1) || offset == 65536) {
2924 /*
2925 * record out of range
2926 */
2927 reg_a = 0x06;
2928 goto premature_exit;
2929 }
2930 /*
2931 * set file position
2932 */
2933 if (seek(fcb, fdp, offset, caller) == (-1)) goto premature_exit;
2934 /*
2935 * write 128 bytes from the current DMA buffer
2936 */
2937 if (write_record(fcb, fdp, caller) == (-1)) goto premature_exit;
2938 /*
2939 * set sequential offset in FCB
2940 */
2941 set_offset(fcb, offset);
2942 /*
2943 * report success
2944 */
2945 reg_a = 0;
2946 premature_exit:
2947 return;
2948 }
2949
2950
2951 /*
2952 * write random record from the FCB pointed to by DE
2953 */
2954 static void
bdos_write_random(void)2955 bdos_write_random(void) {
2956 static const char func[] = "write random";
2957 FDOS_ENTRY(func, REGS_DE);
2958 write_random(func);
2959 reg_l = reg_a;
2960 reg_h = reg_b = 0;
2961 FDOS_EXIT(func, REGS_A);
2962 }
2963
2964
2965 /*
2966 * calculate file size and deposit it in the random record number of the
2967 * FCB pointed to by DE
2968 */
2969 static void
bdos_compute_file_size(void)2970 bdos_compute_file_size(void) {
2971 int fcb, drive, t;
2972 off_t size;
2973 char unix_name[L_UNIX_NAME];
2974 char *path = NULL;
2975 struct stat s;
2976 static const char func[] = "compute file size";
2977 FDOS_ENTRY(func, REGS_DE);
2978 /*
2979 * assume the operation fails
2980 */
2981 reg_a = 0xff;
2982 /*
2983 * get and check FCB address
2984 */
2985 fcb = get_fcb(36, func);
2986 if (fcb == (-1)) goto premature_exit;
2987 /*
2988 * get and check drive
2989 */
2990 drive = get_drive(fcb, func);
2991 if (drive == (-1)) goto premature_exit;
2992 /*
2993 * extract name from FCB and check it
2994 */
2995 if (get_unix_name(fcb, unix_name, func) == (-1)) goto premature_exit;
2996 /*
2997 * file name may not be ambigous
2998 */
2999 if (is_ambigous(unix_name)) {
3000 plog("%s (FCB 0x%04x): ambigous file name %s", func,
3001 fcb, unix_name);
3002 goto premature_exit;
3003 }
3004 /*
3005 * allocate and assemble file path
3006 */
3007 path = alloc(strlen(conf_drives[drive]) + strlen(unix_name) + 2);
3008 sprintf(path, "%s/%s", conf_drives[drive], unix_name);
3009 /*
3010 * get size of file
3011 */
3012 t = lstat(path, &s);
3013 if (t == (-1)) {
3014 plog("%s (FCB 0x%04x): lstat(%s) failed: %s", func, fcb,
3015 path, strerror(errno));
3016 goto premature_exit;
3017 }
3018 if (! S_ISREG(s.st_mode)) {
3019 plog("%s (FCB 0x%04x): %s is no regular file", func, fcb,
3020 path);
3021 goto premature_exit;
3022 }
3023 if (s.st_size > 8 * 1024 * 1024) {
3024 plog("%s (FCB 0x%04x): %s is larger than 8 MB", func, fcb,
3025 path);
3026 goto premature_exit;
3027 }
3028 size = (s.st_size + 127) / 128;
3029 /*
3030 * set random record number
3031 */
3032 memory[fcb + 33] = (size & 0xff);
3033 memory[fcb + 34] = ((size >> 8) & 0xff);
3034 memory[fcb + 35] = ((size >> 16) & 0xff);
3035 /*
3036 * success: always return directory code 0
3037 */
3038 reg_a = 0x00;
3039 premature_exit:
3040 free(path);
3041 reg_l = reg_a;
3042 reg_h = reg_b = 0;
3043 FDOS_EXIT(func, REGS_A);
3044 }
3045
3046
3047 /*
3048 * set random record field from the current sequential offset
3049 */
3050 static void
bdos_set_random_record(void)3051 bdos_set_random_record(void) {
3052 int fcb, offset;
3053 static const char func[] = "set random record";
3054 FDOS_ENTRY(func, REGS_DE);
3055 /*
3056 * assume the operation fails
3057 */
3058 reg_a = 0xff;
3059 /*
3060 * get and check FCB address
3061 */
3062 fcb = get_fcb(36, func);
3063 if (fcb == (-1)) goto premature_exit;
3064 /*
3065 * get current offset
3066 */
3067 offset = get_offset(fcb, func);
3068 if (offset == (-1)) goto premature_exit;
3069 /*
3070 * set random record number
3071 */
3072 memory[fcb + 33] = (offset & 0xff);
3073 memory[fcb + 34] = ((offset >> 8) & 0xff);
3074 memory[fcb + 35] = ((offset >> 16) & 0xff);
3075 /*
3076 * success: always return directory code 0
3077 */
3078 reg_a = 0x00;
3079 premature_exit:
3080 reg_l = reg_a;
3081 reg_h = reg_b = 0;
3082 FDOS_EXIT(func, REGS_A);
3083 return;
3084 }
3085
3086
3087 /*
3088 * reset drives in vector in DE
3089 */
3090 static void
bdos_reset_drive(void)3091 bdos_reset_drive(void) {
3092 static const char func[] = "reset drive";
3093 FDOS_ENTRY(func, REGS_DE);
3094 int vector = get_de(), i;
3095 for (vector = get_de(), i = 0; i < 16; vector >>= 1, i++) {
3096 if (! (vector & 1)) continue;
3097 if (! conf_drives[i]) {
3098 plog("reset drive: illegal disk %d", i);
3099 terminate = 1;
3100 term_reason = ERR_SELECT;
3101 continue;
3102 }
3103 read_only[i] = conf_readonly[i];
3104 }
3105 reg_l = reg_a = 0;
3106 reg_h = reg_b = 0;
3107 FDOS_EXIT(func, 0);
3108 }
3109
3110
3111 /*
3112 * unsupported BDOS call; these simply return zero
3113 */
3114 static void
bdos_unsupported(void)3115 bdos_unsupported(void) {
3116 char func[64];
3117 sprintf(func, "unsupported BDOS function %d", (int) reg_c);
3118 SYS_ENTRY(func, REGS_DE);
3119 reg_l = reg_a = 0;
3120 reg_h = reg_b = 0;
3121 SYS_EXIT(func, REGS_HL);
3122 }
3123
3124
3125 /*
3126 * write random record from the FCB pointed to by DE, zeroing allocated but
3127 * unwritten records
3128 */
3129 static void
bdos_write_random_with_zero_fill(void)3130 bdos_write_random_with_zero_fill(void) {
3131 static const char func[] = "write random with zero fill";
3132 FDOS_ENTRY(func, REGS_DE);
3133 write_random(func);
3134 reg_l = reg_a;
3135 reg_h = reg_b = 0;
3136 FDOS_EXIT(func, REGS_A);
3137 }
3138
3139
3140 /*
3141 * Extended BDOS functions: these are not present in CP/M 2.2, but are
3142 * supported in other versions of CP/M or MP/M; currently, there is no
3143 * intention to implement full support for any interface apart from the
3144 * CP/M 2.2 API, and even those extended functions which are implemented
3145 * may not support all possible options of their originals.
3146 *
3147 * Extended BDOS functions are meant to support things like getting the
3148 * current time from the host operating system or passing an exit
3149 * status to the environment without inventing new, totally incompatible
3150 * interfaces resp. to support existing application programs which make
3151 * moderate use of some CP/M 3 features.
3152 *
3153 * The addition of extended BDOS functions was inspired by Joerg Pleumann,
3154 * who also provided me with a prototype of the first implemented extended
3155 * BDOS function, #105 "Get Date and Time".
3156 */
3157
3158
3159 /*
3160 * get a byte from the simulated SCB
3161 */
3162 static unsigned char
read_scb(int offset)3163 read_scb(int offset) {
3164 switch (offset) {
3165 case 0x05: /* BDOS Version number */
3166 return 0x22;
3167 case 0x10: /* program return code, low byte */
3168 return (program_return_code & 0xff);
3169 case 0x11: /* program return code, high byte */
3170 return ((program_return_code >> 8) & 0xff);
3171 case 0x1a: /* console columns - 1 */
3172 return cols - 1;
3173 case 0x1c: /* console lines */
3174 return lines;
3175 case 0x37: /* output delimiter */
3176 return 0x24 /* $ */;
3177 case 0x3c: /* current DMA address, low byte */
3178 return (current_dma & 0xff);
3179 case 0x3d: /* current DMA address, high byte */
3180 return ((current_dma >> 8) & 0xff);
3181 case 0x3e: /* current disk, 0..15 */
3182 return current_drive;
3183 case 0x44: /* current user number, 0..15 */
3184 return current_user;
3185 case 0x4A: /* current multi sector count */
3186 return 1;
3187 default:
3188 return 0x00;
3189 }
3190 }
3191
3192
3193 /*
3194 * get a word from the simulated SCB; tnylpo's simulated SCB is
3195 * readonly, any attempts to write data to the SCB are silently ignored.
3196 */
3197 static void
bdosx_get_set_scb(void)3198 bdosx_get_set_scb(void) {
3199 static const char func[] = "get/set scb";
3200 int addr, offset, action;
3201 SYS_ENTRY(func, REGS_DE);
3202 reg_l = reg_h = 0;
3203 /*
3204 * get and check buffer address
3205 */
3206 addr = get_de();
3207 if (MEMORY_SIZE - addr < 2) {
3208 plog("%s: invalid buffer 0x%04x", func, addr);
3209 terminate = 1;
3210 term_reason = ERR_BDOSARG;
3211 goto premature_exit;
3212 }
3213 /*
3214 * get offset and action code
3215 */
3216 offset = memory[addr];
3217 action = memory[addr + 1];
3218 /*
3219 * check function code
3220 */
3221 switch (action) {
3222 case 0x00: /* read word */
3223 reg_l = read_scb(offset);
3224 reg_h = read_scb(offset + 1);
3225 break;
3226 case 0xfe: /* set word */
3227 case 0xff: /* set byte */
3228 break;
3229 default:
3230 plog("%s: invalid action code 0x%02x", func, action);
3231 terminate = 1;
3232 term_reason = ERR_BDOSARG;
3233 goto premature_exit;
3234 }
3235 premature_exit:
3236 reg_a = reg_l;
3237 reg_b = reg_h;
3238 SYS_EXIT(func, REGS_HL);
3239 }
3240
3241
3242 /*
3243 * date components in CP/M 3 (always local time)
3244 */
3245 struct cpm_time {
3246 int day; /* 1..65535, day 1 is 1978-01-01 */
3247 int hour;
3248 int minute;
3249 int second;
3250 };
3251
3252
3253 /*
3254 * convert a Unix date (seconds since 1970-01-01 UTC) to
3255 * a CP/M date (day number 1 = 1978-01-01, hours, minutes, and seconds,
3256 * all in local time); an out-of-range date will be signalled by day number 0
3257 */
3258 static void
unix_to_cpm_time(const time_t * tp,struct cpm_time * ct_p)3259 unix_to_cpm_time(const time_t *tp, struct cpm_time *ct_p) {
3260 struct tm *tm_p, tm;
3261 time_t t_first, t_this;
3262 /*
3263 * get corresponding local time stamp
3264 */
3265 tm_p = localtime(tp);
3266 /*
3267 * save hour, minute, and second fields
3268 */
3269 ct_p->hour = tm_p->tm_hour;
3270 ct_p->minute = tm_p->tm_min;
3271 ct_p->second = tm_p->tm_sec;
3272 /*
3273 * check for date below the CP/M range: CP/M day 1 is 1978-01-01
3274 */
3275 if (tm_p->tm_year < 78) {
3276 ct_p->day = 0;
3277 } else {
3278 /*
3279 * the calculation of the day number is quite convoluted;
3280 * it is meant to take case of DST and leap seconds...
3281 */
3282 /*
3283 * calculate seconds of first day of current year, 00:00:00
3284 * local time
3285 */
3286 memset(&tm, 0, sizeof tm);
3287 tm.tm_year = tm_p->tm_year;
3288 tm.tm_mon = 0;
3289 tm.tm_mday = 1;
3290 tm.tm_hour = 0;
3291 tm.tm_min = 0;
3292 tm.tm_sec = 0;
3293 t_this = mktime(&tm);
3294 /*
3295 * calculate seconds of 1978-01-01, 00:00:00 local time
3296 */
3297 memset(&tm, 0, sizeof tm);
3298 tm.tm_year = 78;
3299 tm.tm_mon = 0;
3300 tm.tm_mday = 1;
3301 tm.tm_hour = 0;
3302 tm.tm_min = 0;
3303 tm.tm_sec = 0;
3304 t_first = mktime(&tm);
3305 /*
3306 * calculate day number
3307 */
3308 ct_p->day = (int) ((t_this - t_first + (12 * 60 * 60)) /
3309 (24 * 60 * 60) + tm_p->tm_yday + 1);
3310 /*
3311 * check for date above the CP/M rage: day 65535 is 2157-07-05
3312 */
3313 if (ct_p->day > 65535) ct_p->day = 0;
3314 }
3315 }
3316
3317
3318 /*
3319 * convert a number in the range 0..99 to a BCD encoded byte
3320 */
3321 static unsigned char
bcd_byte(int b)3322 bcd_byte(int b) {
3323 return (((b % 100) / 10) << 4) | (b % 10);
3324 }
3325
3326
3327 /*
3328 * copies a CP/M timestamp into CP/M memory at the given address
3329 */
3330 static void
store_cpm_time(const struct cpm_time * ct_p,int addr)3331 store_cpm_time(const struct cpm_time *ct_p, int addr) {
3332 memory[addr] = (ct_p->day & 0xff);
3333 memory[addr + 1] = ((ct_p->day >> 8) & 0xff);
3334 memory[addr + 2] = bcd_byte(ct_p->hour);
3335 memory[addr + 3] = bcd_byte(ct_p->minute);
3336 }
3337
3338
3339 /*
3340 * return the directory label byte for a drive, which always indicates
3341 * that file access and file update time stamps are enabled and
3342 * passwords are disabled.
3343 */
3344 static void
bdosx_return_directory_label_data(void)3345 bdosx_return_directory_label_data(void) {
3346 static const char func[] = "return directory label data";
3347 FDOS_ENTRY(func, REGS_E);
3348 /*
3349 * drive number must be valid
3350 */
3351 check_drive(reg_e, func);
3352 reg_l = reg_a = 0x61; /* label present, access and update stamps */
3353 reg_h = reg_b = 0;
3354 FDOS_EXIT(func, REGS_A);
3355 }
3356
3357
3358 /*
3359 * get file time stamps for the file described by the FCB pointed to by
3360 * register DE
3361 */
3362 static void
bdosx_read_file_date_stamps_and_password_mode(void)3363 bdosx_read_file_date_stamps_and_password_mode(void) {
3364 int fcb, drive;
3365 unsigned char temp_fcb[12];
3366 char unix_name[L_UNIX_NAME];
3367 struct file_list *flp = NULL;
3368 struct cpm_time ct;
3369 static const char func[] = "read file date stamps and password mode";
3370 FDOS_ENTRY(func, REGS_DE);
3371 /*
3372 * assume the operation fails
3373 */
3374 reg_a = 0xff;
3375 /*
3376 * get and check FCB address
3377 */
3378 fcb = get_fcb(32, func);
3379 if (fcb == (-1)) goto premature_exit;
3380 /*
3381 * get and check drive
3382 */
3383 drive = get_drive(fcb, func);
3384 if (drive == (-1)) goto premature_exit;
3385 /*
3386 * extract name from FCB and check it
3387 */
3388 if (get_unix_name(fcb, unix_name, func) == (-1)) goto premature_exit;
3389 /*
3390 * get a list of regular files matching the name in the FCB
3391 * (usually, this will be a one-element list)
3392 */
3393 flp = get_filelist(conf_drives[drive], unix_name, func);
3394 if (! flp) goto premature_exit;
3395 /*
3396 * if the file name in the FCB was ambigous, update it
3397 */
3398 if (is_ambigous(unix_name)) {
3399 setup_fcb(flp->name, temp_fcb);
3400 memcpy(memory + fcb + 1, temp_fcb + 1, 11);
3401 }
3402 /*
3403 * clear FCB byte 12 to indicate that the file has no password
3404 */
3405 memory[fcb + 12] = 0;
3406 /*
3407 * copy access and modify time stamps to the FCB
3408 */
3409 unix_to_cpm_time(&flp->access, &ct);
3410 store_cpm_time(&ct, fcb + 24);
3411 unix_to_cpm_time(&flp->modify, &ct);
3412 store_cpm_time(&ct, fcb + 28);
3413 /*
3414 * success: always return directory code 0
3415 */
3416 reg_a = 0x00;
3417 premature_exit:
3418 /*
3419 * clean up
3420 */
3421 free_filelist(flp);
3422 reg_l = reg_a;
3423 reg_h = reg_b = 0;
3424 FDOS_EXIT(func, REGS_A);
3425 }
3426
3427
3428 /*
3429 * return the current date and time
3430 */
3431 static void
bdosx_get_date_and_time(void)3432 bdosx_get_date_and_time(void) {
3433 static const char func[] = "get date and time";
3434 time_t t;
3435 struct cpm_time ct;
3436 int addr;
3437 reg_a = 0;
3438 SYS_ENTRY(func, REGS_DE);
3439 /*
3440 * get and check buffer address
3441 */
3442 addr = get_de();
3443 if (MEMORY_SIZE - addr < 4) {
3444 plog("get date and time: invalid buffer 0x%04x", addr);
3445 terminate = 1;
3446 term_reason = ERR_BDOSARG;
3447 goto premature_exit;
3448 }
3449 /*
3450 * get current Unix time
3451 */
3452 time(&t);
3453 /*
3454 * convert it to the CP/M format
3455 */
3456 unix_to_cpm_time(&t, &ct);
3457 /*
3458 * copy time to user buffer; set A to the second value
3459 */
3460 store_cpm_time(&ct, addr);
3461 reg_a = bcd_byte(ct.second);
3462 premature_exit:
3463 reg_l = reg_a;
3464 reg_h = reg_b = 0;
3465 SYS_EXIT(func, REGS_A);
3466 }
3467
3468
3469 /*
3470 * query or set the program return code; since program chaining is not
3471 * supported by tnylpo, the initial program return code value is always 0.
3472 * If the program return code is in the range above 0xff00 on program exit,
3473 * an unsuccessful program run will be signalled to the Unix environment.
3474 */
3475 static void
bdosx_get_set_program_return_code(void)3476 bdosx_get_set_program_return_code(void) {
3477 static const char func[] = "get/set program return code";
3478 int code;
3479 SYS_ENTRY(func, REGS_DE);
3480 code = get_de();
3481 if (code == 0xffff) {
3482 reg_l = (program_return_code & 0xff);
3483 reg_h = ((program_return_code >> 8) & 0xff);
3484 } else {
3485 program_return_code = code;
3486 reg_h = reg_l = 0;
3487 }
3488 reg_a = reg_l;
3489 reg_b = reg_h;
3490 SYS_EXIT(func, REGS_HL);
3491 }
3492
3493
3494 /*
3495 * pause program execution for a number of milliseconds; calls
3496 * console_poll() at least four times a second to keep the
3497 * full screen console emulation happy.
3498 */
3499 static void
pause_execution(int delay)3500 pause_execution(int delay) {
3501 struct timeval end, t;
3502 /*
3503 * calculate absolute end time
3504 */
3505 gettimeofday(&end, NULL);
3506 end.tv_sec += delay / 1000;
3507 end.tv_usec += (delay % 1000) * 1000;
3508 if (end.tv_usec >= 1000000L) {
3509 end.tv_sec++;
3510 end.tv_usec -= 1000000L;
3511 }
3512 for (;;) {
3513 /*
3514 * calculate the difference between the current time
3515 * and the end time
3516 */
3517 gettimeofday(&t, NULL);
3518 t.tv_usec = end.tv_usec - t.tv_usec;
3519 t.tv_sec = end.tv_sec - t.tv_sec;
3520 if (t.tv_usec < 0) {
3521 t.tv_usec += 1000000L;
3522 t.tv_sec--;
3523 }
3524 /*
3525 * if the difference is not positive, stop
3526 */
3527 if (t.tv_sec < 0 || (t.tv_sec == 0 && t.tv_usec == 0)) break;
3528 /*
3529 * calculate the minimum between the remaining time and
3530 * the quarter of a second
3531 */
3532 if (t.tv_sec > 0 || t.tv_usec > 250000L) {
3533 t.tv_sec = 0;
3534 t.tv_usec = 250000L;
3535 }
3536 /*
3537 * wait this long
3538 */
3539 select(0, NULL, NULL, NULL, &t);
3540 /*
3541 * poll the console to keep window resizing etc. happy
3542 */
3543 console_poll();
3544 }
3545 }
3546
3547
3548 /*
3549 * delay program execution for the number of "ticks" passed in register DE;
3550 * unfortunately, the duration of a "tick" is system dependent, but the
3551 * system clock usually ticks at 50 or 60 Hz.
3552 *
3553 * Well, I'm from Europe, and 50 Hz therefore is what I'm used to, so let's
3554 * assume a tick duration of 20 ms for tnylpo...
3555 */
3556 static void
bdosx_delay(void)3557 bdosx_delay(void) {
3558 static const char func[] = "delay";
3559 int delay;
3560 SYS_ENTRY(func, REGS_DE);
3561 delay = get_de();
3562 pause_execution(delay * 20);
3563 reg_a = reg_l = 0;
3564 reg_b = reg_h = 0;
3565 SYS_EXIT(func, REGS_A);
3566 }
3567
3568
3569 /*
3570 * function dispatcher table for the BDOS functions
3571 */
3572 static void (*bdos_functions_p[])(void) = {
3573 /*0*/ bdos_system_reset,
3574 /*1*/ bdos_console_input,
3575 /*2*/ bdos_console_output,
3576 /*3*/ bdos_reader_input,
3577 /*4*/ bdos_punch_output,
3578 /*5*/ bdos_list_output,
3579 /*6*/ bdos_direct_console_io,
3580 /*7*/ bdos_get_io_byte,
3581 /*8*/ bdos_set_io_byte,
3582 /*9*/ bdos_print_string,
3583 /*10*/ bdos_read_console_buffer,
3584 /*11*/ bdos_get_console_status,
3585 /*12*/ bdos_return_version_number,
3586 /*13*/ bdos_reset_disk_system,
3587 /*14*/ bdos_select_disk,
3588 /*15*/ bdos_open_file,
3589 /*16*/ bdos_close_file,
3590 /*17*/ bdos_search_for_first,
3591 /*18*/ bdos_search_for_next,
3592 /*19*/ bdos_delete_file,
3593 /*20*/ bdos_read_sequential,
3594 /*21*/ bdos_write_sequential,
3595 /*22*/ bdos_make_file,
3596 /*23*/ bdos_rename_file,
3597 /*24*/ bdos_return_log_in_vector,
3598 /*25*/ bdos_return_current_disk,
3599 /*26*/ bdos_set_dma_address,
3600 /*27*/ bdos_get_addr_alloc,
3601 /*28*/ bdos_write_protect_disk,
3602 /*29*/ bdos_get_read_only_vector,
3603 /*30*/ bdos_set_file_attributes,
3604 /*31*/ bdos_get_addr_diskparams,
3605 /*32*/ bdos_set_get_user_code,
3606 /*33*/ bdos_read_random,
3607 /*34*/ bdos_write_random,
3608 /*35*/ bdos_compute_file_size,
3609 /*36*/ bdos_set_random_record,
3610 /*37*/ bdos_reset_drive,
3611 /*38*/ bdos_unsupported,
3612 /*39*/ bdos_unsupported,
3613 /*40*/ bdos_write_random_with_zero_fill,
3614 /*41*/ bdos_unsupported,
3615 /*42*/ bdos_unsupported,
3616 /*43*/ bdos_unsupported,
3617 /*44*/ bdos_unsupported,
3618 /*45*/ bdos_unsupported,
3619 /*46*/ bdos_unsupported,
3620 /*47*/ bdos_unsupported,
3621 /*48*/ bdos_unsupported,
3622 /*49*/ bdosx_get_set_scb,
3623 /*50*/ bdos_unsupported,
3624 /*51*/ bdos_unsupported,
3625 /*52*/ bdos_unsupported,
3626 /*53*/ bdos_unsupported,
3627 /*54*/ bdos_unsupported,
3628 /*55*/ bdos_unsupported,
3629 /*56*/ bdos_unsupported,
3630 /*57*/ bdos_unsupported,
3631 /*58*/ bdos_unsupported,
3632 /*59*/ bdos_unsupported,
3633 /*60*/ bdos_unsupported,
3634 /*61*/ bdos_unsupported,
3635 /*62*/ bdos_unsupported,
3636 /*63*/ bdos_unsupported,
3637 /*64*/ bdos_unsupported,
3638 /*65*/ bdos_unsupported,
3639 /*66*/ bdos_unsupported,
3640 /*67*/ bdos_unsupported,
3641 /*68*/ bdos_unsupported,
3642 /*69*/ bdos_unsupported,
3643 /*70*/ bdos_unsupported,
3644 /*71*/ bdos_unsupported,
3645 /*72*/ bdos_unsupported,
3646 /*73*/ bdos_unsupported,
3647 /*74*/ bdos_unsupported,
3648 /*75*/ bdos_unsupported,
3649 /*76*/ bdos_unsupported,
3650 /*77*/ bdos_unsupported,
3651 /*78*/ bdos_unsupported,
3652 /*79*/ bdos_unsupported,
3653 /*80*/ bdos_unsupported,
3654 /*81*/ bdos_unsupported,
3655 /*82*/ bdos_unsupported,
3656 /*83*/ bdos_unsupported,
3657 /*84*/ bdos_unsupported,
3658 /*85*/ bdos_unsupported,
3659 /*86*/ bdos_unsupported,
3660 /*87*/ bdos_unsupported,
3661 /*88*/ bdos_unsupported,
3662 /*89*/ bdos_unsupported,
3663 /*90*/ bdos_unsupported,
3664 /*91*/ bdos_unsupported,
3665 /*92*/ bdos_unsupported,
3666 /*93*/ bdos_unsupported,
3667 /*94*/ bdos_unsupported,
3668 /*95*/ bdos_unsupported,
3669 /*96*/ bdos_unsupported,
3670 /*97*/ bdos_unsupported,
3671 /*98*/ bdos_unsupported,
3672 /*99*/ bdos_unsupported,
3673 /*100*/ bdos_unsupported,
3674 /*101*/ bdosx_return_directory_label_data,
3675 /*102*/ bdosx_read_file_date_stamps_and_password_mode,
3676 /*103*/ bdos_unsupported,
3677 /*104*/ bdos_unsupported,
3678 /*105*/ bdosx_get_date_and_time,
3679 /*106*/ bdos_unsupported,
3680 /*107*/ bdos_unsupported,
3681 /*108*/ bdosx_get_set_program_return_code,
3682 /*109*/ bdos_unsupported,
3683 /*110*/ bdos_unsupported,
3684 /*111*/ bdos_unsupported,
3685 /*112*/ bdos_unsupported,
3686 /*113*/ bdos_unsupported,
3687 /*114*/ bdos_unsupported,
3688 /*115*/ bdos_unsupported,
3689 /*116*/ bdos_unsupported,
3690 /*117*/ bdos_unsupported,
3691 /*118*/ bdos_unsupported,
3692 /*119*/ bdos_unsupported,
3693 /*120*/ bdos_unsupported,
3694 /*121*/ bdos_unsupported,
3695 /*122*/ bdos_unsupported,
3696 /*123*/ bdos_unsupported,
3697 /*124*/ bdos_unsupported,
3698 /*125*/ bdos_unsupported,
3699 /*126*/ bdos_unsupported,
3700 /*127*/ bdos_unsupported,
3701 /*128*/ bdos_unsupported,
3702 /*129*/ bdos_unsupported,
3703 /*130*/ bdos_unsupported,
3704 /*131*/ bdos_unsupported,
3705 /*132*/ bdos_unsupported,
3706 /*133*/ bdos_unsupported,
3707 /*134*/ bdos_unsupported,
3708 /*135*/ bdos_unsupported,
3709 /*136*/ bdos_unsupported,
3710 /*137*/ bdos_unsupported,
3711 /*138*/ bdos_unsupported,
3712 /*139*/ bdos_unsupported,
3713 /*140*/ bdos_unsupported,
3714 /*141*/ bdosx_delay
3715 };
3716
3717 #define BDOS_COUNT (sizeof bdos_functions_p / sizeof bdos_functions_p[0])
3718
3719
3720 /*
3721 * BDOS call
3722 */
3723 static void
magic_bdos(void)3724 magic_bdos(void) {
3725 if (reg_c < BDOS_COUNT) {
3726 (*bdos_functions_p[reg_c])();
3727 } else {
3728 bdos_unsupported();
3729 }
3730 }
3731
3732
3733 /*
3734 * BIOS BOOT
3735 */
3736 static void
magic_boot(void)3737 magic_boot(void) {
3738 static const char func[] = "bios boot";
3739 SYS_ENTRY(func, 0);
3740 /*
3741 * no program should ever call BOOT
3742 */
3743 perr("%s called by program", func);
3744 term_reason = ERR_BOOT;
3745 terminate = 1;
3746 }
3747
3748
3749 /*
3750 * BIOS WBOOT
3751 */
3752 static void
magic_wboot(void)3753 magic_wboot(void) {
3754 static const char func[] = "bios wboot";
3755 SYS_ENTRY(func, 0);
3756 term_reason = OK_TERM;
3757 terminate = 1;
3758 }
3759
3760
3761 /*
3762 * BIOS CONST
3763 */
3764 static void
magic_const(void)3765 magic_const(void) {
3766 static const char func[] = "bios const";
3767 SYS_ENTRY(func, 0);
3768 reg_a = console_status() ? 0xff : 0x00;
3769 SYS_EXIT(func, REGS_A);
3770 }
3771
3772
3773 /*
3774 * BIOS CONIN (high bit is not stripped!)
3775 */
3776 static void
magic_conin(void)3777 magic_conin(void) {
3778 static const char func[] = "bios conin";
3779 SYS_ENTRY(func, 0);
3780 reg_a = console_in();
3781 SYS_EXIT(func, REGS_A);
3782 }
3783
3784
3785 /*
3786 * BIOS CONOUT
3787 */
3788 static void
magic_conout(void)3789 magic_conout(void) {
3790 static const char func[] = "bios conout";
3791 SYS_ENTRY(func, REGS_C);
3792 console_out(reg_c);
3793 SYS_EXIT(func, 0);
3794 }
3795
3796
3797 /*
3798 * BIOS LIST
3799 */
3800 static void
magic_list(void)3801 magic_list(void) {
3802 static const char func[] = "bios list";
3803 SYS_ENTRY(func, REGS_C);
3804 printer_out(reg_c);
3805 SYS_EXIT(func, 0);
3806 }
3807
3808
3809 /*
3810 * BIOS PUNCH
3811 */
3812 static void
magic_punch(void)3813 magic_punch(void) {
3814 static const char func[] = "bios punch";
3815 SYS_ENTRY(func, REGS_C);
3816 punch_out(reg_c);
3817 SYS_EXIT(func, 0);
3818 }
3819
3820
3821 /*
3822 * BIOS READER
3823 */
3824 static void
magic_reader(void)3825 magic_reader(void) {
3826 static const char func[] = "bios reader";
3827 SYS_ENTRY(func, 0);
3828 reg_a = reader_in();
3829 SYS_EXIT(func, REGS_A);
3830 }
3831
3832
3833 /*
3834 * BIOS HOME
3835 */
3836 static void
magic_home(void)3837 magic_home(void) {
3838 static const char func[] = "bios home";
3839 SYS_ENTRY(func, 0);
3840 SYS_EXIT(func, 0);
3841 }
3842
3843
3844 /*
3845 * BIOS SELDSK
3846 */
3847 static void
magic_seldsk(void)3848 magic_seldsk(void) {
3849 static const char func[] = "bios seldsk";
3850 SYS_ENTRY(func, REGS_C|REGS_E);
3851 /*
3852 * report nonexisting drive
3853 */
3854 reg_h = reg_l = 0x00;
3855 SYS_EXIT(func, REGS_HL);
3856 }
3857
3858
3859 /*
3860 * BIOS SETTRK
3861 */
3862 static void
magic_settrk(void)3863 magic_settrk(void) {
3864 static const char func[] = "bios settrk";
3865 SYS_ENTRY(func, REGS_BC);
3866 SYS_EXIT(func, 0);
3867 }
3868
3869
3870 /*
3871 * BIOS SETSEC
3872 */
3873 static void
magic_setsec(void)3874 magic_setsec(void) {
3875 static const char func[] = "bios setsec";
3876 SYS_ENTRY(func, REGS_BC);
3877 SYS_EXIT(func, 0);
3878 }
3879
3880
3881 /*
3882 * BIOS SETDMA
3883 */
3884 static void
magic_setdma(void)3885 magic_setdma(void) {
3886 static const char func[] = "bios setdma";
3887 SYS_ENTRY(func, REGS_BC);
3888 SYS_EXIT(func, 0);
3889 }
3890
3891
3892 /*
3893 * BIOS READ
3894 */
3895 static void
magic_read(void)3896 magic_read(void) {
3897 static const char func[] = "bios read";
3898 SYS_ENTRY(func, 0);
3899 /*
3900 * report an error
3901 */
3902 reg_a = 1;
3903 SYS_EXIT(func, REGS_A);
3904 }
3905
3906
3907 /*
3908 * BIOS WRITE
3909 */
3910 static void
magic_write(void)3911 magic_write(void) {
3912 static const char func[] = "bios write";
3913 SYS_ENTRY(func, REGS_C);
3914 /*
3915 * report an error
3916 */
3917 reg_a = 1;
3918 SYS_EXIT(func, REGS_A);
3919 }
3920
3921
3922 /*
3923 * BIOS LISTST
3924 */
3925 static void
magic_listst(void)3926 magic_listst(void) {
3927 static const char func[] = "bios listst";
3928 SYS_ENTRY(func, 0);
3929 reg_a = printer_status() ? 0xff : 0x00;
3930 SYS_EXIT(func, REGS_A);
3931 }
3932
3933
3934 /*
3935 * BIOS SECTRAN
3936 */
3937 static void
magic_sectran(void)3938 magic_sectran(void) {
3939 static const char func[] = "bios sectran";
3940 SYS_ENTRY(func, REGS_BC|REGS_DE);
3941 reg_l = reg_c;
3942 reg_h = reg_b;
3943 SYS_EXIT(func, REGS_HL);
3944 }
3945
3946
3947 /*
3948 * this routine is a tnylpo specific extension hiding as 18th BIOS routine:
3949 * it takes the value in register BC and delays program execution for this
3950 * many wall clock milliseconds.
3951 */
3952 static void
magic_delay(void)3953 magic_delay(void) {
3954 static const char func[] = "tnylpo delay";
3955 int delay;
3956 SYS_ENTRY(func, REGS_BC);
3957 delay = get_bc();
3958 pause_execution(delay);
3959 SYS_EXIT(func, 0);
3960 }
3961
3962
3963 /*
3964 * dispatcher table for the magic addresses
3965 */
3966 static void (*magic_functions_p[1 + BIOS_VECTOR_COUNT])(void) = {
3967 /* BDOS */ magic_bdos,
3968 /* BOOT */ magic_boot,
3969 /* WBOOT */ magic_wboot,
3970 /* CONST */ magic_const,
3971 /* CONIN */ magic_conin,
3972 /* CONOUT */ magic_conout,
3973 /* LIST */ magic_list,
3974 /* PUNCH */ magic_punch,
3975 /* READER */ magic_reader,
3976 /* HOME */ magic_home,
3977 /* SELDSK */ magic_seldsk,
3978 /* SETTRK */ magic_settrk,
3979 /* SETSEC */ magic_setsec,
3980 /* SETDMA */ magic_setdma,
3981 /* READ */ magic_read,
3982 /* WRITE */ magic_write,
3983 /* LISTST */ magic_listst,
3984 /* SECTRAN */ magic_sectran,
3985 /* delay */ magic_delay
3986 };
3987
3988
3989 /*
3990 * handle call to the OS: magic determines the call:
3991 * magic == 0: BDOS call, reg_c contains function number
3992 * magic == 1 ... 17: call to one of the 17 CP/M 2.2 BIOS entries
3993 * magic == 18: call to the emulator delay routine
3994 */
3995 void
os_call(int magic)3996 os_call(int magic) { (*magic_functions_p[magic])(); }
3997
3998
3999 /*
4000 * finalize OS emulation
4001 */
4002 int
os_exit(void)4003 os_exit(void) {
4004 int rc = 0;
4005 struct file_data *fdp;
4006 /*
4007 * return error if the application program set a program return
4008 * code in above 0xff00. The more detailed return code of CP/M
4009 * (0x0000..0xfeff: shades of success; 0xff00..0xfffe: shades
4010 * of failure) is simplified to just success or failure.
4011 */
4012 if (program_return_code) {
4013 plog("CP/M program return code is 0x%04x",
4014 (unsigned) program_return_code);
4015 }
4016 if (program_return_code >= 0xff00) rc = (-1);
4017 /*
4018 * reset disk subsystem
4019 */
4020 disk_reset();
4021 /*
4022 * tear down file list
4023 */
4024 while (first_file_p) {
4025 fdp = first_file_p;
4026 first_file_p = fdp->next_p;
4027 free_filedata(fdp);
4028 }
4029 return rc;
4030 }
4031