1 /*************************************************************************
2  *                                                                       *
3  * $Id: sim_imd.c 1999 2008-07-22 04:25:28Z hharte $                     *
4  *                                                                       *
5  * Copyright (c) 2007-2008 Howard M. Harte.                              *
6  * http://www.hartetec.com                                               *
7  *                                                                       *
8  * Permission is hereby granted, free of charge, to any person obtaining *
9  * a copy of this software and associated documentation files (the       *
10  * "Software"), to deal in the Software without restriction, including   *
11  * without limitation the rights to use, copy, modify, merge, publish,   *
12  * distribute, sublicense, and/or sell copies of the Software, and to    *
13  * permit persons to whom the Software is furnished to do so, subject to *
14  * the following conditions:                                             *
15  *                                                                       *
16  * The above copyright notice and this permission notice shall be        *
17  * included in all copies or substantial portions of the Software.       *
18  *                                                                       *
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
20  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
21  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND                 *
22  * NONINFRINGEMENT. IN NO EVENT SHALL HOWARD M. HARTE BE LIABLE FOR ANY  *
23  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  *
24  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     *
25  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                *
26  *                                                                       *
27  * Except as contained in this notice, the name of Howard M. Harte shall *
28  * not be used in advertising or otherwise to promote the sale, use or   *
29  * other dealings in this Software without prior written authorization   *
30  * Howard M. Harte.                                                      *
31  *                                                                       *
32  * SIMH Interface based on altairz80_hdsk.c, by Peter Schorn.            *
33  *                                                                       *
34  * Module Description:                                                   *
35  *     ImageDisk (IMD) Disk Image File access module for SIMH.           *
36  *     see: http://www.classiccmp.org/dunfield/img/index.htm             *
37  *     for details on the ImageDisk format and other utilities.          *
38  *                                                                       *
39  * Environment:                                                          *
40  *     User mode only                                                    *
41  *                                                                       *
42  *************************************************************************/
43 
44 /* Change log:
45      - 06-Aug-2008, Tony Nicholson, Add support for logical Head and
46                     Cylinder maps in the .IMD image file (AGN)
47 */
48 
49 #include "sim_defs.h"
50 #include "sim_imd.h"
51 #include <fcntl.h>
52 #ifdef _WIN32
53 #include <io.h>     /* for _chsize() */
54 #else
55 #include <unistd.h>
56 #endif
57 /* #define DBG_MSG */
58 
59 #ifdef DBG_MSG
60 #define DBG_PRINT(args) printf args
61 #else
62 #define DBG_PRINT(args)
63 #endif
64 
65 /* use NLP for new line printing while the simulation is running */
66 #if defined (__linux) || defined(__NetBSD__) || defined (__OpenBSD__) || defined (__FreeBSD__) || defined (__APPLE__)
67 #define UNIX_PLATFORM 1
68 #else
69 #define UNIX_PLATFORM 0
70 #endif
71 
72 #if UNIX_PLATFORM
73 #define NLP "\r\n"
74 #else
75 #define NLP "\n"
76 #endif
77 
78 #if (defined (__MWERKS__) && defined (macintosh)) || defined(__DECC)
79 #define __FUNCTION__ __FILE__
80 #endif
81 
82 static t_stat commentParse(DISK_INFO *myDisk, uint8 comment[], uint32 buffLen);
83 static t_stat diskParse(DISK_INFO *myDisk, uint32 isVerbose);
84 static t_stat diskFormat(DISK_INFO *myDisk);
85 
86 /* Open an existing IMD disk image.  It will be opened and parsed, and after this
87  * call, will be ready for sector read/write. The result is the corresponding
88  * DISK_INFO or NULL if an error occurred.
89  */
diskOpen(FILE * fileref,uint32 isVerbose)90 DISK_INFO *diskOpen(FILE *fileref, uint32 isVerbose)
91 {
92     DISK_INFO *myDisk = NULL;
93 
94     myDisk = (DISK_INFO *)malloc(sizeof(DISK_INFO));
95     myDisk->file = fileref;
96 
97     if (diskParse(myDisk, isVerbose) != SCPE_OK) {
98         free(myDisk);
99         myDisk = NULL;
100     }
101 
102     return myDisk;
103 }
104 
105 /* Scans the IMD file for the comment string, and returns it in comment buffer.
106  * After this function returns, the file pointer is placed after the comment and
107  * the 0x1A "EOF" marker.
108  *
109  * The comment parameter is optional, and if NULL, then the ocmment will not
110  * be extracted from the IMD file, but the file position will still be advanced
111  * to the end of the comment.
112  */
commentParse(DISK_INFO * myDisk,uint8 comment[],uint32 buffLen)113 static t_stat commentParse(DISK_INFO *myDisk, uint8 comment[], uint32 buffLen)
114 {
115     uint8 cData;
116     uint32 commentLen = 0;
117 
118     /* rewind to the beginning of the file. */
119     rewind(myDisk->file);
120     cData = fgetc(myDisk->file);
121     while ((!feof(myDisk->file)) && (cData != 0x1a)) {
122         if ((comment != NULL) && (commentLen < buffLen)) {
123             comment[commentLen++] = cData;
124         }
125         cData = fgetc(myDisk->file);
126     }
127     if (comment != NULL) {
128         if (commentLen == buffLen)
129             commentLen--;
130         comment[commentLen] = 0;
131     }
132     return SCPE_OK;
133 }
134 
headerOk(IMD_HEADER imd)135 static uint32 headerOk(IMD_HEADER imd) {
136     return (imd.cyl < MAX_CYL) && (imd.head < MAX_HEAD);
137 }
138 
139 /* Parse an IMD image.  This sets up sim_imd to be able to do sector read/write and
140  * track write.
141  */
diskParse(DISK_INFO * myDisk,uint32 isVerbose)142 static t_stat diskParse(DISK_INFO *myDisk, uint32 isVerbose)
143 {
144     uint8 comment[256];
145     uint8 sectorMap[256];
146     uint8 sectorHeadMap[256];
147     uint8 sectorCylMap[256];
148     uint32 sectorSize, sectorHeadwithFlags, sectRecordType;
149     uint32 i;
150     uint8 start_sect;
151 
152     uint32 TotalSectorCount = 0;
153     IMD_HEADER imd;
154 
155     if(myDisk == NULL) {
156         return (SCPE_OPENERR);
157     }
158 
159     memset(myDisk->track, 0, (sizeof(TRACK_INFO)*MAX_CYL*MAX_HEAD));
160 
161     if (commentParse(myDisk, comment, sizeof(comment)) != SCPE_OK) {
162         return (SCPE_OPENERR);
163     }
164 
165     if(isVerbose)
166         printf("%s" NLP, comment);
167 
168     myDisk->nsides = 1;
169     myDisk->ntracks = 0;
170     myDisk->flags = 0;      /* Make sure all flags are clear. */
171 
172     if(feof(myDisk->file)) {
173         printf("SIM_IMD: Disk image is blank, it must be formatted." NLP);
174         return (SCPE_OPENERR);
175     }
176 
177     do {
178         DBG_PRINT(("start of track %d at file offset %ld" NLP, myDisk->ntracks, ftell(myDisk->file)));
179 
180         sim_fread(&imd, 1, 5, myDisk->file);
181         if (feof(myDisk->file))
182             break;
183         sectorSize = 128 << imd.sectsize;
184         sectorHeadwithFlags = imd.head; /*AGN save the head and flags */
185         imd.head &= 1 ; /*AGN mask out flag bits to head 0 or 1 */
186 
187         DBG_PRINT(("Track %d:" NLP, myDisk->ntracks));
188         DBG_PRINT(("\tMode=%d, Cyl=%d, Head=%d(%d), #sectors=%d, sectsize=%d (%d bytes)" NLP, imd.mode, imd.cyl, sectorHeadwithFlags, imd.head, imd.nsects, imd.sectsize, sectorSize));
189 
190         if (!headerOk(imd)) {
191             printf("SIM_IMD: Corrupt header." NLP);
192             return (SCPE_OPENERR);
193         }
194 
195         if((imd.head + 1) > myDisk->nsides) {
196             myDisk->nsides = imd.head + 1;
197         }
198 
199         myDisk->track[imd.cyl][imd.head].mode = imd.mode;
200         myDisk->track[imd.cyl][imd.head].nsects = imd.nsects;
201         myDisk->track[imd.cyl][imd.head].sectsize = sectorSize;
202 
203         if (sim_fread(sectorMap, 1, imd.nsects, myDisk->file) != imd.nsects) {
204             printf("SIM_IMD: Corrupt file [Sector Map]." NLP);
205             return (SCPE_OPENERR);
206         }
207         myDisk->track[imd.cyl][imd.head].start_sector = imd.nsects;
208         DBG_PRINT(("\tSector Map: "));
209         for(i=0;i<imd.nsects;i++) {
210             DBG_PRINT(("%d ", sectorMap[i]));
211             if(sectorMap[i] < myDisk->track[imd.cyl][imd.head].start_sector) {
212                 myDisk->track[imd.cyl][imd.head].start_sector = sectorMap[i];
213             }
214         }
215         DBG_PRINT((", Start Sector=%d", myDisk->track[imd.cyl][imd.head].start_sector));
216 
217         if(sectorHeadwithFlags & IMD_FLAG_SECT_HEAD_MAP) {
218             if (sim_fread(sectorHeadMap, 1, imd.nsects, myDisk->file) != imd.nsects) {
219                 printf("SIM_IMD: Corrupt file [Sector Head Map]." NLP);
220                 return (SCPE_OPENERR);
221             }
222             DBG_PRINT(("\tSector Head Map: "));
223             for(i=0;i<imd.nsects;i++) {
224                 DBG_PRINT(("%d ", sectorHeadMap[i]));
225             }
226             DBG_PRINT(("" NLP));
227         } else {
228             /* Default Head is physical head for each sector */
229             for(i=0;i<imd.nsects;i++) {
230                 sectorHeadMap[i] = imd.head;
231             };
232         }
233 
234         if(sectorHeadwithFlags & IMD_FLAG_SECT_CYL_MAP) {
235             if (sim_fread(sectorCylMap, 1, imd.nsects, myDisk->file) != imd.nsects) {
236                 printf("SIM_IMD: Corrupt file [Sector Cyl Map]." NLP);
237                 return (SCPE_OPENERR);
238             }
239             DBG_PRINT(("\tSector Cyl Map: "));
240             for(i=0;i<imd.nsects;i++) {
241                 DBG_PRINT(("%d ", sectorCylMap[i]));
242             }
243             DBG_PRINT((NLP));
244         } else {
245             /* Default Cyl Map is physical cylinder for each sector */
246             for(i=0;i<imd.nsects;i++) {
247                 sectorCylMap[i] = imd.cyl;
248             }
249         }
250 
251         DBG_PRINT((NLP "Sector data at offset 0x%08lx" NLP, ftell(myDisk->file)));
252 
253         /* Build the table with location 0 being the start sector. */
254         start_sect = myDisk->track[imd.cyl][imd.head].start_sector;
255 
256         /* Now read each sector */
257         for(i=0;i<imd.nsects;i++) {
258             TotalSectorCount++;
259             DBG_PRINT(("Sector Phys: %d/Logical: %d: %d bytes: ", i, sectorMap[i], sectorSize));
260             sectRecordType = fgetc(myDisk->file);
261             /* AGN Logical head mapping */
262             myDisk->track[imd.cyl][imd.head].logicalHead[i] = sectorHeadMap[i];
263             /* AGN Logical cylinder mapping */
264             myDisk->track[imd.cyl][imd.head].logicalCyl[i] = sectorCylMap[i];
265             switch(sectRecordType) {
266                 case SECT_RECORD_UNAVAILABLE:   /* Data could not be read from the original media */
267                     if (sectorMap[i]-start_sect < MAX_SPT)
268                         myDisk->track[imd.cyl][imd.head].sectorOffsetMap[sectorMap[i]-start_sect] = 0xBADBAD;
269                     else {
270                         printf("SIM_IMD: ERROR: Illegal sector offset %d" NLP, sectorMap[i]-start_sect);
271                         return (SCPE_OPENERR);
272                     }
273                     break;
274                 case SECT_RECORD_NORM:          /* Normal Data */
275                 case SECT_RECORD_NORM_DAM:      /* Normal Data with deleted address mark */
276                 case SECT_RECORD_NORM_ERR:      /* Normal Data with read error */
277                 case SECT_RECORD_NORM_DAM_ERR:  /* Normal Data with deleted address mark with read error */
278 /*                  DBG_PRINT(("Uncompressed Data" NLP)); */
279                     if (sectorMap[i]-start_sect < MAX_SPT) {
280                         myDisk->track[imd.cyl][imd.head].sectorOffsetMap[sectorMap[i]-start_sect] = ftell(myDisk->file);
281                         sim_fseek(myDisk->file, sectorSize, SEEK_CUR);
282                     }
283                     else {
284                         printf("SIM_IMD: ERROR: Illegal sector offset %d" NLP, sectorMap[i]-start_sect);
285                         return (SCPE_OPENERR);
286                     }
287                     break;
288                 case SECT_RECORD_NORM_COMP:     /* Compressed Normal Data */
289                 case SECT_RECORD_NORM_DAM_COMP: /* Compressed Normal Data with deleted address mark */
290                 case SECT_RECORD_NORM_COMP_ERR: /* Compressed Normal Data */
291                 case SECT_RECORD_NORM_DAM_COMP_ERR: /* Compressed Normal Data with deleted address mark */
292                     if (sectorMap[i]-start_sect < MAX_SPT) {
293                         myDisk->track[imd.cyl][imd.head].sectorOffsetMap[sectorMap[i]-start_sect] = ftell(myDisk->file);
294                         myDisk->flags |= FD_FLAG_WRITELOCK; /* Write-protect the disk if any sectors are compressed. */
295 #ifdef VERBOSE_DEBUG
296                         DBG_PRINT(("Compressed Data = 0x%02x" NLP, fgetc(myDisk->file)));
297 #else
298                         fgetc(myDisk->file);
299 #endif
300                     }
301                     else {
302                         printf("SIM_IMD: ERROR: Illegal sector offset %d" NLP, sectorMap[i]-start_sect);
303                         return (SCPE_OPENERR);
304                     }
305                     break;
306                 default:
307                     printf("SIM_IMD: ERROR: unrecognized sector record type %d" NLP, sectRecordType);
308                     return (SCPE_OPENERR);
309                     break;
310             }
311             DBG_PRINT((NLP));
312         }
313 
314         myDisk->ntracks++;
315     } while (!feof(myDisk->file));
316 
317     DBG_PRINT(("Processed %d sectors" NLP, TotalSectorCount));
318 
319 #ifdef VERBOSE_DEBUG
320     for(i=0;i<myDisk->ntracks;i++) {
321         DBG_PRINT(("Track %02d: ", i));
322         for(j=0;j<imd.nsects;j++) {
323             DBG_PRINT(("0x%06x ", myDisk->track[i][0].sectorOffsetMap[j]));
324         }
325         DBG_PRINT((NLP));
326     }
327 #endif
328     if(myDisk->flags & FD_FLAG_WRITELOCK) {
329         printf("Disk write-protected because the image contains compressed sectors. Use IMDU to uncompress." NLP);
330     }
331 
332     return SCPE_OK;
333 }
334 
335 /*
336  * This function closes the IMD image.  After closing, the sector read/write operations are not
337  * possible.
338  *
339  * The IMD file is not actually closed, we leave that to SIMH.
340  */
diskClose(DISK_INFO ** myDisk)341 t_stat diskClose(DISK_INFO **myDisk)
342 {
343     if(*myDisk == NULL)
344         return SCPE_OPENERR;
345     free(*myDisk);
346     *myDisk = NULL;
347     return SCPE_OK;
348 }
349 
350 #define MAX_COMMENT_LEN 256
351 
352 /*
353  * Create an ImageDisk (IMD) file.  This function just creates the comment header, and allows
354  * the user to enter a comment.  After the IMD is created, it must be formatted with a format
355  * program on the simulated operating system, ie CP/M, CDOS, 86-DOS.
356  *
357  * If the IMD file already exists, the user will be given the option of overwriting it.
358  */
diskCreate(FILE * fileref,char * ctlr_comment)359 t_stat diskCreate(FILE *fileref, char *ctlr_comment)
360 {
361     DISK_INFO *myDisk = NULL;
362     char *comment;
363     char *curptr;
364     char *result;
365     uint8 answer;
366     int32 len, remaining;
367 
368     if(fileref == NULL) {
369         return (SCPE_OPENERR);
370     }
371 
372     if(sim_fsize(fileref) != 0) {
373         printf("SIM_IMD: Disk image already has data, do you want to overwrite it? ");
374         answer = getchar();
375 
376         if((answer != 'y') && (answer != 'Y')) {
377             return (SCPE_OPENERR);
378         }
379     }
380 
381     if((curptr = comment = calloc(1, MAX_COMMENT_LEN)) == 0) {
382         printf("Memory allocation failure.\n");
383         return (SCPE_MEM);
384     }
385 
386     printf("SIM_IMD: Enter a comment for this disk.\n"
387            "SIM_IMD: Terminate with a '.' on an otherwise blank line.\n");
388     remaining = MAX_COMMENT_LEN;
389     do {
390         printf("IMD> ");
391         result = fgets(curptr, remaining - 3, stdin);
392         if ((result == NULL) || (strcmp(curptr, ".\n") == 0)) {
393             remaining = 0;
394         } else {
395             len = strlen(curptr) - 1;
396             if (curptr[len] != '\n')
397                 len++;
398             remaining -= len;
399             curptr += len;
400             *curptr++ = 0x0d;
401             *curptr++ = 0x0a;
402         }
403     } while (remaining > 4);
404     *curptr = 0x00;
405 
406     /* rewind to the beginning of the file. */
407     rewind(fileref);
408 
409     /* Erase the contents of the IMD file in case we are overwriting an existing image. */
410 #ifdef _WIN32 /* This might work under UNIX and/or VMS since this POSIX, but I haven't tried it. */
411     _chsize(_fileno(fileref), ftell (fileref));
412 #else
413     if (ftruncate(fileno(fileref), ftell (fileref)) == -1) {
414         printf("SIM_IMD: Error overwriting disk image.\n");
415         return(SCPE_OPENERR);
416     }
417 #endif
418 
419     fprintf(fileref, "IMD SIMH %s %s\n", __DATE__, __TIME__);
420     fputs(comment, fileref);
421     free(comment);
422     fprintf(fileref, "\n\n$Id: sim_imd.c 1999 2008-07-22 04:25:28Z hharte $\n");
423     fprintf(fileref, "%s\n", ctlr_comment);
424     fputc(0x1A, fileref); /* EOF marker for IMD comment. */
425     fflush(fileref);
426 
427     if((myDisk = diskOpen(fileref, 0)) == NULL) {
428         printf("SIM_IMD: Error opening disk for format.\n");
429         return(SCPE_OPENERR);
430     }
431 
432     if(diskFormat(myDisk) != SCPE_OK) {
433         printf("SIM_IMD: error formatting disk.\n");
434     }
435 
436     return diskClose(&myDisk);
437 }
438 
439 
diskFormat(DISK_INFO * myDisk)440 t_stat diskFormat(DISK_INFO *myDisk)
441 {
442     uint8 i;
443     uint8 sector_map[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26};
444     uint32 flags;
445 
446     printf("SIM_IMD: Formatting disk in IBM 3740 SS/SD Format.\n");
447 
448     for(i=0;i<77;i++) {
449         if((trackWrite(myDisk, i, 0, 26, 128, sector_map, IMD_MODE_500K_FM, 0xE5, &flags)) != 0) {
450             printf("SIM_IMD: Error formatting track %d\n", i);
451             return SCPE_IOERR;
452         } else {
453             putchar('.');
454         }
455     }
456 
457     printf("\nSIM_IMD: Format Complete.\n");
458 
459     return SCPE_OK;
460 }
461 
imdGetSides(DISK_INFO * myDisk)462 uint32 imdGetSides(DISK_INFO *myDisk)
463 {
464     if(myDisk != NULL) {
465         return(myDisk->nsides);
466     }
467 
468     return (0);
469 }
470 
imdIsWriteLocked(DISK_INFO * myDisk)471 uint32 imdIsWriteLocked(DISK_INFO *myDisk)
472 {
473     if(myDisk != NULL) {
474         return((myDisk->flags & FD_FLAG_WRITELOCK) ? 1 : 0);
475     }
476 
477     return (0);
478 }
479 
480 /* Check that the given track/sector exists on the disk */
sectSeek(DISK_INFO * myDisk,uint32 Cyl,uint32 Head)481 t_stat sectSeek(DISK_INFO *myDisk,
482              uint32 Cyl,
483              uint32 Head)
484 {
485     if(Cyl >= myDisk->ntracks) {
486         return(SCPE_IOERR);
487     }
488 
489     if(Head >= myDisk->nsides) {
490         return(SCPE_IOERR);
491     }
492 
493     if(myDisk->track[Cyl][Head].nsects == 0) {
494         DBG_PRINT(("%s: invalid track/head" NLP, __FUNCTION__));
495         return(SCPE_IOERR);
496     }
497 
498     return(SCPE_OK);
499 }
500 
501 /* Read a sector from an IMD image. */
sectRead(DISK_INFO * myDisk,uint32 Cyl,uint32 Head,uint32 Sector,uint8 * buf,uint32 buflen,uint32 * flags,uint32 * readlen)502 t_stat sectRead(DISK_INFO *myDisk,
503              uint32 Cyl,
504              uint32 Head,
505              uint32 Sector,
506              uint8 *buf,
507              uint32 buflen,
508              uint32 *flags,
509              uint32 *readlen)
510 {
511     uint32 sectorFileOffset;
512     uint8 sectRecordType;
513     uint8 start_sect;
514     *readlen = 0;
515     *flags = 0;
516 
517     /* Check parameters */
518     if(myDisk == NULL) {
519         *flags |= IMD_DISK_IO_ERROR_GENERAL;
520         return(SCPE_IOERR);
521     }
522 
523     if(sectSeek(myDisk, Cyl, Head) != SCPE_OK) {
524         *flags |= IMD_DISK_IO_ERROR_GENERAL;
525         return(SCPE_IOERR);
526     }
527 
528     if(Sector > myDisk->track[Cyl][Head].nsects) {
529         DBG_PRINT(("%s: invalid sector" NLP, __FUNCTION__));
530         *flags |= IMD_DISK_IO_ERROR_GENERAL;
531         return(SCPE_IOERR);
532     }
533 
534     if(buflen < myDisk->track[Cyl][Head].sectsize) {
535         printf("%s: Reading C:%d/H:%d/S:%d, len=%d: user buffer too short, need %d" NLP, __FUNCTION__, Cyl, Head, Sector, buflen, myDisk->track[Cyl][Head].sectsize);
536         *flags |= IMD_DISK_IO_ERROR_GENERAL;
537         return(SCPE_IOERR);
538     }
539 
540     start_sect = myDisk->track[Cyl][Head].start_sector;
541 
542     sectorFileOffset = myDisk->track[Cyl][Head].sectorOffsetMap[Sector-start_sect];
543 
544     DBG_PRINT(("Reading C:%d/H:%d/S:%d, len=%d, offset=0x%08x" NLP, Cyl, Head, Sector, buflen, sectorFileOffset));
545 
546     sim_fseek(myDisk->file, sectorFileOffset-1, 0);
547 
548     sectRecordType = fgetc(myDisk->file);
549     switch(sectRecordType) {
550         case SECT_RECORD_UNAVAILABLE:   /* Data could not be read from the original media */
551             *flags |= IMD_DISK_IO_ERROR_GENERAL;
552             break;
553         case SECT_RECORD_NORM_ERR:      /* Normal Data with read error */
554         case SECT_RECORD_NORM_DAM_ERR:  /* Normal Data with deleted address mark with read error */
555             *flags |= IMD_DISK_IO_ERROR_CRC;
556         case SECT_RECORD_NORM:          /* Normal Data */
557         case SECT_RECORD_NORM_DAM:      /* Normal Data with deleted address mark */
558 
559 /*          DBG_PRINT(("Uncompressed Data" NLP)); */
560             if (sim_fread(buf, 1, myDisk->track[Cyl][Head].sectsize, myDisk->file) != myDisk->track[Cyl][Head].sectsize) {
561                 printf("SIM_IMD[%s]: sim_fread error for SECT_RECORD_NORM_DAM." NLP, __FUNCTION__);
562             }
563             *readlen = myDisk->track[Cyl][Head].sectsize;
564             break;
565         case SECT_RECORD_NORM_COMP_ERR: /* Compressed Normal Data */
566         case SECT_RECORD_NORM_DAM_COMP_ERR: /* Compressed Normal Data with deleted address mark */
567             *flags |= IMD_DISK_IO_ERROR_CRC;
568         case SECT_RECORD_NORM_COMP:     /* Compressed Normal Data */
569         case SECT_RECORD_NORM_DAM_COMP: /* Compressed Normal Data with deleted address mark */
570 /*          DBG_PRINT(("Compressed Data" NLP)); */
571             memset(buf, fgetc(myDisk->file), myDisk->track[Cyl][Head].sectsize);
572             *readlen = myDisk->track[Cyl][Head].sectsize;
573             *flags |= IMD_DISK_IO_COMPRESSED;
574             break;
575         default:
576             printf("ERROR: unrecognized sector record type %d" NLP, sectRecordType);
577             break;
578     }
579 
580     /* Set flags for deleted address mark. */
581     switch(sectRecordType) {
582         case SECT_RECORD_NORM_DAM:      /* Normal Data with deleted address mark */
583         case SECT_RECORD_NORM_DAM_ERR:  /* Normal Data with deleted address mark with read error */
584         case SECT_RECORD_NORM_DAM_COMP: /* Compressed Normal Data with deleted address mark */
585         case SECT_RECORD_NORM_DAM_COMP_ERR: /* Compressed Normal Data with deleted address mark */
586             *flags |= IMD_DISK_IO_DELETED_ADDR_MARK;
587         default:
588             break;
589     }
590 
591     return(SCPE_OK);
592 }
593 
594 /* Write a sector to an IMD image. */
sectWrite(DISK_INFO * myDisk,uint32 Cyl,uint32 Head,uint32 Sector,uint8 * buf,uint32 buflen,uint32 * flags,uint32 * writelen)595 t_stat sectWrite(DISK_INFO *myDisk,
596               uint32 Cyl,
597               uint32 Head,
598               uint32 Sector,
599               uint8 *buf,
600               uint32 buflen,
601               uint32 *flags,
602               uint32 *writelen)
603 {
604     uint32 sectorFileOffset;
605     uint8 sectRecordType;
606     uint8 start_sect;
607     *writelen = 0;
608 
609     DBG_PRINT(("Writing C:%d/H:%d/S:%d, len=%d" NLP, Cyl, Head, Sector, buflen));
610 
611     /* Check parameters */
612     if(myDisk == NULL) {
613         *flags = IMD_DISK_IO_ERROR_GENERAL;
614         return(SCPE_IOERR);
615     }
616 
617     if(sectSeek(myDisk, Cyl, Head) != 0) {
618         *flags = IMD_DISK_IO_ERROR_GENERAL;
619         return(SCPE_IOERR);
620     }
621 
622     if(Sector > myDisk->track[Cyl][Head].nsects) {
623         DBG_PRINT(("%s: invalid sector" NLP, __FUNCTION__));
624         *flags = IMD_DISK_IO_ERROR_GENERAL;
625         return(SCPE_IOERR);
626     }
627 
628     if(myDisk->flags & FD_FLAG_WRITELOCK) {
629         printf("Disk write-protected because the image contains compressed sectors. Use IMDU to uncompress." NLP);
630         *flags = IMD_DISK_IO_ERROR_WPROT;
631         return(SCPE_IOERR);
632     }
633 
634     if(buflen < myDisk->track[Cyl][Head].sectsize) {
635         printf("%s: user buffer too short [buflen %i < sectsize %i]" NLP,
636             __FUNCTION__, buflen, myDisk->track[Cyl][Head].sectsize);
637         *flags = IMD_DISK_IO_ERROR_GENERAL;
638         return(SCPE_IOERR);
639     }
640 
641     start_sect = myDisk->track[Cyl][Head].start_sector;
642 
643     sectorFileOffset = myDisk->track[Cyl][Head].sectorOffsetMap[Sector-start_sect];
644 
645     sim_fseek(myDisk->file, sectorFileOffset-1, 0);
646 
647     if (*flags & IMD_DISK_IO_ERROR_GENERAL) {
648         sectRecordType = SECT_RECORD_UNAVAILABLE;
649     } else if (*flags & IMD_DISK_IO_ERROR_CRC) {
650         if (*flags & IMD_DISK_IO_DELETED_ADDR_MARK)
651             sectRecordType = SECT_RECORD_NORM_DAM_ERR;
652         else
653             sectRecordType = SECT_RECORD_NORM_ERR;
654     } else {
655         if (*flags & IMD_DISK_IO_DELETED_ADDR_MARK)
656             sectRecordType = SECT_RECORD_NORM_DAM;
657         else
658         sectRecordType = SECT_RECORD_NORM;
659     }
660 
661     fputc(sectRecordType, myDisk->file);
662     sim_fwrite(buf, 1, myDisk->track[Cyl][Head].sectsize, myDisk->file);
663     *writelen = myDisk->track[Cyl][Head].sectsize;
664 
665     return(SCPE_OK);
666 }
667 
668 /* Format an entire track.  The new track to be formatted must be after any existing tracks on
669  * the disk.
670  *
671  * This routine should be enhanced to re-format an existing track to the same format (this
672  * does not involve changing the disk image size.)
673  *
674  * Any existing data on the disk image will be destroyed when Track 0, Head 0 is formatted.
675  * At that time, the IMD file is truncated.  So for the trackWrite to be used to sucessfully
676  * format a disk image, then format program must format tracks starting with Cyl 0, Head 0,
677  * and proceed sequentially through all tracks/heads on the disk.
678  *
679  * Format programs that are known to work include:
680  * Cromemco CDOS "INIT.COM"
681  * ADC Super-Six (CP/M-80) "FMT8.COM"
682  * 86-DOS "INIT.COM"
683  *
684  */
trackWrite(DISK_INFO * myDisk,uint32 Cyl,uint32 Head,uint32 numSectors,uint32 sectorLen,uint8 * sectorMap,uint8 mode,uint8 fillbyte,uint32 * flags)685 t_stat trackWrite(DISK_INFO *myDisk,
686                uint32 Cyl,
687                uint32 Head,
688                uint32 numSectors,
689                uint32 sectorLen,
690                uint8 *sectorMap,
691                uint8 mode,
692                uint8 fillbyte,
693                uint32 *flags)
694 {
695     FILE *fileref;
696     IMD_HEADER track_header;
697     uint8 *sectorData;
698     unsigned long i;
699     unsigned long dataLen;
700 
701     *flags = 0;
702 
703     /* Check parameters */
704     if(myDisk == NULL) {
705         *flags |= IMD_DISK_IO_ERROR_GENERAL;
706         return(SCPE_IOERR);
707     }
708 
709     if(myDisk->flags & FD_FLAG_WRITELOCK) {
710         printf("Disk write-protected, cannot format tracks." NLP);
711         *flags |= IMD_DISK_IO_ERROR_WPROT;
712         return(SCPE_IOERR);
713     }
714 
715     fileref = myDisk->file;
716 
717     DBG_PRINT(("Formatting C:%d/H:%d/N:%d, len=%d, Fill=0x%02x" NLP, Cyl, Head, numSectors, sectorLen, fillbyte));
718 
719     /* Truncate the IMD file when formatting Cyl 0, Head 0 */
720     if((Cyl == 0) && (Head == 0))
721     {
722         /* Skip over IMD comment field. */
723         commentParse(myDisk, NULL, 0);
724 
725         /* Truncate the IMD file after the comment field. */
726 #ifdef _WIN32 /* This might work under UNIX and/or VMS since this POSIX, but I haven't tried it. */
727         _chsize(_fileno(fileref), ftell (fileref));
728 #else
729         if (ftruncate(fileno(fileref), ftell (fileref)) == -1) {
730             printf("Disk truncation failed." NLP);
731             *flags |= IMD_DISK_IO_ERROR_GENERAL;
732             return(SCPE_IOERR);
733         }
734 #endif
735         /* Flush and re-parse the IMD file. */
736         fflush(fileref);
737         diskParse(myDisk, 0);
738     }
739 
740     /* Check to make sure the Cyl / Head is not already formatted. */
741     if(sectSeek(myDisk, Cyl, Head) == 0) {
742         printf("SIM_IMD: ERROR: Not Formatting C:%d/H:%d, track already exists." NLP, Cyl, Head);
743         *flags |= IMD_DISK_IO_ERROR_GENERAL;
744         return(SCPE_IOERR);
745     }
746 
747     track_header.mode = mode;
748     track_header.cyl = Cyl;
749     track_header.head = Head;
750     track_header.nsects = numSectors;
751     track_header.sectsize = sectorLen;
752 
753     /* Forward to end of the file, write track header and sector map. */
754     sim_fseek(myDisk->file, 0, SEEK_END);
755     sim_fwrite(&track_header, 1, sizeof(IMD_HEADER), fileref);
756     sim_fwrite(sectorMap, 1, numSectors, fileref);
757 
758     /* Compute data length, and fill a sector buffer with the
759      * sector record type as the first byte, and fill the sector
760      * data with the fillbyte.
761      */
762     dataLen = (128 << sectorLen)+1;
763     sectorData = malloc(dataLen);
764     memset(sectorData, fillbyte, dataLen);
765     sectorData[0] = SECT_RECORD_NORM;
766 
767     /* For each sector on the track, write the record type and sector data. */
768     for(i=0;i<numSectors;i++) {
769         sim_fwrite(sectorData, 1, dataLen, fileref);
770     }
771 
772     /* Flush the file, and free the sector buffer. */
773     fflush(fileref);
774     free(sectorData);
775 
776     /* Now that the disk track/sector layout has been modified, re-parse the disk image. */
777     diskParse(myDisk, 0);
778 
779     return(SCPE_OK);
780 }
781 
782 /* Utility function to set the image type for a unit to the correct value.
783  * Prints an error message in case a CPT image is presented and returns
784  * SCPE_OPENERR in this case. Otherwise the return value is SCPE_OK
785  */
assignDiskType(UNIT * uptr)786 t_stat assignDiskType(UNIT *uptr) {
787     t_stat result = SCPE_OK;
788     char header[4];
789     if (fgets(header, 4, uptr->fileref) == NULL)
790         uptr->u3 = IMAGE_TYPE_DSK;
791     else if (strncmp(header, "IMD", 3) == 0)
792         uptr->u3 = IMAGE_TYPE_IMD;
793     else if(strncmp(header, "CPT", 3) == 0) {
794         printf("CPT images not yet supported.\n");
795         uptr->u3 = IMAGE_TYPE_CPT;
796         result = SCPE_OPENERR;
797     }
798     else
799         uptr->u3 = IMAGE_TYPE_DSK;
800     return result;
801 }
802 
803