1 /*  System monitor.
2     Copyright (C) 1995  Frank D. Cringle.
3     Modifications for CP/M 3.1 Copyright (C) 2000/2004 by Andreas Gerlich (agl)
4 
5     This file is part of yaze-ag - yet another Z80 emulator by ag.
6 
7     Yaze-ag is free software; you can redistribute it and/or modify it under
8     the terms of the GNU General Public License as published by the Free
9     Software Foundation; either version 2 of the License, or (at your
10     option) any later version.
11 
12     This program is distributed in the hope that it will be useful, but
13     WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15     General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License
18     along with this program; if not, write to the Free Software
19     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #ifndef CYGWIN
25 #include <libgen.h>
26 #endif
27 #include <strings.h>
28 #include <time.h>
29 #include <limits.h>
30 #include <fcntl.h>
31 #include <termios.h>
32 #include <ctype.h>
33 #include <signal.h>
34 #include <time.h>
35 #include <sys/times.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <dirent.h>
39 #include <sys/mman.h>
40 
41 #include "mem_mmu.h"
42 #include "simz80.h"
43 #include "yaze.h"
44 #include "ybios.h"
45 
46 /* TTY management */
47 
48 struct termios cookedtio, rawtio;
49 int ttyflags;
50 int interrupt;
51 
52 void
ttyraw(void)53 ttyraw(void)
54 {
55     if ((ttyflags & (ISATTY | ISRAW)) == ISATTY)
56     {
57         tcsetattr(fileno(stdin), TCSAFLUSH, &rawtio);
58         ttyflags |= ISRAW;
59     }
60 }
61 
62 void
ttycook(void)63 ttycook(void)
64 {
65     if (ttyflags & ISRAW)
66     {
67         tcsetattr(fileno(stdin), TCSAFLUSH, &cookedtio);
68         putc('\n', stdout);
69         ttyflags &= ~ISRAW;
70     }
71 }
72 
73 
74 /*  memory management routines for disk descriptors
75     (we need to allocate chunks of cp/m ram for these) */
76 
77 /* inefficient but robust bit-wise algorithm (we're not doing this all day) */
78 #define bytefree(x)	(!(global_alv[(x-bios_top) >> 3] &		\
79 			   (0x80 >> ((x-bios_top) & 7))))
80 
81 static WORD
cpmalloc(WORD len)82 cpmalloc(WORD len)
83 {
84     WORD p = bios_top;
85 
86     while (p < dptable - len)
87         if (!bytefree(p))
88             p++;
89         else
90         {
91             int i;
92             for (i = 1; i < len; i++)
93                 if (!bytefree(p + i))
94                     break;
95             if (i == len)
96             {
97                 WORD p1 = p - bios_top;
98                 while (len--)
99                 {
100                     global_alv[p1 >> 3] |= (0x80 >> (p1 & 7));
101                     p1++;
102                 }
103                 return p;
104             }
105             p += i;
106         }
107     return 0;
108 }
109 
110 
111 static void
cpmfree(WORD adr,WORD len)112 cpmfree(WORD adr, WORD len)
113 {
114     WORD p = adr - bios_top;
115 
116     while (len--)
117     {
118         global_alv[p >> 3] &= ~(0x80 >> (p & 7));
119         p++;
120     }
121 }
122 
123 
124 /* Disk management */
125 
126 /*  There are two kinds of simulated disk:
127     a unix file containing an image of a cp/m disk and
128     a unix directory which is made to look like a cp/m disk on the fly */
129 
130 #define MNT_ACTIVE	1
131 #define MNT_RDONLY	2
132 #define MNT_UNIXDIR	4
133 
134 struct mnt mnttab[16], *curdisk;      /* mount table (simulated drives A..P) */
135 
136 /* display a mount table entry */
137 void
showdisk(int disk,int verbose)138 showdisk(int disk, int verbose)
139 {
140     struct mnt *dp = mnttab + disk;
141     BYTE   version = dp->buf[16]; /* get version indentifier */
142 
143     printf("%c: ", disk + 'A');
144     if (!(dp->flags & MNT_ACTIVE))
145     {
146         puts("not mounted\r\n");
147         return;
148     }
149     printf(dp->flags & MNT_UNIXDIR ? "%s %s/\r\n" : "%s %s\r\n",
150            dp->flags & MNT_RDONLY ? "r/o " : "r/w ", dp->filename);
151     if (!verbose)
152         return;
153     if (cpm3)
154     {
155         if (dp->flags & MNT_UNIXDIR)
156             printf("\r\n  Drive connected to a directory!");
157         else
158         {
159             printf("\r\n  Disk file created under ");
160             switch (version)
161             {
162             case 0:
163                 printf("yaze 1.10/1.06 (version 0)");
164                 break;
165             case 1:
166                 printf("yaze-ag 2.xx (version 1)");
167                 break;
168             default:
169                 printf("- not known");
170             }
171         }
172         printf("\r\n\n  CP/M 3.1 DPH (%04X)\r\n", dp->dph);
173         printf("  xlt=%04X, mf=%02X, dpb=%04X,"
174                " csv=%04X, alv=%04X, dirbcb=%04X\r\n",
175                GetWORD(dp->dph), GetBYTE(dp->dph + 11), GetWORD(dp->dph + 12),
176                GetWORD(dp->dph + 14), GetWORD(dp->dph + 16), GetWORD(dp->dph + 18));
177         printf("  dtabcb=%04X, hash=%04X, hbank=%02X\r\n",
178                GetWORD(dp->dph + 20), GetWORD(dp->dph + 22), GetBYTE(dp->dph + 24));
179         printf("\r\n  CP/M 3.1 DPB (%04X)\r\n", dp->dpb);
180         printf("  spt=%04X, bsh=%02X, blm=%02X, exm=%02X, dsm=%04X, drm=%04X,"
181                " al=%02X%02X, cks=%04X,\r\n  off=%04X, psh=%02X,"
182                " phm=%02X ",
183                GetWORD(dp->dpb), GetBYTE(dp->dpb + 2), GetBYTE(dp->dpb + 3),
184                GetBYTE(dp->dpb + 4), GetWORD(dp->dpb + 5), GetWORD(dp->dpb + 7),
185                GetBYTE(dp->dpb + 9), GetBYTE(dp->dpb + 10), GetWORD(dp->dpb + 11),
186                GetWORD(dp->dpb + 13), GetBYTE(dp->dpb + 15), GetBYTE(dp->dpb + 16));
187         printf("(sektor size: %d)\r\n\n", (128 << GetBYTE(dp->dpb + 15)));
188 
189     }
190     else
191     {
192 
193         printf("  dph=%04X, xlt=%04X, dirbuf=%04X, dpb=%04X,"
194                " csv=%04X, alv=%04X, spt=%04X\r\n",
195                dp->dph, GetWORD(dp->dph), GetWORD(dp->dph + 8), GetWORD(dp->dph + 10),
196                GetWORD(dp->dph + 12), GetWORD(dp->dph + 14), GetWORD(dp->dph + 16));
197         printf("  bsh=%02X, blm=%02X, exm=%02X, dsm=%04X, drm=%04X,"
198                " al=%02X%02X, cks=%04X, off=%04X\r\n",
199                GetBYTE(dp->dph + 18), GetBYTE(dp->dph + 19), GetBYTE(dp->dph + 20),
200                GetWORD(dp->dph + 21), GetWORD(dp->dph + 23),
201                GetBYTE(dp->dph + 25), GetBYTE(dp->dph + 26),
202                GetWORD(dp->dph + 27), GetWORD(dp->dph + 29));
203     }
204 }
205 
206 /* unmount a disk */
207 static int
umount(int disk)208 umount(int disk)
209 {
210     struct mnt *dp = mnttab + disk;
211     /* WORD xlt; <- deletet by agl, is in dp */
212 
213     if (!(dp->flags & MNT_ACTIVE))
214         return 0;
215     if (dp->flags & MNT_UNIXDIR)
216     {
217         int i;
218         clearfc(dp);			/* clear the bios's file cache */
219         for (i = 0; i < dp->nfds; i++)
220             free(dp->fds[i].fullname);
221         free(dp->data);
222         free(dp->fds);
223     }
224     else if (munmap(dp->header, dp->isize) == -1 ||
225              close(dp->ifd) == -1)
226         perror(dp->filename);
227     dp->flags = 0;
228     free(dp->filename);
229     /*  modified by agl ...
230         if ((xlt = GetWORD(dp->dph)) != 0)
231     	cpmfree(xlt, GetWORD(dp->dph+16));
232     */
233     /* by agl */
234     if ((dp->xlt = GetWORD(dp->dph)) != 0)
235     {
236         if (!cpm3) cpmfree(dp->xlt, GetWORD(dp->dph + 16));
237         dp->xlt = 0;
238     }
239     if (cpm3)
240         PutWORD((dtbl + (2 * disk)), 0);  /* delete entry in dtbl (CP/M 3.1) */
241     else
242         cpmfree(GetWORD(dp->dph + 14), (GetWORD(dp->dph + 16 + 5) >> 3) + 1);
243     return 0;
244 }
245 
246 /* stash a string away on the heap */
247 char *
newstr(const char * str)248 newstr(const char *str)
249 {
250     char *p = xmalloc(strlen(str) + 1);
251     (void) strcpy(p, str);
252     return p;
253 }
254 
255 /* format of a cp/m 3.1 date stamp entry */
256 struct s_cpmdate
257 {
258     WORD day;	/* count days since 1.1.1978 */
259     BYTE hour;
260     BYTE min;
261     /*    BYTE sec; */
262 };
263 
264 /* converts a time information of unix to a date information under cp/m 3.1 */
265 void
cpmdate(time_t * unixtime,struct s_cpmdate * cpmdat)266 cpmdate(time_t *unixtime, struct s_cpmdate *cpmdat)
267 {
268     struct tm *t;
269 
270     t = localtime(unixtime);
271 
272     cpmdat->day = (WORD)(dayfaktor(t->tm_mday,
273                                    (t->tm_mon + 1),
274                                    (1900 + t->tm_year)
275                                   ) - DayFaktor_CPMSTART);
276 
277     cpmdat->hour = (BYTE)(((t->tm_hour / 10) << 4) | (t->tm_hour % 10));
278     cpmdat->min  = (BYTE)(((t->tm_min / 10) << 4) | (t->tm_min % 10));
279     /*    cpmdat->sec  = (BYTE)( ((t->tm_sec/10)<<4) | (t->tm_sec%10) ); */
280 }
281 
282 /*  Decide if a unix file is eligible to be included as a cp/m file in a
283     simulated disk constructed from a unix directory.  Return the filesize in
284     bytes if so, 0 if not. */
285 static off_t
cpmsize(const char * dir_name,const char * filename,unsigned char * cpmname,char ** unixname,struct s_cpmdate * acc_time,struct s_cpmdate * mod_time)286 cpmsize(const char *dir_name, const char *filename, unsigned char *cpmname,
287         char **unixname, struct s_cpmdate *acc_time, struct s_cpmdate *mod_time)
288 {
289     int i, fd;
290     const char *p = filename;
291     unsigned char *p1 = cpmname;
292     struct stat st;
293     char *path;
294 
295     /* construct cpm filename, rejecting any that dont fit */
296     for (i = 0; i < 8; i++)
297     {
298         if (*p == 0 || *p == '.')
299             break;
300         if (*p <= ' ' || *p >= '{' || strchr("=_:,;<>", *p))
301             return 0;
302         *p1++ = toupper(*p);
303         p++;
304     }
305     for (; i < 8; i++)
306         *p1++ = ' ';
307     if (*p)
308     {
309         if (*p == '.')
310             p++;
311         else
312             return 0;
313     }
314     for (; i < 11; i++)
315     {
316         if (*p == 0)
317             break;
318         if (*p <= ' ' || *p >= '{' || strchr(".=_:,;<>", *p))
319             return 0;
320         *p1++ = toupper(*p);
321         p++;
322     }
323     if (*p)
324         return 0;
325     for (; i < 11; i++)
326         *p1++ = ' ';
327 
328     /* construct unix filename */
329     path = xmalloc(strlen(dir_name) + strlen(filename) + 2);
330     sprintf(path, "%s/%s", dir_name, filename);
331 
332     /* check that file is readable, regular and non-empty */
333     if ((fd = open(path, O_RDONLY)) < 0)
334     {
335         free(path);
336         return 0;
337     }
338     if (fstat(fd, &st) < 0)
339     {
340         close(fd);
341         free(path);
342         return 0;
343     }
344     close(fd);
345 #ifdef __EXTENSIONS__
346     if (((st.st_mode & S_IFMT) != S_IFREG) || st.st_size == 0)
347     {
348 #else
349     if (((st.st_mode & __S_IFMT) != __S_IFREG) || st.st_size == 0)
350     {
351 #endif
352         free(path);
353         return 0;
354     }
355 
356     cpmdate(&st.st_atime, acc_time);   /* convert access time in cp/m format */
357     cpmdate(&st.st_mtime, mod_time);   /* modification time (will be update) */
358     /* added by agl (26.1.2004 */
359     *unixname = path;
360     return st.st_size;
361 }
362 
363 /* mount a unix directory as a simulated cpm disk */
364 /* expanded Jan 2004 by Andreas Gerlich to handle cp/m date stamps entries */
365 /* (It's a terrible hack ;-) */
366 
367 static struct cpmlabel   /* record of a cp/m 3.1 label */
368 {
369     char user;
370     char labelname[8 + 3];
371     char LB;		/* Label byte */
372     char PB;		/* Used to decode label password */
373     char RR1, RR2;	/* reserved */
374     char Password[8];	/* password */
375     /* Label create/access datestamp */
376     BYTE cr_day_low;
377     BYTE cr_day_high;
378     BYTE cr_hour;
379     BYTE cr_min;
380     /* Label update datestamp */
381     BYTE up_day_low;
382     BYTE up_day_high;
383     BYTE up_hour;
384     BYTE up_min;
385 } stdlabel =
386 {
387     0x20,
388     "           ",
389     0x21,		/* LB: update + label exists */
390     0,			/* PB */
391     0, 0,		/* RR */
392     "\x00\x00\x00\x00\x00\x00\x00\x00", /* no password */
393     0x31, 0x25, 0x22, 0x55,			/* 25.1.2004, 22:55 */
394     0x31, 0x25, 0x22, 0x56			/* 25.1.2004, 22:56 */
395 };
396 
397 /* LB - byte, modified by function dosetacc */
398 #define LBL_existsbit	0x01
399 #define LBL_createbit	0x10
400 #define LBL_updatebit	0x20
401 #define LBL_accessbit	0x40
402 unsigned int lbl = 0x21; /* default: exists & update */
403 
404 
405 struct s_datestamps  	/* record of one datestamp in the dates stamps */
406 {
407     /* CP/M create/access time */   /* directory Entry (10 bytes) */
408     BYTE cr_day_low;
409     BYTE cr_day_high;
410     BYTE cr_hour;
411     BYTE cr_min;
412     /* CP/M update time */
413     BYTE up_day_low;
414     BYTE up_day_high;
415     BYTE up_hour;
416     BYTE up_min;
417     BYTE pm;	/* password mode */
418     BYTE null;
419 };
420 
421 
422 static int
423 mountdir(struct mnt *dp, const char *filename, unsigned int labelb)
424 {
425     DIR *dirp;
426     struct dirent *direntp;
427     unsigned char *cpmdir = xmalloc(N_ENTRIES * 32);
428     unsigned long blockno = COVER((N_ENTRIES * 32), BLOCK_SIZE);
429     int direno = 0;
430     int direno2 = 0;
431     int nfiles = 0;
432     int i;
433     WORD alv, w;
434     static unsigned long serialno = 0;
435     unsigned char *cp;	/* pointer to cp/m directory entry   */
436     char *cp2;
437     /* for time */
438     time_t now;
439     struct s_cpmdate cpm_now, acc_time, mod_time;
440     struct cpmlabel *cpmlbl;
441     unsigned char *datestamps;
442     struct s_datestamps filedate;
443 
444     dp->fds = xmalloc(N_ENTRIES * sizeof(struct fdesc));
445     if ((dirp = opendir(filename)) == NULL)
446     {
447         perror(filename);
448         free(cpmdir);
449         free(dp->fds);
450         return 0;
451     }
452     memclr(cpmdir, (N_ENTRIES * 32));
453 
454     /* setup the first 4 directory entries (first 128 byte) */
455     cp = cpmdir;
456     *cp = 0xE5; /* E5H --> erased directory entry */
457     cp += 32;
458     *cp = 0xE5;
459     cp += 32;
460     *cp = 0xE5;
461     cp += 32;
462     *cp = 0x21; /* 21H -> date stamps directory entry */
463     /* datestamps = cpmdir + 3*32 + 1; */
464     datestamps = cp + 1;  /* pointer to the date stamp of the first file */
465 
466     /* Setup label entry */
467     memcpy(cpmdir, &stdlabel, sizeof(stdlabel));
468     cpmlbl = (struct cpmlabel *) cpmdir;
469     /* points to the cp/m label entry (first entry in */
470     /* the cp/m directory */
471     /* copy name of directory into the labelname of the CP/M disk label */
472 #ifndef CYGWIN
473     cp = cpmdir + 1;
474     cp2 = basename((char *) filename);
475     for (i = 0; *cp2 && i < 11 ; i++)
476         *cp++ = *cp2++;
477 #endif
478     /* cpmlbl->LB = labelb;	 \* Label byte */
479     /*  LB wird nicht gesetzt (bleibt auf 0x21). Wenn das
480         accessBit gesetzt ist versucht CP/M beim lesen einer File
481         in die directory mit den access time informationen zu
482         schreiben. Dies verursacht ein Fehler (da R/O):
483         CP/M verschluckt sich dabei in der Weise, dass die
484         betroffenen Dateien nicht
485         mehr sichtbar sind (mit DIR) aber noch z.b. mit type
486         geoeffnet werden koennen.
487     */
488 
489     time(&now);		/* get time/date */
490     cpmdate(&now, &cpm_now);   /* convert into cp/m 3.1 format */
491 
492     /* set create time in label */
493     cpmlbl->cr_day_low  = cpm_now.day & 0x00ff; /* % 256; */
494     cpmlbl->cr_day_high = cpm_now.day >> 8;    /* / 256; */
495     cpmlbl->cr_hour = cpm_now.hour;
496     cpmlbl->cr_min  = cpm_now.min;
497 
498     /* set update time in label */
499     cpmlbl->up_day_low  = cpm_now.day & 0x00ff; /* % 256; */
500     cpmlbl->up_day_high = cpm_now.day >> 8;    /* / 256; */
501     cpmlbl->up_hour = cpm_now.hour;
502     cpmlbl->up_min  = cpm_now.min;
503 
504 
505     /* time stamps into the date stamps directory entry */
506 
507     memclr(&filedate, sizeof(struct s_datestamps));
508 
509     if (labelb & LBL_accessbit)
510     {
511         filedate.cr_day_low  = cpm_now.day & 0x00ff;	/* mod 256 */
512         filedate.cr_day_high = cpm_now.day >> 8;	/* div 256 */
513         filedate.cr_hour = cpm_now.hour;
514         filedate.cr_min  = cpm_now.min;
515     }
516 
517     filedate.up_day_low  = cpm_now.day & 0x00ff;  /* mod 256 */
518     filedate.up_day_high = cpm_now.day >> 8;	  /* div 256 */
519     filedate.up_hour = cpm_now.hour;
520     filedate.up_min  = cpm_now.min;
521 
522     memcpy(datestamps, &filedate, sizeof(struct s_datestamps));
523 
524     direno++;
525 
526     while ((direntp = readdir(dirp)) != NULL)
527     {
528         char *fullname;
529         off_t size;		/* file size in bytes */
530         int ndirents;		/* number of directory entries required for file */
531         int nlogexts;		/* number of full logical extents occupied by */
532         /* file */
533         unsigned long nblocks;	/* number of blocks occupied by file */
534 
535         if ((size = cpmsize(filename, direntp->d_name, cpmdir + 32 * direno + 1,
536                             &fullname, &acc_time, &mod_time)) == 0)
537             continue;
538         for (i = 0; i < direno; i++)
539             if (memcmp(cpmdir + 32 * i + 1, cpmdir + 32 * direno + 1, 11) == 0)
540             {
541                 free(fullname);		/* discard case-collapsed duplicates */
542                 size = 0;		/* added by agl, 20.12.2003 */
543                 break;			/* added by agl */
544                 /* continue;		\* deleted by agl, this does not work */
545             }
546         if (size == 0)			/* added by agl, 20.12.2003 */
547             continue;
548 
549         /* setup date stamp of the directory entry */
550         /* adr of the related datestamp := datestamps + (direno % 4)*10; */
551 
552         memclr(&filedate, sizeof(struct s_datestamps));
553 
554         /* unix access time as create/access time under cp/m */
555         if (labelb & LBL_accessbit)
556         {
557             filedate.cr_day_low  = acc_time.day & 0x0ff; /* mod 256 */
558             filedate.cr_day_high = acc_time.day >> 8;	 /* div 256 */
559             filedate.cr_hour = acc_time.hour;	/* time under cp/m 3.1 */
560             filedate.cr_min  = acc_time.min;
561         }
562 
563         /* unix modification time as update time under cp/m */
564         filedate.up_day_low  = mod_time.day & 0x0ff;	/* mod 256 */
565         filedate.up_day_high = mod_time.day >> 8;	/* div 256 */
566         filedate.up_hour = mod_time.hour;	/* time under cp/m 3.1 */
567         filedate.up_min  = mod_time.min;
568 
569         memcpy(datestamps + (direno % 4) * 10, &filedate,
570                sizeof(struct s_datestamps));
571 
572         ndirents = COVER(size, 8 * BLOCK_SIZE);
573         /* nlogexts = size/(16*1024);  orig changed at 2008-06-15 */
574         nlogexts = (size - 1) / (16 * 1024);
575         nblocks = COVER(size, BLOCK_SIZE);
576 
577         if ((direno + ndirents > (N_ENTRIES - 1)) ||
578                 ((blockno + nblocks) * BLOCK_SIZE > 0xffff * 128))
579         {
580             fprintf(stderr, "not all files in %s will fit on disk\n", filename);
581             free(fullname);
582             break;
583         }
584         dp->fds[nfiles].fullname = fullname;
585         dp->fds[nfiles].firstblock = blockno;
586         dp->fds[nfiles].lastblock = blockno + nblocks - 1;
587         dp->fds[nfiles++].serial = serialno++;
588         direno2 = direno;
589         for (i = 0; i < ndirents; i++)
590         {
591             int ex = nlogexts < 2 * i + 1 ? nlogexts : 2 * i + 1;
592             int j;
593             cp = cpmdir + (direno2) * 32;
594             *cp = 0;	/* user = 0 */
595             if (i)
596                 memcpy(cp + 1, cpmdir + direno * 32 + 1, 11);
597             cp[12] = ex & 0x1f;
598             cp[13] = 0;
599             cp[14] = ex >> 5;
600             /*
601                 cp[15] = nblocks <= 8 ?
602                 COVER((size-16*1024*nlogexts), 128) : 128;
603             */
604             /* added the followng 2 lines 2008-06-02, AG */
605             if ((size % 16384) == 0)
606                 cp[15] = 128;
607             else
608             {
609                 cp[15] = nblocks <= 8 ?
610                          COVER((size - 16 * 1024 * nlogexts), 128) : 128;
611             }
612             cp += 16;
613             for (j = 0; j < 8; j++)
614             {
615                 if (nblocks > 0)
616                 {
617                     *cp++ = blockno;
618                     *cp++ = blockno >> 8;
619                     ++blockno;
620                     --nblocks;
621                 }
622                 else
623                 {
624                     *cp++ = 0;
625                     *cp++ = 0;
626                 }
627             }
628             direno2++;
629             /* handle last dir entry (date stamps entry) */
630             if ((direno2 % 4) == 3)   /* date stamps entry ? */
631             {
632                 cp = cpmdir + (direno2) * 32;
633                 *cp = 0x21;
634                 if (++direno2 < N_ENTRIES)
635                 {
636                     /* setup next sektor */
637                     cp = cpmdir + (direno2) * 32;
638                     *cp = 0xE5;
639                     cp += 32;
640                     *cp = 0xE5;
641                     cp += 32;
642                     *cp = 0xE5;
643                     cp += 32;
644                     *cp = 0x21; /* date stamps */
645                     datestamps = cp + 1;
646                 }
647             }
648         }
649         direno = direno2;
650     }
651     while ((direno % 4) != 0) direno++; /* direno begins at next sector */
652 
653     closedir(dirp);
654     if (nfiles == 0)
655     {
656         fprintf(stderr, "no suitable files in %s\n", filename);
657         free(cpmdir);
658         free(dp->fds);
659         return 0;
660     }
661     dp->nde = direno;
662     while (direno & 3)
663     {
664         memset(cpmdir + direno * 32, 0xe5, 32);
665         ++direno;
666     }
667     dp->nfds = nfiles;
668     /* dp->fds = realloc(dp->fds, nfiles*sizeof(struct fdesc)); \* del by agl */
669     /* dp->data = realloc(cpmdir, direno*32); \* deleted by agl */
670     dp->data = cpmdir;  /* */
671 
672     /* always setup buf[] if setup_cpm3_dph_dpb is running */
673 
674     dp->xlt = 0;		/* No translation table */
675 
676     dp->buf[16] = 1;		/* version indentifier DPB for CP/M 3.1	*/
677 
678     dp->buf[32 + 0] = 0x00;	/* LOW(SPT) 256 SPT	*/
679     dp->buf[32 + 1] = 0x01;	/* HIGH(SPT)		*/
680 
681     dp->buf[32 + 2] = 5;	/* block shift factor   */
682     dp->buf[32 + 3] = 31;	/* block mask           */
683     dp->buf[32 + 4] = 1;	/* extend mask		*/
684 
685     w = blockno > 256 ? blockno - 1 : 256;
686     dp->buf[32 + 5] = (BYTE)(w & 0xff); /* LOW(DSM)	*/
687     dp->buf[32 + 6] = (BYTE)(w >> 8);	 /* HIGH(DSM)	*/
688 
689     w = N_ENTRIES - 1;
690     dp->buf[32 + 7] = (BYTE)(w & 0xff); /* LOW(DRM)	*/
691     dp->buf[32 + 8] = (BYTE)(w >> 8);	 /* HIGH(DRM)	*/
692 
693     dp->buf[32 + 9] = 0xff;	/* AL0			*/
694     dp->buf[32 + 10] = 0xff;	/* AL1			*/
695 
696     dp->buf[32 + 11] = 0;	/* LOW(CKS)		*/
697     dp->buf[32 + 12] = 0;	/* HIGH(CKS)		*/
698 
699     dp->buf[32 + 13] = 0;	/* LOW(OFF)		*/
700     dp->buf[32 + 14] = 0;	/* HIGH(OFF)		*/
701 
702     dp->buf[32 + 15] = 0;	/* PSH			*/
703     dp->buf[32 + 16] = 0;	/* PHM			*/
704 
705     if (!cpm3)
706     {
707         /* set up disk parameter header for CP/M 2.2 */
708         dp->dph = dptable + (16 + 15) * (dp - mnttab);
709         memclr(ram + dp->dph, 16 + 15);
710         PutWORD(dp->dph + 8, dirbuff);	/* pointer to directory buffer */
711         dp->dpb = dp->dph + 16;		/* Pointer to dpb (for yaze-ag) (agl)*/
712         /* PutWORD(dp->dph+10, dp->dph+16);	\* pointer to dpb  */
713         PutWORD(dp->dph + 10, dp->dpb);	/* set pointer to dpb in cp/m 2.2 (a) */
714 
715         /* setting up dpb CP/M 2.2 */
716         PutWORD(dp->dpb + 0,  256);		/* sectors per track */
717         PutBYTE(dp->dpb + 2,  5);		/* block shift factor */
718         PutBYTE(dp->dpb + 3,  31);		/* block mask */
719         PutBYTE(dp->dpb + 4,  1);		/* extent mask */
720         PutWORD(dp->dpb + 5,  blockno > 256 ? blockno - 1 : 256); /* DSM */
721         PutWORD(dp->dpb + 7,  N_ENTRIES - 1);	/* DRM */
722         PutWORD(dp->dpb + 9,  0xffff);	/* AL0,AL1 */
723         /*
724             PutWORD(dp->dph+16, 256);		\* sectors per track *\
725             PutBYTE(dp->dph+18, 5);		\* block shift factor *\
726             PutBYTE(dp->dph+19, 31);		\* block mask *\
727             PutBYTE(dp->dph+20, 1);		\* extent mask *\
728             PutWORD(dp->dph+21, blockno > 256 ? blockno-1 : 256); \* DSM *\
729             PutWORD(dp->dph+23, N_ENTRIES-1);	\* DRM *\
730             PutWORD(dp->dph+25, 0xffff);	\* AL0,AL1 *\
731         */
732 
733         alv = cpmalloc((GetWORD(dp->dph + 16 + 5) >> 3) + 1);
734         if (alv == 0)
735         {
736             fprintf(stderr, "insufficient space to mount %s\n", filename);
737             for (i = 0; i < dp->nfds; i++)
738                 free(dp->fds->fullname);
739             free(dp->data);
740             free(dp->fds);
741             return 0;
742         }
743         PutWORD(dp->dph + 14, alv);	/* pointer to allocation vector  */
744     }
745     dp->filename = newstr(filename);
746     dp->flags = MNT_ACTIVE | MNT_RDONLY | MNT_UNIXDIR;
747 
748     return 1;
749 }
750 
751 static struct
752 {
753     char magic[32];
754     char dpb[15];
755 } sssd =
756 {
757     "<CPM_Disk>  Drive x",
758     "\x1a\x00\x03\x07\x00\xf2\x00\x3f\x00\xc0\x00\x00\x00\x02\x00"
759 };
760 
761 static char *xlt26 =
762     "\x00\x06\x0c\x12\x18\x04\x0a\x10\x16\x02\x08\x0e\x14"
763     "\x01\x07\x0d\x13\x19\x05\x0b\x11\x17\x03\x09\x0f\x15";
764 
765 static int
766 mount(int disk, const char *filename, int readonly)
767 {
768     struct mnt *dp = mnttab + disk;
769     int prot = PROT_READ | PROT_WRITE;
770     int doffs, r;
771     WORD alv;
772     int disksize, ldisksize;	/* for calculation of disk size */
773     /* BYTE buf[128];  <-- it is now defined in struct mnt (for cp/m 3) */
774     struct stat st;
775 
776     if (dp->flags & MNT_ACTIVE)
777         umount(disk);
778 
779     dp->flags = 0;
780     if (stat(filename, &st) < 0)
781     {
782         perror(filename);
783         return 0;
784     }
785 
786 #ifdef __EXTENSIONS__
787     if ((st.st_mode & S_IFMT) == S_IFDIR)
788     {
789 #else
790     if ((st.st_mode & __S_IFMT) == __S_IFDIR)
791     {
792 #endif
793 
794         if ((r = mountdir(dp, filename, lbl))
795                 && cpm3)
796             /* if (cpm3) */
797             setup_cpm3_dph_dpb(disk);
798         return r;
799     }
800 
801 #ifdef __EXTENSIONS__
802     if ((st.st_mode & S_IFMT) != S_IFREG)
803     {
804 #else
805     if ((st.st_mode & __S_IFMT) != __S_IFREG)
806     {
807 #endif
808 
809         fprintf(stderr, "%s is neither a regular file nor a directory\n", filename);
810         return 0;
811     }
812 
813     if (readonly || (dp->ifd = open(filename, O_RDWR)) < 0)
814     {
815         prot = PROT_READ;
816         dp->flags |= MNT_RDONLY;
817         if ((dp->ifd = open(filename, O_RDONLY)) < 0)
818         {
819             perror(filename);
820             return 0;
821         }
822     }
823 
824     /* peek at descriptor page */
825     if (read(dp->ifd, dp->buf, 128) != 128)
826     {
827         perror(filename);
828         close(dp->ifd);
829         return 0;
830     }
831     if (memcmp(dp->buf, "<CPM_Disk>", 10) != 0)
832     {
833         /* WORD xlt; */
834         if (st.st_size != 256256)
835         {
836             fprintf(stderr, "%s is not a valid <CPM_Disk> file\n", filename);
837             close(dp->ifd);
838             return 0;
839         }
840         /* assume this is an image of a sssd floppy */
841         memcpy(dp->buf, &sssd, sizeof(sssd));
842         if (cpm3)
843         {
844             dp->buf[sizeof(sssd)]   = 0;	/* psh = 0 */ /* added 27.3.2005 */
845             dp->buf[sizeof(sssd) + 1] = 0;	/* phm = 0 */
846             dp->xlt = 1;		/* look to setup_cpm3_dph_dpb() in bios.c */
847         }
848         else
849         {
850             dp->dph = dptable + (16 + 15) * disk;
851             memclr(ram + dp->dph, 16 + 15);
852             dp->xlt = cpmalloc(26);	/* space for sector translation table */
853             memcpy(ram + dp->xlt, xlt26, 26);
854             PutWORD(dp->dph, dp->xlt);
855         }
856         doffs = 0;
857     }
858     else
859     {
860         dp->xlt = 0;			/* no translation table */
861         if (!cpm3)
862         {
863             dp->dph = dptable + (16 + 15) * disk;
864             memclr(ram + dp->dph, 16 + 15);
865         }
866         doffs = 128;
867     }
868     if (cpm3)
869     {
870         setup_cpm3_dph_dpb(disk);
871         if (dp->dsm > maxdsm
872                 || (dp->bls == 1024 && dp->drm > 511) /* see page 43 of the SYSTEM */
873                 || (dp->bls == 2048 && dp->drm > 1023) /* GUIDE, table 3-6 */
874                 || (dp->bls == 4096 && dp->drm > 2047)
875                 || (dp->bls == 8192 && dp->drm > 4095)
876                 || (dp->bls == 16384 && dp->drm > 8191)
877                 || (dp->drm > maxdrm))
878         {
879             fprintf(stderr, "Error to mount '%s'\n", filename);
880             fprintf(stderr, "  Either the parameters at creation time of '%s' "
881                     "are wrong\n  or you have created a disk file greater "
882                     "8 MB with\n  the default parameters.\n", filename);
883             fprintf(stderr, "  If you want to mount disks with greater size "
884                     "than\n");
885             fprintf(stderr, "  8 MB then use the following create commands:\n\n");
886             ldisksize = (int)(2048 * (maxdsm + 1) / (1024 * 1024) + 1);
887             disksize = (int)(4096 * (maxdsm + 1) / (1024 * 1024));
888             fprintf(stderr, "    'create -b %d -d %d <filename> <x>M' for a "
889                     "%d - %d MB disk\n", 4096, maxdrm, ldisksize, disksize);
890             ldisksize = disksize + 1;
891             disksize = (int)(8192 * (maxdsm + 1) / (1024 * 1024));
892             fprintf(stderr, "    'create -b %d -d %d <filename> <x>M' for a "
893                     "%d - %d MB disk\n", 8192, maxdrm, ldisksize, disksize);
894             ldisksize = disksize + 1;
895             disksize = (int)(16384 * (maxdsm + 1) / (1024 * 1024));
896             fprintf(stderr, "    'create -b %d -d %d <filename> <x>M' for a "
897                     "%d - %d MB disk\n\n", 16384, maxdrm, ldisksize, disksize);
898             fprintf(stderr, "  Or edit MAXDSM and MAXDRM in sysdef.lib of the Z80 "
899                     "bios files and\n  compile the CP/M 3.1 BIOS and "
900                     "generate CPM3.SYS and CPM3.COM and\n  transfere "
901                     "CPM3.COM to the UNIX file yaze-cpm3.boot.\n"
902                     "  ATTENTION! Before you edit refer the sections "
903                     "\"Disk Parameter Header\"\n"
904                     "  and \"Disk Parameter Block\" "
905                     "page 36 - 44 of the System Guide.\n");
906             close(dp->ifd);
907             return 0;
908         }
909     }
910     else
911     {
912         PutWORD(dp->dph + 8, dirbuff);	/* pointer to directory buffer */
913         dp->dpb = dp->dph + 16;		/* pointer to dpb  */
914         /* PutWORD(dp->dph+10, dp->dph+16);\* set pointer to dpb in cp/m 2.2 */
915         PutWORD(dp->dph + 10, dp->dpb);	/* set pointer to dpb in cp/m 2.2 */
916         memcpy(ram + dp->dph + 16, dp->buf + 32, 15); /* copy dpb into cp/m ram */
917         PutWORD(dp->dph + 16 + 11, 0);	/* check vector size = 0 (fixed disk) */
918 
919         /* calculate memory requirement */
920         /* (((DSM+1)<<BSH) + OFFS*SPT + 1)*128 */
921         dp->isize = (((GetWORD(dp->dph + 16 + 5) + 1) << GetBYTE(dp->dph + 16 + 2)) +
922                      GetWORD(dp->dph + 16 + 13) * GetWORD(dp->dph + 16 + 0) + 1) * 128;
923 
924         alv = cpmalloc((GetWORD(dp->dph + 16 + 5) >> 3) + 1);
925         if (alv == 0)
926         {
927             fprintf(stderr, "insufficient space to mount %s\n", filename);
928             close(dp->ifd);
929             return 0;
930         }
931         PutWORD(dp->dph + 14, alv);	/* pointer to allocation vector  */
932     } /* of if (cpm3) */
933 
934 #ifndef MAP_FILE
935 #define MAP_FILE 0
936 #endif
937 
938 #ifdef __BOUNDS_CHECKING_ON
939     /* absurd -1 return code blows bgcc's mind */
940     dp->header = mmap(NULL, dp->isize, prot, MAP_FILE | MAP_SHARED, dp->ifd, 0);
941 #else
942     if ((dp->header = mmap(NULL, dp->isize, prot, MAP_FILE | MAP_SHARED,
943                            dp->ifd, 0)) == (char *) - 1)
944     {
945         perror(filename);
946         close(dp->ifd);
947         return 0;
948     }
949 #endif
950     dp->filename = newstr(filename);
951     dp->data = (BYTE *) dp->header + doffs;
952     dp->flags |= MNT_ACTIVE;
953 
954     return 1;
955 }
956 
957 int
958 remount(int disk)
959 {
960     struct mnt *dp = mnttab + disk;
961     char *filename;
962     int r;
963 
964     if (!(dp->flags & MNT_ACTIVE))
965         return 0;
966     filename = newstr(dp->filename);
967     r = (dp->flags & MNT_RDONLY) ? 1 : 0;
968     /*    printf("remount: disk = %d, filename = %s, r = %d\r\n",disk,filename,r);
969     */
970     r = mount(disk, filename, r);
971     free(filename);
972     return r;
973 }
974 
975 static const char *white = " \t";
976 
977 static int
978 dosetacc(char *cmd)
979 {
980     char *tok = strtok(NULL, white);
981 
982     if (tok)
983     {
984         if (strcmp(tok, "on") == 0)
985             lbl = LBL_existsbit | LBL_updatebit | LBL_accessbit;
986         else if (strcmp(tok, "off") == 0)
987             lbl = LBL_existsbit | LBL_updatebit;
988         else
989             fprintf(stderr, "setaccess needs the parameter \"on\" or "
990                     "\"off\" (see \"help setaccess\")\r\n");
991     }
992     else
993         printf("access stamps = %s\n", (lbl & LBL_accessbit) ? "ON" : "OFF");
994     return 0;
995 }
996 
997 static int
998 doremount(char *cmd)
999 {
1000     int d;
1001     char *tok = strtok(NULL, white);
1002 
1003     if (tok)
1004     {
1005         d = *tok - 'A';
1006         if (d < 0 || d > 15)
1007             d = *tok - 'a';
1008         if (d < 0 || d > 15 || tok[1])
1009         {
1010             fprintf(stderr, "illegal disk specifier: %s\n", tok);
1011             return 0;
1012         }
1013         /* printf("doremount: disk = %d\n",d); */
1014         if (mnttab[d].flags & MNT_ACTIVE)
1015             remount(d);
1016         else
1017             fprintf(stderr, "drive %c is not mounted\n", d + 'A');
1018     }
1019     else
1020         fprintf(stderr, "remount needs a disk specifier\n");
1021     return 0;
1022 }
1023 
1024 static int
1025 domount(char *cmd)
1026 {
1027     int d, v, r;
1028     char *tok = strtok(NULL, white);
1029 
1030     if ((v = (tok && (strcmp(tok, "-v") == 0))))
1031         tok = strtok(NULL, white);
1032     if ((r = tok && (strcmp(tok, "-r") == 0)))
1033         tok = strtok(NULL, white);
1034     if (tok && !v)
1035     {
1036         d = *tok - 'A';
1037         if (d < 0 || d > 15)
1038             d = *tok - 'a';
1039         if (d < 0 || d > 15 || tok[1])
1040         {
1041             fprintf(stderr, "illegal disk specifier: %s\r\n", tok);
1042             return 0;
1043         }
1044 	tok = strtok(NULL, white);
1045 	mount(d, tok, r);
1046 	/* printf("domount: disk: %c:, ydsk-file: %s, read only = %c\r\n",'A'+d,tok,r?'Y':'N'); */
1047     }
1048     else
1049         for (d = 0; d < 16; d++)
1050             if (mnttab[d].flags & MNT_ACTIVE)
1051                 showdisk(d, v);
1052     return 0;
1053 }
1054 
1055 static int
1056 doumount(char *cmd)
1057 {
1058     int d;
1059     char *tok = strtok(NULL, white);
1060 
1061     if (tok)
1062     {
1063         d = *tok - 'a';
1064         if (d < 0 || d > 15 || tok[1])
1065         {
1066             fprintf(stderr, "illegal disk specifier: %s\n", tok);
1067             return 0;
1068         }
1069         umount(d);
1070     }
1071     else
1072         fprintf(stderr, "umount needs a disk specifier\n");
1073     return 0;
1074 }
1075 
1076 struct sio siotab[MAXPSTR] =
1077 {
1078     { NULL, NULL, "ttyin", 0, ST_IN2 },
1079     { NULL, NULL, "ttyout", 0, ST_OUT2 },
1080     { NULL, NULL, "crtin", 0, ST_IN2 },
1081     { NULL, NULL, "crtout", 0, ST_OUT2 },
1082     { NULL, NULL, "uc1in", 0, ST_IN2 },
1083     { NULL, NULL, "uc1out", 0, ST_OUT2 },
1084     { NULL, NULL, "rdr", 0, ST_IN },
1085     { NULL, NULL, "ur1", 0, ST_IN },
1086     { NULL, NULL, "ur2", 0, ST_IN },
1087     { NULL, NULL, "pun", 0, ST_OUT },
1088     { NULL, NULL, "up1", 0, ST_OUT },
1089     { NULL, NULL, "up2", 0, ST_OUT },
1090     { NULL, NULL, "lpt", 0, ST_OUT },
1091     { NULL, NULL, "ul1", 0, ST_OUT },
1092     { NULL, NULL, "aux", 0, ST_INOUT }
1093 };
1094 
1095 
1096 /* Table of logical streams */
1097 int chn[6];
1098 
1099 static int
1100 doattach(char *cmd)
1101 {
1102     int fd, i, opflags;
1103     struct sio *s;
1104     char *tok = strtok(NULL, white);
1105 
1106     if (tok)
1107     {
1108         char *p = tok + strlen(tok);
1109         if (p > tok && *--p == ':')
1110             *p = 0;
1111         for (i = 0; i < MAXPSTR; i++)
1112         {
1113             s = siotab + i;
1114             if (strncmp(tok, s->streamname, 3) == 0)
1115                 break;
1116         }
1117         if (i == MAXPSTR)
1118         {
1119             fprintf(stderr, "stream not recognized: %s\n", tok);
1120             return 0;
1121         }
1122         if (s->strtype == ST_INOUT)
1123         {
1124             opflags = O_RDWR;
1125             /* printf("ST_INOUT\n"); */
1126         }
1127         else if (s->strtype == ST_IN2)
1128         {
1129             if (strcmp(tok, (s + 1)->streamname) == 0)
1130             {
1131                 s++;
1132                 opflags = O_WRONLY | O_CREAT | O_TRUNC;
1133             }
1134             else if (strcmp(tok, s->streamname) == 0)
1135                 opflags = O_RDONLY;
1136             else
1137                 opflags = O_RDWR | O_CREAT;
1138         }
1139         else
1140             opflags = s->strtype ==
1141                       ST_IN ? O_RDONLY : O_WRONLY | O_CREAT | O_TRUNC;
1142         tok = strtok(NULL, white);
1143         if (!tok || !*tok)
1144         {
1145             fputs("need a filename\n", stderr);
1146             return 0;
1147         }
1148         if (s->fp)
1149         {
1150             fclose(s->fp);
1151             s->fp = NULL;
1152             free(s->filename);
1153         }
1154         if ((fd = open(tok, opflags, 0666)) < 0)
1155             perror(tok);
1156         else
1157         {
1158             char *mode = "rb";
1159             if (opflags & O_WRONLY)
1160                 mode = "wb";
1161             else if (opflags & O_RDWR)
1162                 mode = "r+b";
1163             s->filename = newstr(tok);
1164             s->fp = fdopen(fd, mode);
1165             s->tty = isatty(fd);
1166             /* printf("isatty %d\n",s->tty); */
1167         }
1168     }
1169     else for (i = 0; i < MAXPSTR; i++)
1170         {
1171             s = siotab + i;
1172             if (s->fp)
1173                 printf("%s:\t%s\n", s->streamname, s->filename);
1174         }
1175     return 0;
1176 }
1177 
1178 static int
1179 dodetach(char *cmd)
1180 {
1181     int i;
1182     struct sio *s;
1183     char *tok = strtok(NULL, white);
1184 
1185     if (tok)
1186     {
1187         char *p = tok + strlen(tok);
1188         if (p > tok && *--p == ':')
1189             *p = 0;
1190         for (i = 0; i < MAXPSTR; i++)
1191         {
1192             s = siotab + i;
1193             if (strncmp(tok, s->streamname, 3) == 0)
1194                 break;
1195         }
1196         if (i == MAXPSTR)
1197         {
1198             fprintf(stderr, "stream not recognized: %s\n", tok);
1199             return 0;
1200         }
1201         if (s->fp)
1202         {
1203             fclose(s->fp);
1204             s->fp = NULL;
1205             free(s->filename);
1206         }
1207     }
1208     return 0;
1209 }
1210 
1211 
1212 static long
1213 getval(char *s)
1214 {
1215     char *tok = s + 2;
1216 
1217     if (*tok == 0)
1218         tok = strtok(NULL, white);
1219     if (tok && *tok)
1220     {
1221         long unit = 1;
1222         char u = tok[strlen(tok) - 1];
1223         switch (toupper(u))
1224         {
1225         case 'K':
1226             unit = 1024;
1227             break;
1228         case 'M':
1229             unit = 1024 * 1024;
1230             break;
1231         }
1232         return unit * strtol(tok, NULL, 10);
1233     }
1234     else
1235     {
1236         fprintf(stderr, "option needs a value: %s\n", s);
1237         return -1;
1238     }
1239 }
1240 
1241 static void
1242 checkval(int ok, long val, char *msg)
1243 {
1244     if (!ok)
1245         fprintf(stderr, "bad %s value: %ld\n", msg, val);
1246 }
1247 
1248 /* count ones in a 16-bit value */
1249 static int
1250 popcount(long v)
1251 {
1252     int total;
1253 
1254     total = ((v >> 1) & 0x5555) + (v & 0x5555);
1255     total = ((total >> 2) & 0x3333) + (total & 0x3333);
1256     total = ((total >> 4) & 0x0f0f) + (total & 0x0f0f);
1257     return (total & 0xff) + (total >> 8);
1258 }
1259 
1260 static void
1261 makedisk(FILE *f, char *fn, long diroffs, long dirsize, long fullsize)
1262 {
1263     long n;
1264     BYTE sector[128];
1265 
1266     memset(sector, 0xe5, sizeof sector);
1267 
1268     /* skip offset tracks */
1269     if (fseek(f, diroffs, SEEK_CUR) < 0)
1270     {
1271         fclose(f);
1272         perror(fn);
1273         return;
1274     }
1275     /* write empty directory */
1276     for (n = 0; n < dirsize; n += sizeof sector)
1277         if (fwrite(sector, sizeof sector, 1, f) == 0)
1278         {
1279             fclose(f);
1280             perror(fn);
1281             return;
1282         }
1283     /* seek to end of disk and write last sector to define size */
1284     if (fseek(f, fullsize - sizeof sector, SEEK_SET) < 0 ||
1285             fwrite(sector, sizeof sector, 1, f) == 0 ||
1286             fclose(f) != 0)
1287         perror(fn);
1288 }
1289 
1290 /* create a new CP/M disk */
1291 static int
1292 docreate(char *tok)
1293 {
1294     char *fn = NULL;
1295     FILE *f;
1296     long size = 1024 * 1024;
1297     char head[128];
1298     long dsm, spt = -1, bsize = -1, drm = -1, offs = -1;
1299     long psh = 0;	/* by agl for CP/M 3.1. */
1300     long phm = 0;	/* default 128 byte sectors*/
1301     int dblocks;
1302     WORD al01;
1303 
1304     while ((tok = strtok(NULL, white)) != NULL)
1305     {
1306         if (*tok == '-')
1307             switch (tok[1])
1308             {
1309             case 'b':
1310                 bsize = getval(tok);
1311                 break;
1312             case 'd':
1313                 drm = getval(tok);
1314                 break;
1315             case 'o':
1316                 offs = getval(tok);
1317                 break;
1318             case 's':
1319                 spt = getval(tok);
1320                 break;
1321             default:
1322                 fprintf(stderr, "unrecognized option: %s\n", tok);
1323             }
1324         else
1325         {
1326             fn = tok;
1327             break;
1328         }
1329     }
1330 
1331     if (fn == NULL)
1332     {
1333         fputs("need a filename\n", stderr);
1334         return 0;
1335     }
1336     if ((tok = strtok(NULL, white)) != NULL)
1337     {
1338         char unit = 'b';
1339         int n = sscanf(tok, "%ld%c", &size, &unit);
1340         if (n == 2)
1341             switch (toupper(unit))
1342             {
1343             case 'B':
1344                 break;
1345             case 'K':
1346                 size *= 1024;
1347                 break;
1348             case 'M':
1349                 size *= 1024 * 1024;
1350                 break;
1351             default:
1352                 fprintf(stderr, "units not recognized: %s\n", tok);
1353                 return 0;
1354             }
1355         else if (n != 1)
1356         {
1357             fprintf(stderr, "need numeric size: %s\n", tok);
1358             return 0;
1359         }
1360     }
1361     if ((f = fopen(fn, "w")) == NULL)
1362     {
1363         perror(fn);
1364         return 0;
1365     }
1366     if (size == 256256 && (spt == -1 || spt == 26) &&
1367             (bsize == -1 || bsize == 1024) &&
1368             (drm == -1 || drm == 63) &&
1369             (offs == -1 || offs == 2))
1370     {
1371         /* raw standard sssd floppy format */
1372         spt = 26;
1373         drm = 63;
1374         offs = 2;
1375         /*  we clear all tracks that might contain directory sectors,
1376             thus avoiding messing with the sector translation table */
1377         makedisk(f, fn, 128 * spt * offs, 128 * (((drm + 4) / 4 + spt - 1) / spt)*spt, size);
1378         return 0;
1379     }
1380     else if (size < 256 * 1024)
1381     {
1382         if (bsize == -1)
1383             bsize = 1024;
1384         if (drm == -1)
1385             drm = 63;
1386         if (spt == -1)
1387             spt = 26;
1388         if (offs == -1)
1389             offs = 0;
1390     }
1391     else
1392     {
1393         if (bsize == -1)
1394             bsize = 2048;
1395         if (drm == -1)
1396             drm = 1023;
1397         if (spt == -1)
1398         {
1399             spt = 128;  /* Version 1 uses 128 sectors per track */
1400             psh = 4;
1401             phm = 15; /* sector size also 2048 */
1402         }
1403         if (offs == -1)
1404             offs = 0;
1405     }
1406     dsm = (size - offs * spt * 128) / bsize - 1;
1407     checkval(spt <= 0xffff, spt, "sectors per track");
1408     checkval(size / (spt * 128) + offs <= 0xffff, size / (spt * 128) + offs, "tracks");
1409     checkval(((bsize & (bsize - 1)) == 0) &&
1410              (bsize >= ((dsm < 256) ? 1024 : 2048)) &&
1411              bsize <= 16384, bsize, "block size");
1412     dblocks = ((drm + 1) * 32 + bsize - 1) / bsize;
1413     checkval(dblocks <= 16 && dblocks < dsm, drm, "max directory entry");
1414     memclr(head, sizeof head);
1415     sprintf(head, "<CPM_Disk>");
1416     head[16] = 1;	/* version identifier by agl */
1417     /* yaze-1.10 have Version 0 */
1418     /* V 1: uses also PSH and PHM */
1419     head[32] = spt;
1420     head[33] = spt >> 8;
1421     head[34] = popcount(bsize - 1) - 7;	/* bsh */
1422     head[35] = (bsize / 128 - 1);		/* blm */
1423     head[36] = dsm < 256 ? bsize / 1024 - 1 : bsize / 2048 - 1; /* exm */
1424     head[37] = dsm;
1425     head[38] = dsm >> 8;
1426     head[39] = drm;
1427     head[40] = drm >> 8;
1428     al01 = ~((1 << (16 - dblocks)) - 1);
1429     head[41] = al01 >> 8;
1430     head[42] = al01;
1431     head[45] = offs;
1432     head[46] = offs >> 8;
1433 
1434     head[47] = psh;	/* PSH */ /* Version 1 */
1435     head[48] = phm;	/* PHM */
1436 
1437     if (fwrite(head, sizeof head, 1, f) == 0)
1438     {
1439         fclose(f);
1440         perror(fn);
1441         return 0;
1442     }
1443     makedisk(f, fn, 128 * spt * offs, 128 * (drm + 4) / 4, sizeof head + size);
1444     return 0;
1445 }
1446 
1447 static int
1448 hexdig(char c)
1449 {
1450     if ('0' <= c && c <= '9')
1451         return c - '0';
1452     if ('A' <= c && c <= 'F')
1453         return c - 'A' + 10;
1454     if ('a' <= c && c <= 'f')
1455         return c - 'a' + 10;
1456     return -1;
1457 }
1458 
1459 static int
1460 doint(char *cmd)
1461 {
1462     int d1, d2;
1463     char *tok = strtok(NULL, white);
1464 
1465     if (tok)
1466     {
1467         if (strlen(tok) != 2)
1468         {
1469 bad:
1470             printf("%s invalid key specifier\n", tok);
1471             return 0;
1472         }
1473         /* let's face it: this doesn't work if the host character set is not ascii */
1474         if (tok[0] == '^' && '@' <= tok[1])
1475             interrupt = tok[1] & 0x1f;
1476         else
1477         {
1478             if ((d1 = hexdig(tok[0])) < 0)
1479                 goto bad;
1480             if ((d2 = hexdig(tok[1])) < 0)
1481                 goto bad;
1482             interrupt = (d1 << 4) + d2;
1483         }
1484         rawtio.c_lflag = interrupt ? ISIG : 0;
1485         rawtio.c_cc[VINTR] = interrupt;
1486     }
1487     else
1488     {
1489         fputs("interrupt key is ", stdout);
1490         if (interrupt == 0)
1491             puts("disabled");
1492         else if (interrupt < 0x20)
1493             printf("^%c\n", interrupt + '@');
1494         else
1495             printf("%2x\n", interrupt);
1496     }
1497     return 0;
1498 }
1499 
1500 extern char *perl_params;
1501 
1502 static int
1503 dotime(char *cmd)
1504 {
1505     static clock_t lastreal;
1506     clock_t now;
1507     static struct tms lastbuf;
1508     struct tms tbuf;
1509     static clock_t tickspersec;
1510     /* extern char *perl_params; */
1511 
1512     if (tickspersec == 0)
1513         tickspersec = sysconf(_SC_CLK_TCK);
1514     now = times(&tbuf);
1515 
1516     printf("elapsed=%.3f, user=%.3f, sys=%.3f (%s)\n",
1517            ((double)(now - lastreal)) / tickspersec,
1518            ((double)(tbuf.tms_utime - lastbuf.tms_utime)) / tickspersec,
1519            ((double)(tbuf.tms_stime - lastbuf.tms_stime)) / tickspersec,
1520            perl_params);
1521     lastreal = now;
1522     lastbuf = tbuf;
1523     return 0;
1524 }
1525 
1526 static int
1527 dogo(char *cmd)
1528 {
1529     return 1;
1530 }
1531 
1532 static int
1533 doshell(char *cmd)
1534 {
1535     char *shell = getenv("SHELL");
1536 #ifdef DEBUG
1537     void (*sigint)(int);
1538 
1539     sigint = signal(SIGINT, SIG_IGN);
1540 #endif
1541     if (shell == NULL)
1542         shell = "/bin/sh";
1543     if (cmd[1])
1544         system(cmd + 1);
1545     else
1546     {
1547         system(shell);
1548         printf("Back in yaze-ag\n\07");
1549     }
1550 #ifdef DEBUG
1551     (void) signal(SIGINT, sigint);
1552 #endif
1553     return 0;
1554 }
1555 
1556 static int
1557 do128(char *cmd)
1558 {
1559     int d;
1560     struct mnt *dp;
1561 
1562     if (!cpm3)
1563     {
1564         puts("only used under CP/M 3.1");
1565         return 0;
1566     }
1567     always128 = 1;
1568     for (d = 0; d < 16; d++)
1569     {
1570         dp = mnttab + d;
1571         if (dp->flags & MNT_ACTIVE)
1572             setup_cpm3_dph_dpb(d);
1573     }
1574     puts("All disks have sektor size 128.");
1575     return 0;
1576 }
1577 
1578 static int
1579 doclock(char *tok)
1580 {
1581     if ((tok = strtok(NULL, white)) != NULL) {
1582 	clockFrequency_save = (FASTREG) strtol(tok, NULL, 10);
1583 	brakeini = true;
1584 	/*
1585 	printf("token:%s\n", tok);
1586 	*/
1587     }
1588     if (clockFrequency_save) {
1589 	printf("Frequency: %d kHz (%d.%d MHz)\n", clockFrequency_save,
1590 						clockFrequency_save/1000,
1591 						clockFrequency_save%1000);
1592     } else {
1593 	puts("Frequency: max speed");
1594     }
1595     return 0;
1596 }
1597 
1598 static int
1599 doquit(char *cmd)
1600 {
1601     exit(0);
1602 }
1603 
1604 static int dohelp(char *cmd);
1605 
1606 typedef struct
1607 {
1608     char *name;				/* User printable name of the function. */
1609     int (*func)(char *);		/* Function to call to do the job. */
1610     char *doc;				/* Short documentation.  */
1611     char *detail;			/* Long documentation. */
1612 } COMMAND;
1613 
1614 static COMMAND commands[] =
1615 {
1616     {
1617         "help",   dohelp,   "Display this text or give help about a command",
1618         "help <cmd>                 displays more information about <cmd>"
1619     },
1620     { "?",      dohelp,   "Synonym for `help'", NULL },
1621     {
1622         "attach", doattach, "Attach CP/M device to a unix file",
1623         "attach                     without arguments lists the current attachments\n"
1624         "attach <physdev> <file>    attaches <physdev> to the unix <file>,\n"
1625         "                           where <physdev> is one of ttyin, ttyout,\n"
1626         "                           crtin, crtout, uc1in, uc1out, rdr,\n"
1627         "                           ur1, ur2, pun, up1, up2, lpt, ul1"
1628     },
1629     {	"clock", doclock, "Set/Display the frequency of the Z80 simulator",
1630 	"clock                      without argument lists the current frequency.\n"
1631 	"                           The frequency is shown in kHz and MHz.\n"
1632 	"                           If the frequency is 0 the emulator runs with\n"
1633 	"                           \"max speed\".\n\n"
1634 	"clock <frequency in kHz>   Set a frequency. The frequency must be in kHz.\n"
1635 	"                           For example for 4 MHz place 4000.\n"
1636 	"                           A \"0\" returns to max speed."
1637     },
1638     {
1639         "detach", dodetach, "Detach CP/M device from file",
1640         "detach <physdev>           closes the file attached to <physdev>\n"
1641         "                           (see attach)"
1642     },
1643     {
1644         "setaccess", dosetacc, "Turns on/off access time stamps for mounted directories",
1645         "setaccess on/off           turns on/off access time stamps for "
1646         "mounted\n"
1647         "                           directories connected to a CP/M drive\n"
1648         "                           (see CP/M command \"SET [ACCESS=ON/OFF]\")\n\n"
1649         "                           without parameter status is printed\n\n"
1650         "                           default : off\n\n"
1651         "                           (update time stamps are always on)\n"
1652         "                           (create time stamps are not supported by "
1653         "host system)"
1654     },
1655     {
1656         "mount",  domount,  "Mount a unix file or directory as a CP/M disk",
1657         "mount                      without arguments lists the mount table\n"
1658         "mount -v                   lists the mount table verbosely\n"
1659         "mount <drive> <file>       mounts <file> as CP/M disk <drive>\n"
1660         "                           (a letter from a..p).\n"
1661         "        If <file> is a plain file it must contain a CP/M filesystem.\n"
1662         "        If <file> is a unix directory its contents may be accessed\n"
1663         "           as a read-only CP/M disk\n"
1664         "mount -r <drive> <file>    mounts the <file> read/only."
1665     },
1666     {
1667         "remount", doremount, "Remount a CP/M disk",
1668         "remount <drive>            remounts the file/directory associated with"
1669         " <drive>\n"
1670         "                           (a directory will be fully rereaded)"
1671     },
1672     {
1673         "umount", doumount, "Unmount a CP/M disk",
1674         "umount <drive>             closes the file associated with <drive>\n"
1675         "                           and frees the resources"
1676     },
1677     {
1678         "create", docreate, "Create a new disk",
1679         "create {flags} <file> {size}  creates a unix <file> initialized as a\n"
1680         "                              CP/M disk of size {size} (default 1MB).\n"
1681         "       -b <block size>        default 1024 if size < 256K, else 2048\n"
1682         "       -d <# dir entries - 1> default 1023\n"
1683         "       -o <track offset>      default 0\n"
1684         "       -s <sectors per track> default 128\n"
1685         "create <file> 256256          create a raw SSSD disk image"
1686     },
1687     {
1688         "128", do128, "Set sektor size to 128 for all disks (only CP/M 3.1)",
1689         "128    Set sektor size to 128 for all disks (only CP/M 3.1)\n\n"
1690         "       If you create a disk file under yaze-ag and you use the default\n"
1691         "       blocksize and the default sectors per track (see create) the\n"
1692         "       sektor size is also set to 2048 bytes like the blocksize.\n\n"
1693         "       If you use software like a disk edit utility under CP/M 3.1\n"
1694         "       it can be necessary to set the sektor size to 128 bytes.\n\n"
1695         "       To reverse this option you must restart yaze-ag."
1696     },
1697     {
1698         "interrupt", doint, "Set user interrupt key",
1699         "interrupt <key>            makes <key> interrupt CP/M back to the monitor\n"
1700         "        <key> may be a 2-digit hex number or ^x where x is one of a..z[\\]^_\n"
1701         "        ^@ makes CP/M uninterruptible (from the keyboard)\n"
1702         "interrupt                  without an argument displays the current setting"
1703     },
1704     { "go",     dogo,     "Start/Continue CP/M execution", NULL },
1705     {
1706         "!",      doshell,  "Execute a unix command",
1707         "!                          escape to a unix shell\n"
1708         "!cmd                       execute unix cmd"
1709     },
1710     { "quit",   doquit,   "Terminate yaze-ag", NULL },
1711     {
1712         "time",   dotime,   "Display elapsed time since last `time' command",
1713         "displays elapsed, user and system time in seconds,\n"
1714         "         along with simulator options"
1715     },
1716     { NULL, NULL, NULL, NULL }
1717 };
1718 
1719 static int
1720 dohelp(char *cmd)
1721 {
1722     char *tok = strtok(NULL, white);
1723     int tlen;
1724     COMMAND *cp;
1725 
1726     if (tok)
1727     {
1728         for (tlen = strlen(tok), cp = commands; cp->name; cp++)
1729             if (strncmp(tok, cp->name, tlen) == 0)
1730                 break;
1731         if (cp->name)
1732         {
1733             puts(cp->detail ? cp->detail : cp->doc);
1734             return 0;
1735         }
1736     }
1737     for (cp = commands; cp->name; cp++)
1738         printf("%-10s  %s\n", cp->name, cp->doc);
1739     return 0;
1740 }
1741 
1742 int
1743 docmd(char *cmd)
1744 {
1745     char *tok;
1746     int tlen;
1747     COMMAND *cp;
1748     int (*func)(char *) = NULL;
1749 
1750     if (cmd == NULL)
1751         return 0;
1752     if (*cmd == '#')
1753         return 0;
1754     while (*cmd == ' ' || *cmd == '\t' || *cmd == '\n')
1755         cmd++;
1756     for (tok = cmd + strlen(cmd) - 1; tok >= cmd; tok--)
1757         if (*tok == ' ' || *tok == '\t' || *tok == '\n')
1758             *tok = 0;
1759         else
1760             break;
1761     if (*cmd == 0)
1762         return 0;
1763     add_history(cmd);
1764     if (*cmd == '!')
1765     {
1766         /* special case */
1767         doshell(cmd);
1768         return 0;
1769     }
1770     tok = strtok(cmd, white);
1771     if (tok == NULL || *tok == 0)
1772         return 0;
1773     for (tlen = strlen(tok), cp = commands; cp->name; cp++)
1774         if (strncmp(tok, cp->name, tlen) == 0)
1775             /* don't allow quit command to be abbreviated */
1776             if (cp->func != doquit || strcmp(tok, cp->name) == 0)
1777             {
1778                 if (func == NULL)
1779                     func = cp->func;
1780                 else
1781                 {
1782                     func = NULL;	/* ambiguous */
1783                     break;
1784                 }
1785             }
1786     if (func)
1787         return func(cmd);
1788     printf("%s ?\n", tok);
1789     return 0;
1790 }
1791 
1792 #ifdef DEBUG
1793 void
1794 sighand(int sig)
1795 {
1796     stopsim = 1;
1797 }
1798 #endif
1799 
1800 void
1801 monitor(FASTWORK adr)
1802 {
1803     static char *cmd = NULL;
1804 
1805     ttycook();
1806 #ifdef DEBUG
1807     if (adr & 0x10000)
1808         printf("stopped at pc=0x%04x\n", adr & 0xffff);
1809     stopsim = 0;
1810     signal(SIGINT, sighand);
1811 #endif
1812 #ifdef USE_GNU_READLINE
1813     do
1814     {
1815         if (cmd)
1816         {
1817             free(cmd);
1818             cmd = NULL;
1819         }
1820         cmd = readline("$>");
1821         if (cmd == NULL)
1822         {
1823             if ((ttyflags & ISATTY) == 0)
1824                 doquit(NULL);
1825             else
1826                 putchar('\n');
1827         }
1828     }
1829     while (!docmd(cmd));
1830 #else
1831     if (cmd == NULL)
1832         cmd = xmalloc(BUFSIZ);
1833     do
1834     {
1835         fputs("$>", stdout);
1836         fflush(stdout);
1837         if (fgets(cmd, BUFSIZ - 1, stdin) == NULL)
1838         {
1839             if ((ttyflags & ISATTY) == 0)
1840                 doquit(NULL);
1841             else
1842             {
1843                 putchar('\n');
1844                 cmd[0] = 0;
1845             }
1846         }
1847     }
1848     while (!docmd(cmd));
1849 #endif
1850     ttyraw();
1851 }
1852