1 /***************************************************************************
2  *                                                                         *
3  *    LIBDSK: General floppy and diskimage access library                  *
4  *    Copyright (C) 2001-2, 2016  John Elliott <seasip.webmaster@gmail.com>*
5  *                                                                         *
6  *    This library is free software; you can redistribute it and/or        *
7  *    modify it under the terms of the GNU Library General Public          *
8  *    License as published by the Free Software Foundation; either         *
9  *    version 2 of the License, or (at your option) any later version.     *
10  *                                                                         *
11  *    This library is distributed in the hope that it will be useful,      *
12  *    but WITHOUT ANY WARRANTY; without even the implied warranty of       *
13  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    *
14  *    Library General Public License for more details.                     *
15  *                                                                         *
16  *    You should have received a copy of the GNU Library General Public    *
17  *    License along with this library; if not, write to the Free           *
18  *    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,      *
19  *    MA 02111-1307, USA                                                   *
20  *                                                                         *
21  ***************************************************************************/
22 
23 /* This driver works with the ImageDisk "IMD" format. This compresses
24  * individual sectors using RLE, so we have to load the whole image into
25  * memory and work on it as an array of sectors.
26  */
27 
28 #include <stdio.h>
29 #include "libdsk.h"
30 #include "drvi.h"
31 #include "drvimd.h"
32 #ifdef HAVE_TIME_H
33 #include <time.h>
34 #endif
35 
36 #define ST_NODATA    0	/* ID but no data */
37 #define ST_NORMAL    1	/* Normal data, uncompressed */
38 #define ST_CNORMAL   2	/* Normal data, compressed */
39 #define ST_DELETED   3	/* Deleted data, uncompressed */
40 #define ST_CDELETED  4	/* Deleted data, compressed */
41 #define ST_DATAERR   5	/* Normal data, uncompressed, data error on read */
42 #define ST_CDATAERR  6	/* Normal data, compressed, data error on read */
43 #define ST_DELERR    7	/* Deleted data, uncompressed, data error on read */
44 #define ST_CDELERR   8	/* Deleted data, compressed, data error on read */
45 
46 /* This struct contains function pointers to the driver's functions, and the
47  * size of its DSK_DRIVER subclass */
48 
49 DRV_CLASS dc_imd =
50 {
51 	sizeof(IMD_DSK_DRIVER),
52 	"imd",
53 	"IMD file driver",
54 	imd_open,	/* open */
55 	imd_creat,	/* create new */
56 	imd_close,	/* close */
57 	imd_read,	/* read sector, working from physical address */
58 	imd_write,	/* write sector, working from physical address */
59 	imd_format,	/* format track, physical */
60 	imd_getgeom,	/* get geometry */
61 	imd_secid,	/* sector ID */
62 	imd_xseek,	/* seek to track */
63 	imd_status,	/* drive status */
64 	imd_xread,	/* Extended sector read */
65 	imd_xwrite,	/* Extended sector write */
66 };
67 
68 
imd_alloc_track(int sectors)69 static IMD_TRACK *imd_alloc_track(int sectors)
70 {
71 	int n;
72 
73 	IMD_TRACK *t = dsk_malloc(sizeof(IMD_TRACK) +
74 				sectors * sizeof(IMD_SECTOR *));
75 
76 	if (!t) return NULL;
77 	for (n = 0; n < sectors; n++)
78 	{
79 		t->imdt_sec[n] = NULL;
80 	}
81 	return t;
82 }
83 
imd_free_track(IMD_TRACK * t)84 static void imd_free_track(IMD_TRACK *t)
85 {
86 	int n;
87 
88 	if (!t) return;
89 
90 	for (n = 0; n < t->imdt_sectors; n++)
91 	{
92 		if (t->imdt_sec[n]) dsk_free(t->imdt_sec[n]);
93 	}
94 	dsk_free(t);
95 }
96 
97 /* For reading strings from the file: Read up to the next occurrence of
98  * either of the specified characters c1 / c2, and return how many bytes
99  * that is (including the terminator).
100  *
101  * If only interested in one terminating character, pass c2 = c1
102  */
imd_readto(FILE * fp,char c1,char c2,int * count,int * termch)103 static dsk_err_t imd_readto(FILE *fp, char c1, char c2, int *count, int *termch)
104 {
105 	int ch;
106 	long pos = ftell(fp);
107 	int cnt = 0;
108 
109 	*termch = EOF;
110 	if (pos < 0)
111 	{
112 		return DSK_ERR_SYSERR;
113 	}
114 	while (1)
115 	{
116 		++cnt;
117 		ch = fgetc(fp);
118 		if (ch == EOF || ch == c1 || ch == c2)
119 		{
120 			*termch = ch;
121 			break;
122 		}
123 	}
124 	if (fseek(fp, pos, SEEK_SET))
125 	{
126 		return DSK_ERR_SYSERR;
127 	}
128 	*count = cnt;
129 	return DSK_ERR_OK;
130 }
131 
132 /* Ensure there are always at least 'count' + 1 tracks in the
133  * self->imd_tracks array */
imd_ensure_trackcount(IMD_DSK_DRIVER * self,dsk_ltrack_t trk)134 static dsk_err_t imd_ensure_trackcount(IMD_DSK_DRIVER *self, dsk_ltrack_t trk)
135 {
136 	IMD_TRACK **ptr;
137 	unsigned n, newc;
138 
139 	if (trk < self->imd_ntracks) return DSK_ERR_OK;
140 
141 	/* Need to malloc some more */
142 	if (self->imd_ntracks == 0)
143 	{
144 		newc = 20;
145 	}
146 	else
147 	{
148 		newc = 2 * self->imd_ntracks;
149 	}
150 	ptr = dsk_malloc(newc * sizeof(IMD_TRACK *));
151 	if (!ptr) return DSK_ERR_NOMEM;
152 
153 	/* Copy existing array (if any) */
154 	for (n = 0; n < self->imd_ntracks; n++)
155 		ptr[n] = self->imd_tracks[n];
156 
157 	/* Blank additional pointers */
158 	for (n = self->imd_ntracks; n < newc; n++)
159 		ptr[n] = NULL;
160 
161 	dsk_free(self->imd_tracks);
162 	self->imd_tracks = ptr;
163 	self->imd_ntracks = newc;
164 	return DSK_ERR_OK;
165 }
166 
167 
168 
imd_load_track(IMD_DSK_DRIVER * self,dsk_ltrack_t count,FILE * fp)169 static dsk_err_t imd_load_track(IMD_DSK_DRIVER *self, dsk_ltrack_t count,
170 	FILE *fp)
171 {
172 	/* Start by loading the track header: Fixed */
173 	IMD_TRACK tmp, *trkh;
174 	IMD_SECTOR *tmpsec;
175 	dsk_err_t err;
176 	int n, c;
177 
178 /*	printf("Loading track %ld, offset %ld \n", count, ftell(fp)); */
179 	if (fread(&tmp.imdt_mode, 1, 4, fp) < 4)
180 	{
181 		return DSK_ERR_OVERRUN;	/* EOF */
182 	}
183 	c = fgetc(fp);
184 	if (c == EOF)
185 	{
186 		return DSK_ERR_OVERRUN;	/* EOF */
187 	}
188 	if (c == 0xFF)	tmp.imdt_seclen = 0xFFFF;
189 	else		tmp.imdt_seclen = (128 << c);
190 /*	printf("Mode %d Cyl %d Head %d Secs %d Size %d\n",
191 		tmp.imdt_mode, tmp.imdt_cylinder, tmp.imdt_head,
192 		tmp.imdt_sectors, tmp.imdt_seclen); */
193 
194 	err = imd_ensure_trackcount(self, count);
195 	if (err) return err;
196 
197 	/* Allocate the full variable-size structure */
198 	trkh = imd_alloc_track(tmp.imdt_sectors);
199 	if (!trkh)
200 	{
201 		return DSK_ERR_NOMEM;
202 	}
203 	/* Copy fixed parts */
204 	memcpy(trkh, &tmp, sizeof(tmp));
205 	/* [1.4.2] But not the sector buffer (so we don't double-free) */
206 	trkh->imdt_sec[0] = NULL;
207 
208 	/* Create a temporary array of sector headers */
209 	tmpsec = dsk_malloc(tmp.imdt_sectors * sizeof(IMD_SECTOR));
210 	if (!tmpsec)
211 	{
212 		imd_free_track(trkh);
213 		return DSK_ERR_NOMEM;
214 	}
215 	/* Populate temporary sector headers */
216 	for (n = 0; n < tmp.imdt_sectors; n++)
217 	{
218 		tmpsec[n].imds_cylinder = tmp.imdt_cylinder;
219 		tmpsec[n].imds_head     = tmp.imdt_head & 0x3F;
220 		tmpsec[n].imds_sector   = 0;
221 		tmpsec[n].imds_status   = ST_NODATA;
222 		tmpsec[n].imds_datalen  = 0;
223 		tmpsec[n].imds_seclen   = tmp.imdt_seclen;
224 	}
225 	/* Load sector IDs */
226 	for (n = 0; n < tmp.imdt_sectors; n++)
227 	{
228 		c = fgetc(fp);
229 		if (c == EOF)
230 		{
231 			dsk_free(tmpsec);
232 			imd_free_track(trkh);
233 			return DSK_ERR_SYSERR;
234 		}
235 
236 		tmpsec[n].imds_sector = c;
237 	}
238 	/* Load sector cylinder map (if present) */
239 	if (tmp.imdt_head & 0x80)
240 	{
241 		for (n = 0; n < tmp.imdt_sectors; n++)
242 		{
243 			c = fgetc(fp);
244 			if (c == EOF)
245 			{
246 				dsk_free(tmpsec);
247 				imd_free_track(trkh);
248 				return DSK_ERR_SYSERR;
249 			}
250 
251 			tmpsec[n].imds_cylinder = c;
252 		}
253 	}
254 	/* Load sector head map (if present) */
255 	if (tmp.imdt_head & 0x40)
256 	{
257 		for (n = 0; n < tmp.imdt_sectors; n++)
258 		{
259 			c = fgetc(fp);
260 			if (c == EOF)
261 			{
262 				dsk_free(tmpsec);
263 				imd_free_track(trkh);
264 				return DSK_ERR_SYSERR;
265 			}
266 
267 			tmpsec[n].imds_head = c;
268 		}
269 	}
270 	/* Load sector lengths (if present) */
271 	if (tmp.imdt_seclen == 0xFFFF)
272 	{
273 		int l, h;
274 
275 		for (n = 0; n < tmp.imdt_sectors; n++)
276 		{
277 			l = fgetc(fp);
278 			h = fgetc(fp);
279 			if (l == EOF || h == EOF)
280 			{
281 				dsk_free(tmpsec);
282 				imd_free_track(trkh);
283 				return DSK_ERR_SYSERR;
284 			}
285 			tmpsec[n].imds_seclen = (l & 0xFF) | ((h << 8) & 0xFF00);
286 		}
287 	}
288 
289 
290 	for (n = 0; n < tmp.imdt_sectors; n++)
291 	{
292 		/* Get status for sector */
293 		c = fgetc(fp);
294 		if (c == EOF)
295 		{
296 			dsk_free(tmpsec);
297 			imd_free_track(trkh);
298 			return DSK_ERR_SYSERR;
299 		}
300 		tmpsec[n].imds_status = c;
301 		switch(tmpsec[n].imds_status)
302 		{
303 			default:
304 #ifndef WIN16
305 				fprintf(stderr, "Unsupported IMD status "
306 					"0x%02x\n", tmpsec[n].imds_status);
307 #endif
308 				dsk_free(tmpsec);
309 				imd_free_track(trkh);
310 				return DSK_ERR_NOTME;
311 			/* ID, no data */
312 			case ST_NODATA: tmpsec[n].imds_datalen = 0; break;
313 			/* Uncompressed */
314 			case ST_NORMAL:
315 			case ST_DELETED:
316 			case ST_DATAERR:
317 			case ST_DELERR: tmpsec[n].imds_datalen =
318 						tmpsec[n].imds_seclen; break;
319 			/* Compressed */
320 			case ST_CNORMAL:
321 			case ST_CDELETED:
322 			case ST_CDATAERR:
323 			case ST_CDELERR: tmpsec[n].imds_datalen = 1; break;
324 		}
325 		trkh->imdt_sec[n] = dsk_malloc(sizeof(IMD_SECTOR) +
326 						tmpsec[n].imds_datalen);
327 		if (!trkh->imdt_sec[n])
328 		{
329 			dsk_free(tmpsec);
330 			imd_free_track(trkh);
331 			return DSK_ERR_NOMEM;
332 		}
333 		memcpy(trkh->imdt_sec[n], &tmpsec[n], sizeof(IMD_SECTOR));
334 
335 		if (tmpsec[n].imds_datalen)
336 		{
337 			if (fread(trkh->imdt_sec[n]->imds_data, 1,
338 				tmpsec[n].imds_datalen, fp) <
339 					tmpsec[n].imds_datalen)
340 			{
341 				dsk_free(tmpsec);
342 				imd_free_track(trkh);
343 				return DSK_ERR_SYSERR;
344 			}
345 		}
346 	}
347 	self->imd_tracks[count] = trkh;
348 	dsk_free(tmpsec);
349 //	for (n = 0; n < tmp.imdt_sectors; n++)
350 //	{
351 //		printf("c=%d h=%d s=%d len=%d status=%d datalen=%d\n",
352 //			tmpsec[n].imds_cylinder, tmpsec[n].imds_head,
353 //			tmpsec[n].imds_sector, tmp.imdt_secsize,
354 //			tmpsec[n].imds_status, tmpsec[n].imds_len);
355 //	}
356 	return DSK_ERR_OK;
357 }
358 
359 
360 
imd_open(DSK_DRIVER * self,const char * filename)361 dsk_err_t imd_open(DSK_DRIVER *self, const char *filename)
362 {
363 	FILE *fp;
364 	IMD_DSK_DRIVER *imdself;
365 	dsk_err_t err;
366 	int ccmt, n;
367 	dsk_ltrack_t count = 0;
368 	char *comment;
369 	int termch;
370 
371 	/* Sanity check: Is this meant for our driver? */
372 	if (self->dr_class != &dc_imd) return DSK_ERR_BADPTR;
373 	imdself = (IMD_DSK_DRIVER *)self;
374 
375 	fp = fopen(filename, "r+b");
376 	if (!fp)
377 	{
378 		imdself->imd_readonly = 1;
379 		fp = fopen(filename, "rb");
380 	}
381 	if (!fp) return DSK_ERR_NOTME;
382 
383 	/* Try to check the magic number. Read the first line, which
384  	 * may terminate with '\n' if a comment follows, or 0x1A
385 	 * otherwise. */
386 	err = imd_readto(fp, '\n', 0x1A, &ccmt, &termch);
387 	if (err)
388 	{
389 		fclose(fp);
390 		return DSK_ERR_NOTME;
391 	}
392 	comment = dsk_malloc(1 + ccmt);
393 	if (!comment)
394 	{
395 		fclose(fp);
396 		return DSK_ERR_NOTME;
397 	}
398 	if ((int)fread(comment, 1, ccmt, fp) < ccmt)
399 	{
400 		fclose(fp);
401 		return DSK_ERR_SYSERR;
402 	}
403 	comment[ccmt] = 0;
404 
405 /*	printf("Header='%s' pos=%ld\n", comment, ftell(fp)); */
406 
407 	/* IMD signature is 4 bytes magic, then the rest of the line is
408 	 * freeform (but probably includes a date stamp) */
409 	if (memcmp(comment, "IMD ", 4))
410 	{
411 		dsk_free(comment);
412 		fclose(fp);
413 		return DSK_ERR_NOTME;
414 	}
415 	dsk_free(comment);
416 
417 	/* If the header wasn't terminated by 0x1A, then a comment
418 	 * follows. */
419 	if (termch != 0x1A)
420 	{
421 		err = imd_readto(fp, 0x1A, 0x1A, &ccmt, &termch);
422 		if (err)
423 		{
424 			fclose(fp);
425 			return DSK_ERR_NOTME;
426 		}
427 		comment = dsk_malloc(1 + ccmt);
428 		if (!comment)
429 		{
430 			fclose(fp);
431 			return DSK_ERR_NOMEM;
432 		}
433 		if ((int)fread(comment, 1, ccmt, fp) < ccmt)
434 		{
435 			fclose(fp);
436 			return DSK_ERR_SYSERR;
437 		}
438 		comment[ccmt - 1] = 0;
439 		dsk_set_comment(self, comment);
440 		dsk_free(comment);
441 	}
442 	imdself->imd_dirty = 0;
443 	imdself->imd_sec = 0;
444 	/* Keep a copy of the filename; when writing back, we will need it */
445 	imdself->imd_filename = dsk_malloc(1 + strlen(filename));
446 	if (!imdself->imd_filename) return DSK_ERR_NOMEM;
447 	strcpy(imdself->imd_filename, filename);
448 
449 	/* And now we're onto the tracks */
450 	ccmt = 0;
451 	dsk_report("Loading IMD file into memory");
452 
453 	err = DSK_ERR_OK;
454 
455 	while (!feof(fp))
456 	{
457 		err = imd_load_track(imdself, count, fp);
458 		if (err == DSK_ERR_OVERRUN) 	/* EOF */
459 		{
460 			dsk_report_end();
461 			return DSK_ERR_OK;
462 		}
463 		else if (err)
464 		{
465 			dsk_free(imdself->imd_filename);
466 			for (n = 0; n < (int)(imdself->imd_ntracks); n++)
467 			{
468 				imd_free_track(imdself->imd_tracks[n]);
469 			}
470 			dsk_report_end();
471 			return err;
472 		}
473 		++count;
474 	}
475 	/* Should never get here! */
476 	dsk_report_end();
477 	return DSK_ERR_OK;
478 }
479 
480 
imd_creat(DSK_DRIVER * self,const char * filename)481 dsk_err_t imd_creat(DSK_DRIVER *self, const char *filename)
482 {
483 	IMD_DSK_DRIVER *imdself;
484 	FILE *fp;
485 
486 	/* Sanity check: Is this meant for our driver? */
487 	if (self->dr_class != &dc_imd) return DSK_ERR_BADPTR;
488 	imdself = (IMD_DSK_DRIVER *)self;
489 
490 	/* See if the file can be created. But don't hold it open. */
491 	fp = fopen(filename, "wb");
492 	imdself->imd_readonly = 0;
493 	if (!fp) return DSK_ERR_SYSERR;
494 	fclose(fp);
495 	imdself->imd_dirty = 1;
496 
497 	/* Keep a copy of the filename, for writing back */
498 	imdself->imd_filename = dsk_malloc(1 + strlen(filename));
499 	if (!imdself->imd_filename) return DSK_ERR_NOMEM;
500 	strcpy(imdself->imd_filename, filename);
501 
502 	imdself->imd_ntracks = 0;
503 	imdself->imd_tracks = NULL;
504 
505 	return DSK_ERR_OK;
506 }
507 
compare_tracks(const void * a,const void * b)508 static int compare_tracks(const void *a, const void *b)
509 {
510 	const IMD_TRACK *ta = *(const IMD_TRACK **)a;
511 	const IMD_TRACK *tb = *(const IMD_TRACK **)b;
512 
513 	/* Sort by cylinder and head, with nulls at the end */
514 	if (ta == NULL && tb == NULL) return 0;
515 	if (ta == NULL) return 1;
516 	if (tb == NULL) return -1;
517 
518 	if (ta->imdt_cylinder != tb->imdt_cylinder)
519 		return ta->imdt_cylinder - tb->imdt_cylinder;
520 	return (ta->imdt_head & 0x3F) - (tb->imdt_head & 0x3F);
521 }
522 
imd_save_track(IMD_DSK_DRIVER * self,IMD_TRACK * trk,FILE * fp)523 static dsk_err_t imd_save_track(IMD_DSK_DRIVER *self, IMD_TRACK *trk, FILE *fp)
524 {
525 	int nsec;
526 	unsigned char secsize = dsk_get_psh(trk->imdt_seclen);
527 	IMD_SECTOR *sec;
528 
529 	/* See if we need to write cylinder / head maps */
530 	trk->imdt_head &= 0x3F;
531 
532 	for (nsec = 0; nsec < trk->imdt_sectors; nsec++)
533 	{
534 		sec = trk->imdt_sec[nsec];
535 		if (sec->imds_cylinder != trk->imdt_cylinder)
536 			trk->imdt_head |= 0x80;
537 		if (sec->imds_head != (trk->imdt_head & 0x3F))
538 			trk->imdt_head |= 0x40;
539 		if (sec->imds_seclen != trk->imdt_seclen)
540 			secsize = 0xFF;
541 	}
542 
543 
544 	/* Write fixed part of header */
545 	if (fwrite(&trk->imdt_mode, 1, 4, fp) < 4
546 	||  fputc(secsize, fp) == EOF)
547 	{
548 		return DSK_ERR_SYSERR;
549 	}
550 
551 	/* Write sector IDs */
552 	for (nsec = 0; nsec < trk->imdt_sectors; nsec++)
553 	{
554 		sec = trk->imdt_sec[nsec];
555 		if (fputc(sec->imds_sector, fp) == EOF)
556 			return DSK_ERR_SYSERR;
557 	}
558 	/* Write cylinder IDs, if necessary */
559 	if (trk->imdt_head & 0x80)
560 	{
561 		for (nsec = 0; nsec < trk->imdt_sectors; nsec++)
562 		{
563 			sec = trk->imdt_sec[nsec];
564 			if (fputc(sec->imds_cylinder, fp) == EOF)
565 				return DSK_ERR_SYSERR;
566 		}
567 	}
568 	/* Write head IDs, if necessary */
569 	if (trk->imdt_head & 0x40)
570 	{
571 		for (nsec = 0; nsec < trk->imdt_sectors; nsec++)
572 		{
573 			sec = trk->imdt_sec[nsec];
574 			if (fputc(sec->imds_head, fp) == EOF)
575 				return DSK_ERR_SYSERR;
576 		}
577 	}
578 	/* Write sector sizes, if necessary */
579 	if (secsize == 0xFF)
580 	{
581 		for (nsec = 0; nsec < trk->imdt_sectors; nsec++)
582 		{
583 			sec = trk->imdt_sec[nsec];
584 			if (fputc(sec->imds_seclen & 0xFF, fp) == EOF)
585 				return DSK_ERR_SYSERR;
586 			if (fputc(sec->imds_seclen >> 8, fp) == EOF)
587 				return DSK_ERR_SYSERR;
588 		}
589 	}
590 
591 	/* Write sector data */
592 	for (nsec = 0; nsec < trk->imdt_sectors; nsec++)
593 	{
594 		sec = trk->imdt_sec[nsec];
595 		if (fputc(sec->imds_status, fp) == EOF)
596 			return DSK_ERR_SYSERR;
597 		if (sec->imds_status)
598 		{
599 			if (fwrite(sec->imds_data, 1, sec->imds_datalen, fp) <
600 					sec->imds_datalen)
601 				return DSK_ERR_SYSERR;
602 		}
603 	}
604 	return DSK_ERR_OK;
605 }
606 
607 
608 
imd_close(DSK_DRIVER * self)609 dsk_err_t imd_close(DSK_DRIVER *self)
610 {
611 	IMD_DSK_DRIVER *imdself;
612 	dsk_err_t err = DSK_ERR_OK;
613 	dsk_ltrack_t trk;
614 	char buf[128];
615 	FILE *fp;
616 
617 	if (self->dr_class != &dc_imd) return DSK_ERR_BADPTR;
618 	imdself = (IMD_DSK_DRIVER *)self;
619 
620 	if (imdself->imd_filename && (imdself->imd_dirty))
621 	{
622 /* When writing back to a IMD, create the file from scratch. We might as well
623  * sort the tracks, too.*/
624 
625 		qsort(imdself->imd_tracks, imdself->imd_ntracks,
626 			sizeof(IMD_TRACK *), compare_tracks);
627 
628 		fp = fopen(imdself->imd_filename, "wb");
629 		if (!fp) err = DSK_ERR_SYSERR;
630 		else
631 		{
632 			time_t t;
633 			struct tm *ptm;
634 			char *comment;
635 
636 			time (&t);
637 			ptm = localtime(&t);
638 
639 			if (dsk_get_comment(self, &comment) != DSK_ERR_OK ||
640 				comment == NULL)
641 			{
642 				comment = "\r\n";
643 			}
644 			dsk_report("Writing IMD file");
645 /* The spec seems to imply the header must read "IMD 1.18: datestamp" but
646  * TD02IMD writes a completely different header... */
647 			sprintf(buf, "IMD LibDsk %s: %02d/%02d/%04d "
648 					"%02d:%02d:%02d\r\n",
649 				LIBDSK_VERSION,
650 				ptm->tm_mday, ptm->tm_mon + 1,
651 				ptm->tm_year + 1900,
652 				ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
653 
654 			if (fwrite(buf, 1, strlen(buf), fp) < strlen(buf))
655 			{
656 				err = DSK_ERR_SYSERR;
657 			}
658 			/* Write comment */
659 			else if (fwrite(comment, 1, strlen(comment), fp) < strlen(comment))
660 			{
661 				err = DSK_ERR_SYSERR;
662 			}
663 			else if (fputc(0x1A, fp) == EOF)
664 			{
665 				err = DSK_ERR_SYSERR;
666 			}
667 			else for (trk = 0; trk < imdself->imd_ntracks; trk++)
668 			{
669 				if (imdself->imd_tracks[trk])
670 				{
671 					err = imd_save_track(imdself,
672 						imdself->imd_tracks[trk], fp);
673 				}
674 				if (err) break;
675 			}
676 			fclose(fp);
677 			dsk_report_end();
678 		}
679 	}
680 /* Free track buffers if we have them */
681 	if (imdself->imd_tracks)
682 	{
683 		unsigned int n;
684 		for (n = 0; n < imdself->imd_ntracks; n++)
685 		{
686 			imd_free_track(imdself->imd_tracks[n]);
687 		}
688 		dsk_free(imdself->imd_tracks);
689 		imdself->imd_tracks = NULL;
690 		imdself->imd_ntracks = 0;
691 	}
692 	if (imdself->imd_filename)
693 	{
694 		dsk_free(imdself->imd_filename);
695 		imdself->imd_filename = NULL;
696 	}
697 	return err;
698 }
699 
encode_mode(const DSK_GEOMETRY * geom)700 static int encode_mode(const DSK_GEOMETRY *geom)
701 {
702 	int fm = ((geom->dg_fm & RECMODE_MASK) == RECMODE_FM);
703 	switch (geom->dg_datarate)
704 	{
705 		case RATE_SD:	return fm ? 2 : 5;
706 		case RATE_DD:	return fm ? 1 : 4;
707 		case RATE_HD:	return fm ? 0 : 3;
708 		case RATE_ED:	/* Not supported by IMD format, so
709 				 * we'll just have to improvise */
710 				return fm ? 6 : 9;
711 	}
712 	return -1;
713 }
714 
715 /* In our internal array, locate the track with the specified cylinder
716  * and head (if it's there) */
imd_seektrack(DSK_DRIVER * self,const DSK_GEOMETRY * geom,dsk_pcyl_t cylinder,dsk_phead_t head,int * result)717 static dsk_err_t imd_seektrack(DSK_DRIVER *self, const DSK_GEOMETRY *geom,
718                       dsk_pcyl_t cylinder, dsk_phead_t head, int *result)
719 {
720 	IMD_DSK_DRIVER *imdself;
721 	int track;
722 	int mode = encode_mode(geom);
723 
724 	if (!self || !geom || self->dr_class != &dc_imd) return DSK_ERR_BADPTR;
725 	imdself = (IMD_DSK_DRIVER *)self;
726 
727 	if (!imdself->imd_filename) return DSK_ERR_NOTRDY;
728 
729 	for (track = 0; track < (int)(imdself->imd_ntracks); track++)
730 	{
731 		if (imdself->imd_tracks[track] == NULL) continue;
732 
733 		if ( imdself->imd_tracks[track]->imdt_cylinder == cylinder &&
734 		    (imdself->imd_tracks[track]->imdt_head & 0x3F) == (int)head &&
735 		     imdself->imd_tracks[track]->imdt_mode == mode)
736 		{
737 			if (result) *result = track;
738 			return DSK_ERR_OK;
739 		}
740 	}
741 	return DSK_ERR_SEEKFAIL;
742 }
743 
isdeleted(int status)744 static int isdeleted(int status)
745 {
746 	return (status == ST_DELETED || status == ST_CDELETED ||
747 		status == ST_DELERR  || status == ST_CDELERR);
748 }
749 
iscompressed(int status)750 static int iscompressed(int status)
751 {
752 	return (status == ST_CNORMAL  || status == ST_CDELETED ||
753 		status == ST_CDATAERR || status == ST_CDELERR);
754 }
755 
756 
imd_find_sector(IMD_DSK_DRIVER * self,const DSK_GEOMETRY * geom,dsk_pcyl_t cylinder,dsk_phead_t head,dsk_psect_t sector,dsk_pcyl_t cyl_expected,dsk_phead_t head_expected,int * deleted,IMD_TRACK ** restrack,IMD_SECTOR ** result)757 static dsk_err_t imd_find_sector(IMD_DSK_DRIVER *self, const DSK_GEOMETRY *geom,
758 				dsk_pcyl_t cylinder, dsk_phead_t head,
759 				dsk_psect_t sector, dsk_pcyl_t cyl_expected,
760 				dsk_phead_t head_expected, int *deleted,
761 				IMD_TRACK **restrack, IMD_SECTOR **result)
762 {
763 	int ntrack, nsec;
764 	IMD_TRACK *trk;
765 	IMD_SECTOR *sec;
766 	int want_deleted = 0;
767 	int have_deleted = 0;
768 	dsk_err_t err;
769 
770 	*restrack = NULL;
771 	*result = NULL;
772 	if (deleted && *deleted) want_deleted = 1;
773 
774 	err = imd_seektrack(&self->imd_super, geom, cylinder, head, &ntrack);
775 	if (err) return err;
776 
777 	trk = self->imd_tracks[ntrack];
778 	for (nsec = 0; nsec < trk->imdt_sectors; nsec++)
779 	{
780 		sec = trk->imdt_sec[nsec];
781 
782 		if (sec->imds_cylinder == cyl_expected &&
783 		    sec->imds_head     == head_expected &&
784 		    sec->imds_sector   == sector)
785 		{
786 			if (isdeleted(sec->imds_status)) have_deleted = 1;
787 
788 			if (geom->dg_noskip == 0 &&
789 				want_deleted != have_deleted) continue;
790 
791 			if (deleted) *deleted = have_deleted;
792 			*restrack = trk;
793 			*result = sec;
794 
795 			/* Translate status to libdsk error code */
796 			switch (sec->imds_status)
797 			{
798 				case ST_NODATA: return DSK_ERR_NODATA;
799 						/* ID but no data */
800 				case ST_DATAERR:
801 				case ST_CDATAERR:
802 				case ST_DELERR:
803 				case ST_CDELERR: return DSK_ERR_DATAERR;
804 				default:return DSK_ERR_OK;
805 			}
806 		}
807 	}
808 	self->imd_sec = 0;
809 	return DSK_ERR_NOADDR;
810 }
811 
812 
imd_read(DSK_DRIVER * self,const DSK_GEOMETRY * geom,void * buf,dsk_pcyl_t cylinder,dsk_phead_t head,dsk_psect_t sector)813 dsk_err_t imd_read(DSK_DRIVER *self, const DSK_GEOMETRY *geom,
814                              void *buf, dsk_pcyl_t cylinder,
815                               dsk_phead_t head, dsk_psect_t sector)
816 {
817         return imd_xread(self, geom, buf, cylinder, head, cylinder,
818                 dg_x_head(geom, head),
819                 dg_x_sector(geom, head, sector), geom->dg_secsize, NULL);
820 }
821 
822 
823 /* Expand a (possibly compressed) sector */
expand_sector(unsigned char * bbuf,size_t sector_size,IMD_SECTOR * sec)824 static void expand_sector(unsigned char *bbuf, size_t sector_size,
825 				IMD_SECTOR *sec)
826 {
827 	unsigned n;
828 
829 	if (iscompressed(sec->imds_status))
830 	{
831 		for (n = 0; n < sector_size; n++)
832 		{
833 			bbuf[n] = sec->imds_data[0];
834 		}
835 	}
836 	else
837 	{
838 		for (n = 0; n < sector_size; n++)
839 		{
840 			if (n < sec->imds_datalen)
841 				bbuf[n] = sec->imds_data[n];
842 			else 	bbuf[n] = 0xE5;
843 		}
844 	}
845 }
846 
imd_xread(DSK_DRIVER * self,const DSK_GEOMETRY * geom,void * buf,dsk_pcyl_t cylinder,dsk_phead_t head,dsk_pcyl_t cyl_expected,dsk_phead_t head_expected,dsk_psect_t sector,size_t sector_size,int * deleted)847 dsk_err_t imd_xread(DSK_DRIVER *self, const DSK_GEOMETRY *geom,
848 		void *buf, dsk_pcyl_t cylinder, dsk_phead_t head,
849 		dsk_pcyl_t cyl_expected, dsk_phead_t head_expected,
850 		dsk_psect_t sector, size_t sector_size, int *deleted)
851 {
852 	IMD_DSK_DRIVER *imdself;
853 	IMD_SECTOR *sec = NULL;
854 	IMD_TRACK *trk = NULL;
855 	unsigned char *bbuf = (unsigned char *)buf;
856 	dsk_err_t err;
857 
858 	if (!buf || !self || !geom || self->dr_class != &dc_imd)
859 		return DSK_ERR_BADPTR;
860 	imdself = (IMD_DSK_DRIVER *)self;
861 
862 	if (!imdself->imd_filename) return DSK_ERR_NOTRDY;
863 
864 	err = imd_find_sector(imdself, geom, cylinder, head, sector,
865 		cyl_expected, head_expected, deleted, &trk, &sec);
866 
867 	if (sec && (err == DSK_ERR_OK || err == DSK_ERR_DATAERR))
868 	{
869 		expand_sector(bbuf, sector_size, sec);
870 	}
871 	return err;
872 }
873 
874 
875 
imd_write(DSK_DRIVER * self,const DSK_GEOMETRY * geom,const void * buf,dsk_pcyl_t cylinder,dsk_phead_t head,dsk_psect_t sector)876 dsk_err_t imd_write(DSK_DRIVER *self, const DSK_GEOMETRY *geom,
877                              const void *buf, dsk_pcyl_t cylinder,
878                               dsk_phead_t head, dsk_psect_t sector)
879 {
880 	return imd_xwrite(self, geom, buf, cylinder, head,
881 			cylinder, dg_x_head(geom, head),
882 			dg_x_sector(geom, head, sector),
883 			geom->dg_secsize, 0);
884 }
885 
imd_xwrite(DSK_DRIVER * self,const DSK_GEOMETRY * geom,const void * buf,dsk_pcyl_t cylinder,dsk_phead_t head,dsk_pcyl_t cyl_expected,dsk_phead_t head_expected,dsk_psect_t sector,size_t sector_size,int deleted)886 dsk_err_t imd_xwrite(DSK_DRIVER *self, const DSK_GEOMETRY *geom,
887 		const void *buf, dsk_pcyl_t cylinder, dsk_phead_t head,
888 		dsk_pcyl_t cyl_expected, dsk_phead_t head_expected,
889 		dsk_psect_t sector, size_t sector_size, int deleted)
890 {
891 	IMD_DSK_DRIVER *imdself;
892 	IMD_SECTOR *sec = NULL, *newsec = NULL;
893 	IMD_TRACK *trk = NULL;
894 	dsk_err_t err;
895 	unsigned n;
896 	int newstatus;
897 	size_t secsize;
898 	unsigned char *curbuf;
899 
900 	if (!buf || !self || !geom || self->dr_class != &dc_imd)
901 		return DSK_ERR_BADPTR;
902 	imdself = (IMD_DSK_DRIVER *)self;
903 
904 	if (!imdself->imd_filename) return DSK_ERR_NOTRDY;
905 	if (imdself->imd_readonly)  return DSK_ERR_RDONLY;
906 
907 	err = imd_find_sector(imdself, geom, cylinder, head, sector,
908 		cyl_expected, head_expected, &deleted, &trk, &sec);
909 
910 	if (err != DSK_ERR_OK && err != DSK_ERR_DATAERR) return err;
911 
912 	secsize = sec->imds_seclen;
913 	curbuf = dsk_malloc(secsize);
914 	if (!curbuf) return DSK_ERR_NOMEM;
915 
916 	expand_sector(curbuf, secsize, sec);
917 
918 	if (sector_size > secsize)
919 		memcpy(curbuf, buf, secsize);
920 	else	memcpy(curbuf, buf, sector_size);
921 
922 	/* Curbuf is the buffer to write back. */
923 	newstatus = deleted ? 4 : 2;
924 
925 	/* See if we can compress. */
926 	for (n = 1; n < secsize; n++)
927 	{
928 		if (curbuf[n] != curbuf[0])	/* Can't compress */
929 		{
930 			--newstatus;
931 			break;
932 		}
933 	}
934 	/* Fancy that! Writing back exactly the same data! */
935 	if (newstatus == sec->imds_status &&
936 	    !memcmp(curbuf, sec->imds_data, sec->imds_datalen))
937 	{
938 		dsk_free(curbuf);
939 		return DSK_ERR_OK;
940 	}
941 	/* OK. We need to do an actual write. */
942 	if (newstatus == 1 || newstatus == 3)
943 	{
944 		newsec = dsk_malloc(sizeof(IMD_SECTOR) + secsize);
945 	}
946 	else
947 	{
948 		newsec = dsk_malloc(sizeof(IMD_SECTOR) + 1);
949 	}
950 	if (!newsec)
951 	{
952 		dsk_free(curbuf);
953 		return DSK_ERR_NOMEM;
954 	}
955 	newsec->imds_cylinder = sec->imds_cylinder;
956 	newsec->imds_head     = sec->imds_head;
957 	newsec->imds_sector   = sec->imds_sector;
958 	newsec->imds_seclen   = secsize;
959 	newsec->imds_status   = newstatus;
960 	newsec->imds_datalen  = (newstatus == 1 || newstatus == 3) ? secsize : 1;
961 	memcpy(newsec->imds_data, curbuf, newsec->imds_datalen);
962 	/* Replace the old sector record with the new one we have
963 	 * constructed */
964 	for (n = 0; n < trk->imdt_sectors; n++)
965 	{
966 		if (trk->imdt_sec[n] == sec)
967 			trk->imdt_sec[n] = newsec;
968 	}
969 	dsk_free(curbuf);
970 	/* Free the old sector */
971 	dsk_free(sec);
972 
973 	/* And mark the file as dirty */
974 	imdself->imd_dirty = 1;
975 	return DSK_ERR_OK;
976 }
977 
978 
979 
imd_format(DSK_DRIVER * self,DSK_GEOMETRY * geom,dsk_pcyl_t cylinder,dsk_phead_t head,const DSK_FORMAT * format,unsigned char filler)980 dsk_err_t imd_format(DSK_DRIVER *self, DSK_GEOMETRY *geom,
981                                 dsk_pcyl_t cylinder, dsk_phead_t head,
982                                 const DSK_FORMAT *format, unsigned char filler)
983 {
984 	IMD_DSK_DRIVER *imdself;
985 	dsk_err_t err;
986 	dsk_psect_t nlsec;
987 	IMD_TRACK *trk;
988 	IMD_SECTOR *sec;
989 	int ntrack;
990 
991 	if (!self || !geom || self->dr_class != &dc_imd) return DSK_ERR_BADPTR;
992 	imdself = (IMD_DSK_DRIVER *)self;
993 
994 	if (!imdself->imd_filename) return DSK_ERR_NOTRDY;
995 	if (imdself->imd_readonly) return DSK_ERR_RDONLY;
996 
997 	/* Construct a new track */
998 	trk = imd_alloc_track(geom->dg_sectors);
999 	if (!trk) return DSK_ERR_NOMEM;
1000 
1001 	trk->imdt_mode     = encode_mode(geom);
1002 	trk->imdt_cylinder = cylinder;
1003 	trk->imdt_head     = head;
1004 	trk->imdt_sectors  = geom->dg_sectors;
1005 
1006 	for (nlsec = 0; nlsec < geom->dg_sectors; nlsec++)
1007 	{
1008 		sec = dsk_malloc(1 + sizeof(IMD_SECTOR));
1009 		if (!sec)
1010 		{
1011 			imd_free_track(trk);
1012 			return DSK_ERR_NOMEM;
1013 		}
1014 
1015 		sec->imds_seclen  = format[nlsec].fmt_secsize;
1016 		if (nlsec == 0)
1017 		{
1018 			trk->imdt_seclen = sec->imds_seclen;
1019 		}
1020 		else if(trk->imdt_seclen != sec->imds_seclen)
1021 		{
1022 			trk->imdt_seclen = 0xFFFF;
1023 		}
1024 		sec->imds_cylinder = format[nlsec].fmt_cylinder;
1025 		sec->imds_head     = format[nlsec].fmt_head;
1026 		sec->imds_sector   = format[nlsec].fmt_sector;
1027 		sec->imds_status   = ST_CNORMAL;
1028 		sec->imds_datalen  = 1;
1029 		sec->imds_data[0]  = filler;
1030 		trk->imdt_sec[nlsec] = sec;
1031 	}
1032 	/* Track populated. Now add it to the image (if it's not there
1033 	 * already) */
1034 	err = imd_seektrack(self, geom, cylinder, head, &ntrack);
1035 
1036 	if (!err)
1037 	{
1038 		imd_free_track(imdself->imd_tracks[ntrack]);
1039 		imdself->imd_tracks[ntrack] = trk;
1040 	}
1041 	else	/* New track, add it */
1042 	{
1043 		for (ntrack = 0; ntrack < (int)imdself->imd_ntracks; ntrack++)
1044 		{
1045 			/* Look for a blank slot */
1046 			if (imdself->imd_tracks[ntrack] == NULL)
1047 				break;
1048 		}
1049 		/* If no blank slot found, use imd_ensure_trackcount to add
1050 		 * some more blank slots */
1051 		err = imd_ensure_trackcount(imdself, ntrack);
1052 		if (err)
1053 		{
1054 			imd_free_track(trk);
1055 			return err;
1056 		}
1057 		imdself->imd_tracks[ntrack] = trk;
1058 	}
1059 	/* And mark the file as dirty */
1060 	imdself->imd_dirty = 1;
1061 
1062 	return DSK_ERR_OK;
1063 }
1064 
1065 
1066 
imd_xseek(DSK_DRIVER * self,const DSK_GEOMETRY * geom,dsk_pcyl_t cylinder,dsk_phead_t head)1067 dsk_err_t imd_xseek(DSK_DRIVER *self, const DSK_GEOMETRY *geom,
1068                       dsk_pcyl_t cylinder, dsk_phead_t head)
1069 {
1070 	return imd_seektrack(self, geom, cylinder, head, NULL);
1071 }
1072 
imd_status(DSK_DRIVER * self,const DSK_GEOMETRY * geom,dsk_phead_t head,unsigned char * result)1073 dsk_err_t imd_status(DSK_DRIVER *self, const DSK_GEOMETRY *geom,
1074                       dsk_phead_t head, unsigned char *result)
1075 {
1076 	IMD_DSK_DRIVER *imdself;
1077 
1078 	if (!self || !geom || self->dr_class != &dc_imd) return DSK_ERR_BADPTR;
1079 	imdself = (IMD_DSK_DRIVER *)self;
1080 
1081 	if (!imdself->imd_filename) *result &= ~DSK_ST3_READY;
1082 	if (imdself->imd_readonly) *result |= DSK_ST3_RO;
1083 	return DSK_ERR_OK;
1084 }
1085 
1086 
1087 /* Read a sector ID from a given track */
imd_secid(DSK_DRIVER * self,const DSK_GEOMETRY * geom,dsk_pcyl_t cylinder,dsk_phead_t head,DSK_FORMAT * result)1088 dsk_err_t imd_secid(DSK_DRIVER *self, const DSK_GEOMETRY *geom,
1089                                 dsk_pcyl_t cylinder, dsk_phead_t head, DSK_FORMAT *result)
1090 {
1091         IMD_DSK_DRIVER *imdself;
1092 	IMD_TRACK *trk;
1093 	IMD_SECTOR *sec;
1094 	dsk_psect_t count;
1095 	int ntrack;
1096 	dsk_err_t err;
1097 
1098         if (!self || !geom || !result || self->dr_class != &dc_imd)
1099                 return DSK_ERR_BADPTR;
1100         imdself = (IMD_DSK_DRIVER *)self;
1101 
1102 	err = imd_seektrack(self, geom, cylinder, head, &ntrack);
1103 	if (err) return err;
1104 	trk = imdself->imd_tracks[ntrack];
1105 
1106 	count = imdself->imd_sec;
1107 	++imdself->imd_sec;
1108 
1109 	sec = trk->imdt_sec[count % trk->imdt_sectors];
1110 
1111 	result->fmt_cylinder = sec->imds_cylinder;
1112 	result->fmt_head     = sec->imds_head;
1113 	result->fmt_sector   = sec->imds_sector;
1114 	result->fmt_secsize  = sec->imds_seclen;
1115 	return DSK_ERR_OK;
1116 }
1117 
1118 
1119 /* An IMD file contains enough information to take a decent stab at
1120  * determining the drive geometry. So if the default libdsk probe
1121  * fails, have a go ourselves. */
1122 
imd_getgeom(DSK_DRIVER * self,DSK_GEOMETRY * geom)1123 dsk_err_t imd_getgeom(DSK_DRIVER *self, DSK_GEOMETRY *geom)
1124 {
1125 	dsk_err_t err;
1126         IMD_DSK_DRIVER *imdself;
1127 	IMD_TRACK *trk;
1128 	IMD_SECTOR *sec;
1129 	DSK_GEOMETRY testgeom;
1130 	int track, sector, es;
1131 	int minsec0, maxsec0, minsec1, maxsec1;
1132 	int fm = 0;
1133 
1134         if (!self || !geom || self->dr_class != &dc_imd)
1135                 return DSK_ERR_BADPTR;
1136         imdself = (IMD_DSK_DRIVER *)self;
1137 	err = dsk_defgetgeom(self, geom);
1138 	if (err == DSK_ERR_OK)
1139 		return err;
1140 
1141 	/* Initialise our test structure to known values */
1142 	dg_stdformat(&testgeom, FMT_180K, NULL, NULL);
1143 	testgeom.dg_cylinders = 0;
1144 	testgeom.dg_sectors = 0;
1145 	testgeom.dg_heads = 0;
1146 
1147 	minsec0 = minsec1 = 256;
1148 	maxsec0 = maxsec1 = 0;
1149 	es = 0;
1150 
1151 	for (track = 0; track < (int)imdself->imd_ntracks; track++)
1152 	{
1153 		trk = imdself->imd_tracks[track];
1154 		if (trk == NULL) continue;
1155 
1156 		if (trk->imdt_sectors > testgeom.dg_sectors)
1157 			testgeom.dg_sectors = trk->imdt_sectors;
1158 		if (trk->imdt_cylinder >= testgeom.dg_cylinders)
1159 			testgeom.dg_cylinders = trk->imdt_cylinder + 1;
1160 		if ((trk->imdt_head & 0x3F) >= (int)(testgeom.dg_heads))
1161 			testgeom.dg_heads = (trk->imdt_head & 0x3F) + 1;
1162 
1163 		switch (trk->imdt_mode)
1164 		{
1165 			case 0: fm = 1; testgeom.dg_datarate = RATE_HD; break;
1166 			case 1: fm = 1; testgeom.dg_datarate = RATE_DD; break;
1167 			case 2: fm = 1; testgeom.dg_datarate = RATE_SD; break;
1168 			case 3: fm = 0; testgeom.dg_datarate = RATE_HD; break;
1169 			case 4: fm = 0; testgeom.dg_datarate = RATE_DD; break;
1170 			case 5: fm = 0; testgeom.dg_datarate = RATE_SD; break;
1171 			case 6: fm = 1; testgeom.dg_datarate = RATE_ED; break;
1172 			case 9: fm = 0; testgeom.dg_datarate = RATE_ED; break;
1173 		}
1174 		testgeom.dg_fm = (fm ? RECMODE_FM : RECMODE_MFM);
1175 		for (sector = 0; sector < trk->imdt_sectors; sector++)
1176 		{
1177 			sec = trk->imdt_sec[sector];
1178 			if (sec == NULL) continue;
1179 
1180 			testgeom.dg_secsize = sec->imds_seclen;
1181 
1182 			if ((trk->imdt_head & 0x3F) == 1)
1183 			{
1184 				if (sec->imds_head == 0) es = 1;
1185 				if (sec->imds_sector < minsec1)
1186 					minsec1 = sec->imds_sector;
1187 				if (sec->imds_sector > maxsec1)
1188 					maxsec1 = sec->imds_sector;
1189 			}
1190 			if ((trk->imdt_head & 0x3F) == 0)
1191 			{
1192 				if (sec->imds_sector < minsec0)
1193 					minsec0 = sec->imds_sector;
1194 				if (sec->imds_sector > maxsec0)
1195 					maxsec0 = sec->imds_sector;
1196 			}
1197 		}
1198 	}
1199 	testgeom.dg_secbase = minsec0;
1200 	testgeom.dg_sectors = (maxsec0 - minsec0) + 1;
1201 
1202 	/* Check for an 'extended surface' format: 2 heads, the
1203 	 * sector ranges on each side are disjoint, and sectors on head 1
1204 	 * are labelled as head 0 */
1205 	if (testgeom.dg_heads == 2 && (maxsec0 + 1 == minsec1) && es)
1206 	{
1207 		testgeom.dg_sidedness = SIDES_EXTSURFACE;
1208 	}
1209 
1210 	if (testgeom.dg_cylinders == 0 ||
1211 	    testgeom.dg_sectors   == 0) return DSK_ERR_BADFMT;
1212 
1213 	memcpy(geom, &testgeom, sizeof(*geom));
1214 	return DSK_ERR_OK;
1215 }
1216 
1217