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