UrForth: "STRLITERAL" now respects the optimiser
[urasm.git] / src / libfdc / dskfs_p3dos.c
blob3fc2e3e953b22690747bcf5bbfc286296704ae5f
1 /*
2 * +3DOS filesystem API
3 * Copyright (c) 2020 Ketmar Dark
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is furnished
10 * to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in all
13 * copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
17 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
20 * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 * coded by Ketmar // Invisible Vector
24 * Understanding is not required. Only obedience.
26 #include "dskfs_all.h"
27 #include "dskfs_p3dos.h"
28 #include "dskldr.h"
30 //#define P3DSK_DEBUG_FILE_READER
31 //#define P3DSK_DEBUG_FILE_GROW
32 //#define P3DSK_DEBUG_FILE_WRITER
34 #define P3DSK_SECTORS_PER_TRACK (9)
36 #define P3DSK_ASSERT(cond_) do { \
37 if (!(cond_)) { fprintf(stderr, "ASSERTION FAILED at file %s, line %d\n", __FILE__, __LINE__); abort(); } \
38 } while (0)
40 static const char *p3doshdrsign = "PLUS3DOS";
43 //==========================================================================
45 // flpIsDiskP3DOS
47 //==========================================================================
48 int flpIsDiskP3DOS (Floppy *flp) {
49 if (!flp || !flp->insert) return 0;
50 P3DskGeometry geom;
51 if (p3dskDetectGeom(flp, &geom) != 0) return 0;
52 return 1;
56 //==========================================================================
58 // p3dskFormatTrack
60 // build a single track 9x512 (+3DOS)
61 // `dsktype` is `P3DSK_XXX`
63 //==========================================================================
64 static int p3dskFormatTrack (Floppy *flp, int dsktype, int tr) {
65 if (!flp || tr < 0 || tr > 255 || dsktype < 0 || dsktype >= P3DSK_MAX) return FLPERR_SHIT;
66 // don't format second side for single-sided disks (but this is not an error)
67 if (dsktype != P3DSK_PCW_DS && (tr&1) != 0) return FLPERR_OK;
68 // first sector number
69 int currsec = (dsktype == P3DSK_SYSTEM ? 65 : dsktype == P3DSK_DATA ? 193 : 1);
70 FDCSector lst[P3DSK_SECTORS_PER_TRACK];
71 memset(lst, 0, sizeof(lst));
72 for (unsigned f = 0; f < P3DSK_SECTORS_PER_TRACK; ++f) {
73 FDCSector *sc = &lst[f];
74 sc->type = 0xfbU;
75 sc->crc = 0xffffU;
76 sc->trk = ((tr&0xfe)>>1);
77 sc->head = (tr&0x01 ? 1 : 0);
78 sc->sz = 2; // 512 bytes
79 sc->sec = currsec++;
80 memset(sc->data, 0, 512);
82 return flpFormatTracks(flp, tr, lst, P3DSK_SECTORS_PER_TRACK, FLP_FORMAT_TRACK_CALC_CRC);
86 //==========================================================================
88 // p3dskFormatDisk
90 // create +3DOS disk with file system
91 // `dsktype` is `P3DSK_XXX`
93 //==========================================================================
94 int p3dskFormatDisk (Floppy *flp, int dsktype) {
95 if (!flp || dsktype < 0 || dsktype >= P3DSK_MAX) return FLPERR_SHIT;
97 flp->protect = 0;
98 flp->doubleSide = 1;
99 flp->trk80 = (dsktype == P3DSK_PCW_DS ? 1 : 0);
100 flp->insert = 0;
101 flp->changed = 0;
103 const int maxtrk = (dsktype == P3DSK_PCW_DS ? 80 : 40)*2;
104 const int firstsec = (dsktype == P3DSK_SYSTEM ? 65 : dsktype == P3DSK_DATA ? 193 : 1);
106 //fprintf(stderr, "formatting: firstsec=%d; maxtrk=%d\n", firstsec, maxtrk);
108 // format all tracks
109 if (flpClearDisk(flp) != FLPERR_OK) return FLPERR_SHIT;
110 for (int tr = 0; tr < maxtrk; ++tr) {
111 int res = p3dskFormatTrack(flp, dsktype, tr);
112 if (res != FLPERR_OK) {
113 //fprintf(stderr, "cannot format track #%d\n", tr);
114 return res;
118 // write dpb
119 if (dsktype == P3DSK_PCW_SS || dsktype == P3DSK_PCW_DS) {
120 //fprintf(stderr, "writing dpb...\n");
121 const uint8_t defdbp[2][10] = {
122 { 0x00u, /* Disc type */
123 0x00u, /* Disc geometry */
124 0x28u, /* Tracks */
125 0x09u, /* Sectors */
126 0x02u, /* Sector size */
127 0x01u, /* Reserved tracks */
128 0x03u, /* ?Sectors per block */
129 0x02u, /* ?Directory blocks */
130 0x2Au, /* Gap length (R/W) */
131 0x52u, /* Gap length (format) */
133 { 0x03u, /* Disc type */
134 0x81u, /* Disc geometry */
135 0x50u, /* Tracks */
136 0x09u, /* Sectors */
137 0x02u, /* Sector size */
138 0x02u, /* Reserved tracks */
139 0x04u, /* ?Sectors per block */
140 0x04u, /* ?Directory blocks */
141 0x2Au, /* Gap length (R/W) */
142 0x52u, /* Gap length (format) */
145 uint8_t *dpb = flpGetSectorDataPtr(flp, 0, firstsec);
146 if (!dpb) return FLPERR_SHIT;
147 memcpy(dpb, defdbp[dsktype == P3DSK_PCW_SS ? 0 : 1], 10);
148 memset(dpb+10, 0, 512-10);
149 if (flpFixSectorDataCRC(flp, 0, firstsec) < 0) return FLPERR_SHIT;
152 // write directory
153 P3DiskInfo p3d;
154 p3d.flp = flp;
155 if (p3dskDetectGeom(p3d.flp, &p3d.geom) < 0) return FLPERR_SHIT;
157 //fprintf(stderr, "filling directory...\n");
158 for (int f = 0; f < p3d.geom.maxdirentries; ++f) {
159 uint8_t *de = p3dskGetDirEntryPtr(&p3d, f);
160 if (!de) return FLPERR_SHIT;
161 memset(de, 0xE5u, 32); // mark as unused
164 // recalc checksums for all modified sectors
165 //fprintf(stderr, "fixing directory checksums...\n");
166 //FIXME: optimise this!
167 int lastls = -1;
168 for (int f = 0; f < p3d.geom.dirblocks; ++f) {
169 int lsidx = p3dskCalcLogicalSectorForBlock(&p3d.geom, f, 0);
170 if (lsidx < 0) return FLPERR_SHIT;
171 if (lsidx != lastls) {
172 int trkn, secn;
173 if (p3dskLogicalSectorToPhysTS(&p3d.geom, lsidx, &trkn, &secn) != FLPERR_OK) return FLPERR_SHIT;
174 if (flpFixSectorDataCRC(flp, trkn, secn) != FLPERR_OK) return FLPERR_SHIT;
175 lastls = lsidx;
179 flp->insert = 1;
180 return FLPERR_OK;
184 //==========================================================================
186 // p3dskDetectGeom
188 // returns 0 on success, or <0 on error
189 // on error, `geom` is undefined, but sides guaranteed to hold zero
191 //==========================================================================
192 int p3dskDetectGeom (Floppy *flp, P3DskGeometry *geom) {
193 if (!geom) return FLPERR_SHIT;
194 memset(geom, 0, sizeof(P3DskGeometry));
196 if (!flp /*|| !flp->insert*/) return FLPERR_SHIT;
197 int minsnum = 65535;
198 int maxsnum = -1;
200 for (int f = 0; f < 256; ++f) {
201 int r = flpGetRawSectorR(flp, 0, f);
202 if (r > 0) {
203 if (minsnum > r) minsnum = r;
204 if (maxsnum < r) maxsnum = r;
207 //printf("minsec=%d; maxsec=%d\n", minsnum, maxsnum);
208 if (maxsnum < 1 || minsnum > 255) return FLPERR_SHIT;
209 if (maxsnum-minsnum < 8) return FLPERR_SHIT;
211 if (minsnum == 193) {
212 // amstrad cpc data only format
213 // 180 blocks
214 // 64 directory entries
215 // 2 directory blocks
216 geom->sides = 1;
217 geom->cyls = 40;
218 geom->sectors = P3DSK_SECTORS_PER_TRACK;
219 geom->firstsector = 193;
220 geom->secsize = 512;
221 geom->successive = 0;
222 geom->restracks = 0;
223 geom->blocksize = 128u<<3;
224 geom->dirblocks = 2;
225 geom->rwgap = 42;
226 geom->fmtgap = 82;
227 geom->checksum = 0;
228 geom->disktype = P3DSK_DATA;
229 } else if (minsnum == 65) {
230 // amstrad cpc system format
231 // 170 blocks
232 // 64 directory entries
233 // 2 directory blocks
234 geom->sides = 1;
235 geom->cyls = 40;
236 geom->sectors = P3DSK_SECTORS_PER_TRACK;
237 geom->firstsector = 65;
238 geom->secsize = 512;
239 geom->successive = 0;
240 geom->restracks = 2;
241 geom->blocksize = 128u<<3;
242 geom->dirblocks = 2;
243 geom->rwgap = 42;
244 geom->fmtgap = 82;
245 geom->checksum = 0;
246 geom->disktype = P3DSK_SYSTEM;
247 } else {
248 const uint8_t bsec_pcw180[10] = { 0, 0, 40u, 9u, 2u, 1u, 3u, 2u, 0x2Au, 0x52u };
250 const uint8_t *bootsec = flpGetSectorDataPtr(flp, 0, minsnum);
251 if (!bootsec) return FLPERR_SHIT;
252 int fssize = flpGetRawSectorN(flp, 0, minsnum);
253 if (fssize > 2) return FLPERR_SHIT; // sector is too big
254 if (fssize < bootsec[4]) return FLPERR_SHIT; // sector size doesn't match
255 //fprintf(stderr, "000 (%u)\n", bootsec[0]);
257 int calcchecksum = 1;
259 // alot of 0xE5s is 180k
260 unsigned e5count = 0;
261 while (e5count < 10 && bootsec[e5count] == 0xE5u) ++e5count;
262 if (e5count >= 10) {
263 bootsec = bsec_pcw180;
264 calcchecksum = 0;
265 } else {
266 //checksum = bootsec[15];
267 // detect PCW16 boot+root
268 if (bootsec[0] == 0xe9U || bootsec[0] == 0xeaU) {
269 if (memcmp(bootsec+0x2bu, "CP/M", 4) != 0 ||
270 memcmp(bootsec+0x33u, "DSK", 3) != 0 ||
271 memcmp(bootsec+0x7cu, "CP/M", 4) != 0)
273 return FLPERR_SHIT;
275 // real DPB is at 0x80
276 bootsec += 0x80u;
277 calcchecksum = 0;
281 // the only disk types +3DOS knows, we don't need the whole CP/M spectrum
282 if (bootsec[0] != 0 && bootsec[0] != 3) return FLPERR_SHIT;
283 // check sector size
284 if (bootsec[4] > 2) return FLPERR_SHIT;
285 // check directory size
286 if (bootsec[7] == 0) return FLPERR_SHIT;
287 // reserved bits must be zero
288 if (bootsec[1]&0x7cu) return FLPERR_SHIT;
290 // sides
291 switch (bootsec[1]&0x03u) {
292 case 0: geom->sides = 1; break;
293 case 1: geom->sides = 2; break;
294 case 2: geom->sides = 2; geom->successive = 1; break;
295 default: return FLPERR_SHIT;
298 /*k8: it seems that this should be set for 80-track disks
299 if (bootsec[1]&0x80u) {
300 fprintf(stderr, "WTF is DoubleTrack?!\n");
301 //return FLPERR_SHIT;
305 geom->cyls = bootsec[2];
306 geom->sectors = bootsec[3];
307 geom->secsize = 128u<<bootsec[4];
308 // check block size
309 if (bootsec[6] > 8) goto error;
310 // check directory blocks
311 if (bootsec[7] == 0) goto error;
312 if (!geom->cyls || !geom->sectors || geom->cyls > 83 || geom->sectors > 0x1800/geom->secsize) goto error;
313 geom->restracks = bootsec[5];
314 geom->blocksize = 128u<<bootsec[6];
315 geom->dirblocks = bootsec[7];
316 geom->rwgap = bootsec[8];
317 geom->fmtgap = bootsec[9];
318 geom->firstsector = 1;
319 geom->disktype = bootsec[0];
320 geom->checksum = 0;
322 if (calcchecksum) {
323 uint8_t checksum = 0;
324 for (unsigned f = 0; f < geom->secsize; ++f) checksum += bootsec[f];
325 geom->checksum = (checksum == 3);
329 // more sanity checks
330 // reserved tracks check
331 if (geom->restracks >= geom->cyls*geom->sides) goto error;
333 geom->maxdirentries = geom->dirblocks*geom->blocksize/32u; // one dir entry is 32 bytes
334 geom->maxblocks = ((geom->cyls*geom->sides-geom->restracks)*geom->sectors*geom->secsize)/geom->blocksize;
336 // is directory too big?
337 if (geom->dirblocks >= geom->maxblocks) goto error;
339 return FLPERR_OK;
341 error:
342 memset(geom, 0, sizeof(P3DskGeometry));
343 return FLPERR_SHIT;
347 //==========================================================================
349 // p3dskLogicalSectorToPhysTS
351 //==========================================================================
352 int p3dskLogicalSectorToPhysTS (P3DskGeometry *geom, int lsidx, int *outtrk, int *outsec) {
353 if (!geom || lsidx < 0 || !geom->sides || lsidx > 16383) return FLPERR_SHIT;
354 // calculate sector number, it is independed of interleaving
355 const int secn = lsidx%geom->sectors+geom->firstsector;
356 // convert to logical sector number (and skip reserved tracks)
357 const int lstrk = lsidx/geom->sectors+geom->restracks;
358 int head, cyl;
359 if (!geom->successive) {
360 // side-interleaved
361 const uint8_t hmaskshift = geom->sides-1; // `sides` is 1 or 2
362 head = lstrk&hmaskshift;
363 cyl = lstrk>>hmaskshift;
364 } else {
365 // side-successive
366 // alas, we have to use real division here
367 head = lstrk/geom->cyls;
368 cyl = lstrk%geom->cyls;
370 if (cyl >= geom->cyls) return FLPERR_SHIT;
371 const int trkn = (cyl<<1)+head; // libfdc always numbers tracks like this, even for single-sided disks
372 if (trkn > 86*2) return FLPERR_SHIT;
373 if (outtrk) *outtrk = trkn;
374 if (outsec) *outsec = secn;
375 return FLPERR_OK;
379 //==========================================================================
381 // p3dskGetLogicalSector
383 // get logical sector data, or NULL on error
384 // it is not const, but don't write it!
386 //==========================================================================
387 uint8_t *p3dskGetLogicalSector (P3DiskInfo *p3d, int lsidx) {
388 if (!p3d) return NULL;
389 Floppy *flp = p3d->flp;
390 P3DskGeometry *geom = &p3d->geom;
391 if (!flp) return NULL;
392 int trkn, secn;
393 if (p3dskLogicalSectorToPhysTS(geom, lsidx, &trkn, &secn) != FLPERR_OK) return NULL;
394 //fprintf(stderr, "p3dskGetLogicalSector: lsidx=%d; lstrk=%d; trkn=%d; secn=%d\n", lsidx, lstrk, trkn, secn);
395 return flpGetSectorDataPtr(flp, trkn, secn);
399 //==========================================================================
401 // p3dskUpdateLogicalSectorCRC
403 //==========================================================================
404 int p3dskUpdateLogicalSectorCRC (P3DiskInfo *p3d, int lsidx) {
405 if (!p3d) return FLPERR_SHIT;
406 Floppy *flp = p3d->flp;
407 P3DskGeometry *geom = &p3d->geom;
408 if (!flp) return FLPERR_SHIT;
409 int trkn, secn;
410 int res = p3dskLogicalSectorToPhysTS(geom, lsidx, &trkn, &secn);
411 if (res != FLPERR_OK) return res;
412 return flpFixSectorDataCRC(flp, trkn, secn);
416 //==========================================================================
418 // p3dskCalcLogicalSectorForBlock
420 // calculate logical sector number for the given disk block
422 //==========================================================================
423 int p3dskCalcLogicalSectorForBlock (P3DskGeometry *geom, int blknum, int bofs) {
424 if (!geom || !geom->sides || blknum < 0 ||
425 blknum >= geom->maxblocks || bofs < 0 || bofs >= geom->blocksize)
427 return FLPERR_SHIT;
429 return (blknum*geom->blocksize+bofs)/geom->secsize;
433 //==========================================================================
435 // p3dskCalcPhysSectorOffsetForBlock
437 // calculate offset in physical sector for the given block offset
439 //==========================================================================
440 int p3dskCalcPhysSectorOffsetForBlock (P3DskGeometry *geom, int blknum, int bofs) {
441 if (!geom || !geom->sides || blknum < 0 || blknum >= geom->maxblocks ||
442 bofs < 0 || bofs >= geom->blocksize)
444 return FLPERR_SHIT;
446 return bofs%geom->secsize;
450 //==========================================================================
452 // p3dskGetDirEntryPtr
454 //==========================================================================
455 uint8_t *p3dskGetDirEntryPtr (P3DiskInfo *p3d, int idx) {
456 if (!p3d) return NULL;
457 Floppy *flp = p3d->flp;
458 P3DskGeometry *geom = &p3d->geom;
459 if (!flp || !geom || !geom->sides || idx < 0 || idx >= geom->maxdirentries) return NULL;
460 const int deblk = idx/(geom->blocksize/32);
461 const int deofs = idx*32%geom->blocksize;
462 const int lsidx = p3dskCalcLogicalSectorForBlock(geom, deblk, deofs);
463 if (lsidx < 0) return NULL;
464 const int sofs = p3dskCalcPhysSectorOffsetForBlock(geom, deblk, deofs);
465 if (sofs < 0) return NULL;
466 //fprintf(stderr, "p3dskGetDirEntryPtr: idx=%d; lsidx=%d; deblk=%d; deofs=%d; sofs=%d\n", idx, lsidx, deblk, deofs, sofs);
467 uint8_t *data = p3dskGetLogicalSector(p3d, lsidx);
468 if (!data) return NULL;
469 return data+sofs;
473 //==========================================================================
475 // p3dskUpdateDirEntryCRC
477 //==========================================================================
478 int p3dskUpdateDirEntryCRC (P3DiskInfo *p3d, int idx) {
479 if (!p3d) return FLPERR_SHIT;
480 Floppy *flp = p3d->flp;
481 P3DskGeometry *geom = &p3d->geom;
482 if (!flp || !geom || !geom->sides || idx < 0 || idx >= geom->maxdirentries) return FLPERR_SHIT;
483 const int deblk = idx/(geom->blocksize/32);
484 const int deofs = idx*32%geom->blocksize;
485 const int lsidx = p3dskCalcLogicalSectorForBlock(geom, deblk, deofs);
486 if (lsidx < 0) return FLPERR_SHIT;
487 const int sofs = p3dskCalcPhysSectorOffsetForBlock(geom, deblk, deofs);
488 if (sofs < 0) return FLPERR_SHIT;
489 return p3dskUpdateLogicalSectorCRC(p3d, lsidx);
493 //==========================================================================
495 // p3dskIsValidDirEntry
497 //==========================================================================
498 static int p3dskIsValidDirEntry (const uint8_t *de) {
499 if (!de) return 0;
500 // 0-15: user #n file
501 // 16-31: user #n for P2DOS, password extent for CP/M 3+
502 // 32: disk label
503 // 33: time stamp
504 // 0xE5: unused
505 // users are not supported
506 if (de[0] && de[0] != 0xE5u) return 0;
507 for (unsigned f = 1; f < 12; ++f) {
508 const uint8_t ch = de[f]&0x7fU;
509 if (ch < 32 || strchr("<>.,;:=?*[]", (char)ch)) return 0;
511 return 1;
515 //==========================================================================
517 // p3dskReadBlock
519 // returns 0 on success, <0 on error
520 // make sure that `dest` is at least `geom->blocksize` bytes
522 //==========================================================================
523 int p3dskReadBlock (P3DiskInfo *p3d, int blkidx, void *dest) {
524 if (!p3d) return FLPERR_SHIT;
525 Floppy *flp = p3d->flp;
526 P3DskGeometry *geom = &p3d->geom;
527 if (!flp || !geom || !flp->insert || !geom->sides || !dest) return FLPERR_SHIT;
528 if (blkidx < 0 || blkidx >= geom->maxblocks) return FLPERR_SHIT;
529 uint8_t *dp = (uint8_t *)dest;
530 const uint16_t secsize = geom->secsize; // cache it
531 const uint16_t blksize = geom->blocksize; // cache it
532 uint16_t bytesleft = blksize;
533 for (unsigned bofs = 0; bofs < blksize; bofs += secsize) {
534 // calculate logical sector
535 const int lsidx = p3dskCalcLogicalSectorForBlock(geom, blkidx, (int)bofs);
536 if (lsidx < 0) return FLPERR_SHIT;
537 // calculate physical sector offset
538 int psofs = p3dskCalcPhysSectorOffsetForBlock(geom, blkidx, (int)bofs);
539 if (psofs < 0) return FLPERR_SHIT;
540 // read logical sector
541 const uint8_t *secdata = p3dskGetLogicalSector(p3d, lsidx);
542 if (secdata == NULL) return FLPERR_SHIT;
543 // copy data
544 if (secsize >= bytesleft) {
545 // last chunk
546 memcpy(dp, secdata, bytesleft);
547 return FLPERR_OK;
549 // not a last chuck
550 memcpy(dp, secdata, secsize);
551 dp += secsize;
552 bytesleft -= secsize;
554 P3DSK_ASSERT(bytesleft == 0);
555 return FLPERR_OK;
560 //**************************************************************************
562 // +3DOS filename operations
564 //**************************************************************************
566 //==========================================================================
568 // cpmUpcase
570 //==========================================================================
571 static __attribute__((const)) __attribute__((always_inline))
572 inline char cpmUpcase (char ch) {
573 return (ch >= 'a' && ch <= 'z' ? ch-32 : ch);
577 //==========================================================================
579 // cpmNameEqu
581 //==========================================================================
582 static int cpmNameEqu (const void *n0, const void *n1) {
583 const uint8_t *p0 = (const uint8_t *)n0;
584 const uint8_t *p1 = (const uint8_t *)n1;
585 for (unsigned f = 0; f < 11; ++f, ++p0, ++p1) {
586 if ((p0[0]&0x7fU) != (p1[0]&0x7fU)) return 0;
588 return 1;
592 //==========================================================================
594 // cpmNameEquCI
596 //==========================================================================
597 static int cpmNameEquCI (const void *n0, const void *n1) {
598 const uint8_t *p0 = (const uint8_t *)n0;
599 const uint8_t *p1 = (const uint8_t *)n1;
600 for (unsigned f = 0; f < 11; ++f, ++p0, ++p1) {
601 const char c0 = cpmUpcase((char)(p0[0]&0x7fU));
602 const char c1 = cpmUpcase((char)(p1[0]&0x7fU));
603 if (c0 != c1) return 0;
605 return 1;
609 //==========================================================================
611 // p3dskCheckNamePart
613 //==========================================================================
614 static __attribute__((const)) const char *p3dskCheckNamePart (const char *name, size_t maxlen) {
615 if (!name || !name[0]) return NULL;
616 while (*name) {
617 uint8_t ch = (uint8_t)(name[0]&0xffU);
618 if (ch < 32 || ch > 127) return NULL;
619 if (ch == '.' || ch == 32) break; // allow trailing spaces
620 if (strchr("<>,;:=?*[]", (char)ch)) return NULL;
621 if (maxlen == 0) return NULL;
622 ++name;
623 --maxlen;
625 while (*name == ' ') ++name;
626 return name;
630 //==========================================================================
632 // p3dskIsValidFileName
634 //==========================================================================
635 int p3dskIsValidFileName (const char *name) {
636 if (!name || !name[0]) return 0;
637 // check name
638 //fprintf(stderr, " 000:<%s>\n", name);
639 name = p3dskCheckNamePart(name, 8);
640 //fprintf(stderr, " 001:<%s>\n", name);
641 if (!name) return 0;
642 if (!name[0]) return 1;
643 if (name[0] != '.') return 0; // can happen due to space skipping
644 ++name;
645 if (!name[0]) return 1;
646 //fprintf(stderr, " 002:<%s>\n", name);
647 name = p3dskCheckNamePart(name, 3);
648 //fprintf(stderr, " 003:<%s>\n", name);
649 if (!name) return 0;
650 return (name[0] == 0);
654 //==========================================================================
656 // p3dskNormaliseFileName
658 //==========================================================================
659 int p3dskNormaliseFileName (char *dest, const char *src) {
660 if (!dest) return FLPERR_SHIT;
661 *dest = 0;
662 if (!src) return FLPERR_SHIT;
663 if (!p3dskIsValidFileName(src)) return FLPERR_SHIT;
664 // `src` is guaranteed to fit
665 char tmpbuf[13];
666 size_t tpos = 0;
667 while (*src) {
668 uint8_t ch = (uint8_t)((*src++)&0xffU);
669 P3DSK_ASSERT(ch >= 32 && ch <= 127);
670 if (ch == '.') { --src; break; }
671 if (ch == 32) break;
672 P3DSK_ASSERT(tpos < 12);
673 tmpbuf[tpos++] = (char)ch;
675 while (*src == ' ') ++src;
676 if (*src == '.') {
677 ++src;
678 tmpbuf[tpos++] = '.';
679 while (*src) {
680 uint8_t ch = (uint8_t)((*src++)&0xffU);
681 P3DSK_ASSERT(ch != '.' && ch >= 32 && ch <= 127);
682 if (ch == 32) break;
683 P3DSK_ASSERT(tpos < 12);
684 tmpbuf[tpos++] = (char)ch;
687 P3DSK_ASSERT(tpos <= 12);
688 tmpbuf[tpos] = 0;
689 strcpy(dest, tmpbuf);
690 return FLPERR_OK;
694 //==========================================================================
696 // p3dskBuildCPMName
698 // `src` must be valid file name
699 // `dest` is 11-byte buffer
701 //==========================================================================
702 static int p3dskBuildCPMName (void *dest, const char *src) {
703 if (!dest) return FLPERR_SHIT;
704 if (!src) return FLPERR_SHIT;
705 if (!p3dskIsValidFileName(src)) return FLPERR_SHIT;
706 // `src` is guaranteed to fit
707 char tmpbuf[11];
708 size_t tpos = 0;
709 while (*src) {
710 uint8_t ch = (uint8_t)((*src++)&0xffU);
711 P3DSK_ASSERT(ch >= 32 && ch <= 127);
712 if (ch == '.') { --src; break; }
713 if (ch == 32) break;
714 P3DSK_ASSERT(tpos < 11);
715 tmpbuf[tpos++] = (char)ch;
717 //fprintf(stderr, "::<%s>\n", src);
718 // pad with spaces
719 while (tpos < 8) tmpbuf[tpos++] = ' ';
720 // extension
721 while (*src == ' ') ++src;
722 if (*src == '.') {
723 ++src;
724 while (*src) {
725 uint8_t ch = (uint8_t)((*src++)&0xffU);
726 P3DSK_ASSERT(ch != '.' && ch >= 32 && ch <= 127);
727 if (ch == 32) break;
728 P3DSK_ASSERT(tpos < 11);
729 tmpbuf[tpos++] = (char)ch;
732 // pad with spaces
733 while (tpos < 11) tmpbuf[tpos++] = ' ';
734 P3DSK_ASSERT(tpos == 11);
735 memcpy(dest, tmpbuf, 11);
736 return FLPERR_OK;
740 //==========================================================================
742 // p3dskIsGlobMask
744 //==========================================================================
745 int p3dskIsGlobMask (const char *str) {
746 if (!str) return 0;
747 for (; *str; ++str) {
748 const char ch = *str;
749 if (ch == '?' || ch == '*' || ch == '[') return 1;
751 return 0;
755 //==========================================================================
757 // p3dskBuildNameDirEntry
759 // `destbuf` must be at least 13 bytes
760 // will never write more than 13 bytes
762 //==========================================================================
763 static int p3dskBuildNameDirEntry (char destbuf[], const uint8_t *de) {
764 if (!de || !destbuf) return FLPERR_SHIT;
765 if (!p3dskIsValidDirEntry(de)) return FLPERR_SHIT;
766 // copy name
767 memcpy(destbuf, de+1, 8);
768 // normalize chars
769 for (unsigned f = 0; f < 8; ++f) destbuf[f] &= 0x7fU;
770 // remove trailing spaces
771 size_t epos = 8;
772 while (epos > 0 && destbuf[epos-1] == ' ') --epos;
773 // put dot (it is always there)
774 destbuf[epos++] = '.';
775 // copy extension
776 memcpy(destbuf+epos, de+9, 3);
777 // normalize chars
778 for (unsigned f = 0; f < 3; ++f) destbuf[epos+f] &= 0x7fU;
779 // remove trailing spaces
780 epos += 3;
781 while (epos > 0 && destbuf[epos-1] == ' ') --epos;
782 destbuf[epos] = 0;
783 return FLPERR_OK;
787 //==========================================================================
789 // p3dskCopyStrN
791 //==========================================================================
792 static int p3dskCopyStrN (char destbuf[], const void *src, size_t nlen) {
793 if (!destbuf) return FLPERR_SHIT;
794 if (nlen == 0) { destbuf[0] = 0; return FLPERR_OK; }
795 if (!src) return FLPERR_SHIT;
796 // copy it
797 memcpy(destbuf, src, nlen);
798 // normalize chars
799 for (unsigned f = 0; f < nlen; ++f) destbuf[f] &= 0x7fU;
800 // remove trailing spaces
802 size_t epos = nlen;
803 while (epos > 0 && destbuf[epos-1] == ' ') --epos;
804 destbuf[epos] = 0;
806 destbuf[nlen] = 0;
807 return FLPERR_OK;
811 //==========================================================================
813 // p3dskGlobMatch
815 //==========================================================================
816 int p3dskGlobMatch (const char *str, const char *pat, unsigned flags) {
817 int hasMatch, inverted;
818 size_t patpos;
819 int star = 0;
820 if (!pat) pat = "";
821 if (!str) str = "";
822 loopStart:
823 patpos = 0;
824 for (size_t i = 0; str[i]; ++i) {
825 char sch = str[i];
826 if ((flags&P3DSK_GLOB_CASE_SENSITIVE) == 0) sch = cpmUpcase(sch);
827 switch (pat[patpos++]) {
828 case 0: goto starCheck;
829 case '?': // match anything (except '.' for file names)
830 if (sch == '.' && (flags&P3DSK_GLOB_STRING) == 0) goto starCheck;
831 break;
832 case '*':
833 star = 1;
834 str += i;
835 pat += patpos;
836 while (*pat == '*') ++pat;
837 if (*pat == 0) return 1;
838 goto loopStart;
839 case '[':
840 hasMatch = inverted = 0;
841 if (pat[patpos] == '^') { inverted = 1; ++patpos; }
842 while (pat[patpos] && pat[patpos] != ']') {
843 char c0 = pat[patpos++];
844 if ((flags&P3DSK_GLOB_CASE_SENSITIVE) == 0) c0 = cpmUpcase(c0);
845 char c1;
846 if (pat[patpos] == '-') {
847 // char range
848 ++patpos; // skip '-'
849 c1 = pat[patpos++];
850 if (!c1) {
851 --patpos;
852 c1 = c0;
854 if ((flags&P3DSK_GLOB_CASE_SENSITIVE) == 0) c1 = cpmUpcase(c1);
855 } else {
856 c1 = c0;
858 // dot cannot be matched for filenames
859 if ((flags&P3DSK_GLOB_STRING) == 0) {
860 if (c0 == '.') ++c0;
861 if (c1 == '.') --c1;
863 if (!hasMatch && (uint8_t)(sch&0xffU) >= (uint8_t)(c0&0xffU) && (uint8_t)(sch&0xffU) <= (uint8_t)(c1&0xffU)) hasMatch = 1;
865 if (pat[patpos] == ']') ++patpos;
866 if (inverted) hasMatch = !hasMatch;
867 if (!hasMatch) goto starCheck;
868 break;
869 default:
870 if ((flags&P3DSK_GLOB_CASE_SENSITIVE) == 0) {
871 if (sch != cpmUpcase(pat[patpos-1])) goto starCheck;
872 } else {
873 if (sch != pat[patpos-1]) goto starCheck;
875 break;
878 pat += patpos;
879 while (*pat == '*') ++pat;
880 if (*pat) {
881 // special handling for file names
882 //if ((flags&P3DSK_GLOB_STRING) == 0 && pat[0] == '.' && !pat[1]) return 1;
883 return 0;
885 // special handling for file names
886 return 1;
888 starCheck:
889 if (!star) return 0;
890 if (*str) ++str;
891 goto loopStart;
896 //**************************************************************************
898 // +3DOS directory searching
900 //**************************************************************************
902 //==========================================================================
904 // p3dskFindInit
906 //==========================================================================
907 int p3dskFindInit (P3DiskInfo *p3d, P3DskFileInfo *nfo, const char *mask) {
908 if (!p3d || !p3d->flp || !p3d->geom.sides || !p3d->flp->insert) return FLPERR_SHIT;
909 if (!mask[0]) return FLPERR_BADMASK;
910 const size_t masklen = strlen(mask);
911 if (masklen >= sizeof(nfo->mask)-2u) return FLPERR_BADMASK;
912 memset(nfo, 0, sizeof(P3DskFileInfo));
913 strcpy(nfo->mask, mask);
914 if (!strchr(nfo->mask, '.')) strcat(nfo->mask, ".");
915 nfo->p3d = p3d;
916 return FLPERR_OK;
920 //==========================================================================
922 // p3dskCalcFileSize
924 // <0: error
926 //==========================================================================
927 static int p3dskCalcFileSize (P3DiskInfo *p3d, const void *namebuf/*[11]*/, uint16_t currextidx) {
928 if (!p3d) return FLPERR_SHIT;
929 Floppy *flp = p3d->flp;
930 P3DskGeometry *geom = &p3d->geom;
931 if (!flp || !geom || !flp->insert || !geom->sides || !namebuf) return FLPERR_SHIT;
932 int fsize = 0;
933 uint16_t nenum = 0;
934 for (;;) {
935 int found = 0;
936 for (unsigned f = 0; f < geom->maxdirentries; ++f) {
937 const uint8_t *de = p3dskGetDirEntryPtr(p3d, currextidx);
938 currextidx = (currextidx+1)%geom->maxdirentries;
939 if (!de) return FLPERR_SHIT;
940 if (de[0] != 0) continue; // allow only USER 0 files
941 // get extent number
942 // bit 5-7 of Xl are 0, bit 0-4 store the lower bits of the extent number.
943 // bit 6 and 7 of Xh are 0, bit 0-5 store the higher bits of the extent number.
944 if (de[12]&0x70u) continue; // check bits 5-7
945 if (de[14]&0xc0u) continue; // check bits 6-7
946 const uint16_t eidx = de[12]|(uint16_t)(de[14]<<5);
947 //fprintf(stderr, "ce=%u; eidx=%u; nenum=%u; [%.*s] [%.*s]\n", f, eidx, nenum, 11, (const char *)namebuf, 11, (const char *)(de+1));
948 if (eidx != nenum) continue;
949 // compare name
950 if (cpmNameEqu(de+1, namebuf) == 0) continue;
951 //fprintf(stderr, " HIT!\n");
952 // calc size
953 uint16_t rn = de[15]*128;
954 if (geom->maxblocks < 256) {
955 // 8-bit block numbers
956 if (rn/geom->blocksize > 16) return FLPERR_SHIT;
957 } else {
958 // 16-bit block numbers
959 if (rn/geom->blocksize > 8) return FLPERR_SHIT;
961 // +3DOS is not using this, but why not?
962 if (de[13] && de[13] <= 128 && de[13] <= rn) rn -= 128-de[13];
963 fsize += rn; // Bc is not used by +3DOS
964 ++nenum;
965 found = 1;
967 if (!found) break;
969 return fsize;
973 //==========================================================================
975 // p3dskFindNext
977 //==========================================================================
978 int p3dskFindNext (P3DskFileInfo *nfo) {
979 if (!nfo || !nfo->p3d) return FLPERR_SHIT;
980 Floppy *flp = nfo->p3d->flp;
981 P3DskGeometry *geom = &nfo->p3d->geom;
982 if (!flp || !geom || !flp->insert || !geom->sides) return FLPERR_SHIT;
983 if (!nfo->mask[0]) return FLPERR_SHIT;
984 for (; nfo->nextextent < geom->maxdirentries; ++nfo->nextextent) {
985 const uint8_t *de = p3dskGetDirEntryPtr(nfo->p3d, nfo->nextextent);
986 if (!de) return FLPERR_SHIT;
987 // ignore non-first extents
988 if (de[12]|de[14]) continue;
989 //FIXME: return error?
990 if (de[0] != 0) continue; // allow only USER 0 files
991 //FIXME: return error?
992 if (!p3dskIsValidDirEntry(de)) continue; // this file entry doesn't look valid, skip it
993 memset(nfo->name, 0, sizeof(nfo->name));
994 memset(nfo->nameonly, 0, sizeof(nfo->nameonly));
995 memset(nfo->extonly, 0, sizeof(nfo->extonly));
996 if (p3dskBuildNameDirEntry(nfo->name, de) != FLPERR_OK) return FLPERR_SHIT;
997 if (p3dskGlobMatch(nfo->name, nfo->mask, P3DSK_GLOB_DEFAULT) == 0) continue;
998 // remove trailing dot
999 const size_t nlen = strlen(nfo->name);
1000 if (nlen && nfo->name[nlen-1] == '.') nfo->name[nlen-1] = 0;
1001 // save name as-is
1002 if (p3dskCopyStrN(nfo->nameonly, de+1, 8) != FLPERR_OK) return FLPERR_SHIT;
1003 if (p3dskCopyStrN(nfo->extonly, de+9, 3) != FLPERR_OK) return FLPERR_SHIT;
1004 // i found her!
1005 // get attrs
1006 nfo->readonly = (de[9+0]&0x80u ? 1 : 0);
1007 nfo->system = (de[9+1]&0x80u ? 1 : 0);
1008 nfo->archive = (de[9+2]&0x80u ? 1 : 0);
1009 // misc info
1010 nfo->firstextent = nfo->nextextent;
1011 // file size
1012 const int fsize = p3dskCalcFileSize(nfo->p3d, de+1, nfo->firstextent);
1013 if (fsize < 0) return FLPERR_SHIT;
1014 nfo->size = (unsigned)fsize;
1015 // continue from the next extent
1016 ++nfo->nextextent;
1017 // return success flag
1018 return 1;
1020 return 0; // no more files
1025 //**************************************************************************
1027 // +3DOS high-level directory operations
1029 //**************************************************************************
1031 //==========================================================================
1033 // p3dskDeleteFile
1035 // delete file
1036 // returns error code (FLPERR_NOFILE means that file not found)
1038 //==========================================================================
1039 int p3dskDeleteFiles (P3DiskInfo *p3d, const char *name) {
1040 // find file
1041 if (!p3d) return FLPERR_SHIT;
1042 Floppy *flp = p3d->flp;
1043 P3DskGeometry *geom = &p3d->geom;
1044 if (!flp || !geom || !flp->insert || !geom->sides || flp->protect) return FLPERR_SHIT;
1045 if (!name || !name[0]) return FLPERR_NOFILE;
1046 // check for glob mask
1047 char pdfname[13];
1048 if (!p3dskIsGlobMask(name)) {
1049 if (!p3dskIsValidFileName(name)) return FLPERR_NOFILE;
1050 if (p3dskBuildCPMName(pdfname, name) != FLPERR_OK) return FLPERR_SHIT;
1051 } else {
1052 pdfname[0] = 0; // "not a file name" flag
1054 int wasfound = 0;
1055 // walk directory, fill all file directory entries with 0xe5
1056 for (unsigned dirext = 0; dirext < p3d->geom.maxdirentries; ++dirext) {
1057 uint8_t *de = p3dskGetDirEntryPtr(p3d, dirext);
1058 if (!de) return FLPERR_SHIT;
1059 //FIXME: return error?
1060 if (de[0] != 0 && de[0] != 33) continue; // allow only USER 0 files and time stamps
1061 //FIXME: return error?
1062 if (!p3dskIsValidDirEntry(de)) continue; // this file entry doesn't look valid, skip it
1063 // compare with a glob mask, or with a name
1064 if (pdfname[0]) {
1065 // name
1066 #if 0
1067 char tmpbuf[11];
1068 memcpy(tmpbuf, de+1, 11);
1069 for (unsigned ff = 0; ff < 11; ++ff) tmpbuf[ff] &= 0x7f;
1070 fprintf(stderr, " de:<%.11s> n:<%.11s>\n", tmpbuf, pdfname);
1071 #endif
1072 if (cpmNameEquCI(pdfname, de+1) == 0) continue;
1073 } else {
1074 // compare with a glob mask (reuse `pdfname`)
1075 memset(pdfname, 0, sizeof(pdfname));
1076 if (p3dskBuildNameDirEntry(pdfname, de) != FLPERR_OK) return FLPERR_SHIT;
1077 const int equ = p3dskGlobMatch(pdfname, name, P3DSK_GLOB_DEFAULT);
1078 pdfname[0] = 0;
1079 if (!equ) continue;
1081 // i found her!
1082 wasfound = 1;
1083 // erase directory entry
1084 memset(de, 0xE5u, 32);
1085 // update sector crc
1086 p3dskUpdateDirEntryCRC(p3d, dirext);
1088 return (wasfound ? FLPERR_OK : FLPERR_NOFILE);
1093 //**************************************************************************
1095 // +3DOS high-level file i/o operations
1097 //**************************************************************************
1099 //==========================================================================
1101 // p3dskOpenFile
1103 // find file and open it for reading/writing
1104 // returns error code
1106 //==========================================================================
1107 int p3dskOpenFile (P3DiskInfo *p3d, P3DskFileInfo *nfo, const char *name) {
1108 if (!nfo || !p3d) return FLPERR_SHIT;
1109 if (!p3dskIsValidFileName(name)) return FLPERR_NOFILE;
1110 char pdfname[13];
1111 if (p3dskNormaliseFileName(pdfname, name) != FLPERR_OK) return FLPERR_NOFILE;
1112 //fprintf(stderr, "<%s> : <%s>\n", pdfname, name);
1113 if (p3dskFindInit(p3d, nfo, pdfname) != FLPERR_OK) return FLPERR_NOFILE;
1114 int res = p3dskFindNext(nfo);
1115 //fprintf(stderr, "<%s> : <%s>; res=%d\n", pdfname, name, res);
1116 if (res < 0) return res;
1117 if (res == 0) return FLPERR_NOFILE;
1118 return FLPERR_OK;
1122 //==========================================================================
1124 // p3dskOpenFile
1126 // create empty +3DOS file
1127 // will fail if the given file already exists
1128 // returns error code
1130 //==========================================================================
1131 int p3dskCreateFile (P3DiskInfo *p3d, const char *name) {
1132 if (!p3d || !name || !name[0]) return FLPERR_SHIT;
1133 if (!p3d->flp || p3d->flp->protect) return FLPERR_SHIT;
1134 if (!p3dskIsValidFileName(name)) return FLPERR_NOFILE;
1135 char pdfname[13];
1136 if (p3dskNormaliseFileName(pdfname, name) != FLPERR_OK) return FLPERR_SHIT;
1137 P3DskFileInfo nfo;
1138 if (p3dskFindInit(p3d, &nfo, pdfname) != FLPERR_OK) return FLPERR_SHIT;
1139 int res = p3dskFindNext(&nfo);
1140 if (res < 0) return res;
1141 if (res != 0) return FLPERR_FILEEXIST;
1142 char cpmname[11];
1143 if (p3dskBuildCPMName(cpmname, pdfname) != FLPERR_OK) return FLPERR_SHIT;
1144 // find empty directory entry
1145 for (unsigned f = 0; f < p3d->geom.maxdirentries; ++f) {
1146 uint8_t *de = p3dskGetDirEntryPtr(p3d, (int)f);
1147 if (!de) return FLPERR_SHIT;
1148 if (de[0] <= 15) continue; // USER n files, preserve
1149 // this is either unused, or timestamp
1150 // build new file extent
1151 memset(de, 0, 32);
1152 de[0] = 0; // always USER 0
1153 // set name
1154 memcpy(de+1, cpmname, 11);
1155 return p3dskUpdateDirEntryCRC(p3d, (int)f);
1157 return FLPERR_MANYFILES;
1161 ////////////////////////////////////////////////////////////////////////////////
1162 // file block walker
1163 typedef struct {
1164 P3DiskInfo *p3d;
1165 // wanted file extent index
1166 uint16_t nenum;
1167 // extent to start scanning from
1168 uint16_t nextextent;
1169 // file name, to detect file extents
1170 char namebuf[11];
1171 // read file blocks
1172 uint16_t blocks[16];
1173 // number of valid blocks in `blocks` array
1174 unsigned blocksused;
1175 uint32_t extbytes; // bytes in this extent
1176 } P3DskExtentWalker;
1179 //==========================================================================
1181 // p3dskInitExtentWalker
1183 // returns error code
1185 //==========================================================================
1186 static int p3dskInitExtentWalker (P3DskExtentWalker *wlk, const P3DskFileInfo *nfo) {
1187 if (wlk) memset(wlk, 0, sizeof(P3DskExtentWalker));
1188 if (!nfo || !nfo->p3d || !wlk) return FLPERR_SHIT;
1189 Floppy *flp = nfo->p3d->flp;
1190 P3DskGeometry *geom = &nfo->p3d->geom;
1191 if (!flp || !geom || !flp->insert || !geom->sides) return FLPERR_SHIT;
1192 if (!nfo->mask[0] || !nfo->name[0]) return FLPERR_SHIT;
1193 // init walker
1194 wlk->p3d = nfo->p3d;
1195 wlk->nenum = 0;
1196 wlk->nextextent = nfo->firstextent;
1197 wlk->blocksused = 0;
1198 // build name buffer, so we can look for extents faster
1199 memcpy(wlk->namebuf, nfo->nameonly, 8);
1200 memcpy(wlk->namebuf+8, nfo->extonly, 3);
1201 return FLPERR_OK;
1205 //==========================================================================
1207 // p3dskNextExtent
1209 // returns error code, 0 if no more extents, >0 if next extent found
1211 //==========================================================================
1212 static int p3dskNextExtent (P3DskExtentWalker *wlk) {
1213 if (!wlk || !wlk->p3d) return FLPERR_SHIT;
1214 Floppy *flp = wlk->p3d->flp;
1215 P3DskGeometry *geom = &wlk->p3d->geom;
1216 if (!flp || !geom || !flp->insert || !geom->sides || !wlk->namebuf[0]) return FLPERR_SHIT;
1217 if (wlk->nextextent >= geom->maxdirentries) return 0; // no more
1218 // find extent
1219 for (unsigned f = 0; f < geom->maxdirentries; ++f) {
1220 const uint8_t *de = p3dskGetDirEntryPtr(wlk->p3d, wlk->nextextent);
1221 if (!de) return FLPERR_SHIT;
1222 wlk->nextextent = (wlk->nextextent+1)%geom->maxdirentries;
1223 if (de[0] != 0) continue; // allow only USER 0 files
1224 // get extent number
1225 // bit 5-7 of Xl are 0, bit 0-4 store the lower bits of the extent number.
1226 // bit 6 and 7 of Xh are 0, bit 0-5 store the higher bits of the extent number.
1227 if (de[12]&0x70u) continue; // check bits 5-7
1228 if (de[14]&0xc0u) continue; // check bits 6-7
1229 const uint16_t eidx = de[12]|(uint16_t)(de[14]<<5);
1230 if (eidx != wlk->nenum) continue;
1231 // compare name
1232 if (cpmNameEqu(de+1, wlk->namebuf) == 0) continue;
1233 // good extent; advance extent sequence number
1234 ++wlk->nenum;
1235 // get bytes in this extent
1236 uint16_t rn = de[15]*128;
1237 // get block list
1238 wlk->blocksused = 0;
1239 if (geom->maxblocks < 256) {
1240 // 8-bit block numbers
1241 if (rn/geom->blocksize > 16) return FLPERR_SHIT;
1242 const unsigned blkused = (rn+geom->blocksize-1u)/geom->blocksize;
1243 for (unsigned bc = 0; bc < blkused; ++bc) {
1244 uint16_t bnum = de[16+bc];
1245 if (bnum) {
1246 if (bnum < geom->dirblocks || bnum >= geom->maxblocks) return FLPERR_SHIT;
1248 wlk->blocks[wlk->blocksused++] = bnum;
1250 } else {
1251 // 16-bit block numbers
1252 if (rn/geom->blocksize > 8) return FLPERR_SHIT;
1253 const unsigned blkused = (rn+geom->blocksize-1u)/geom->blocksize;
1254 for (unsigned bc = 0; bc < blkused; ++bc) {
1255 uint16_t bnum = de[16+(bc<<1)]|((uint16_t)(de[17+(bc<<1)]));
1256 if (bnum) {
1257 if (bnum < geom->dirblocks || bnum >= geom->maxblocks) return FLPERR_SHIT;
1259 wlk->blocks[wlk->blocksused++] = bnum;
1262 // +3DOS is not using this, but why not?
1263 if (de[13] && de[13] <= 128 && de[13] <= rn) rn -= 128-de[13];
1264 // clamp bytes in this extent
1265 if (rn > wlk->blocksused*geom->blocksize) rn = wlk->blocksused*geom->blocksize;
1266 wlk->extbytes = rn;
1267 // check for possible EOF
1268 if (wlk->blocksused == 0 || rn == 0) {
1269 wlk->nextextent = geom->maxdirentries;
1270 wlk->blocksused = 0;
1271 break;
1273 // found something
1274 return 1;
1276 return 0; // no more
1280 //==========================================================================
1282 // p3dskFindCalcBlockbufLength
1284 // returns -1, or number of 16-bit items in file block buffer
1286 //==========================================================================
1287 int p3dskFindCalcBlockbufLength (P3DskFileInfo *nfo) {
1288 if (!nfo || !nfo->p3d) return FLPERR_SHIT;
1289 Floppy *flp = nfo->p3d->flp;
1290 P3DskGeometry *geom = &nfo->p3d->geom;
1291 if (!flp || !geom || !flp->insert || !geom->sides) return FLPERR_SHIT;
1292 if (!nfo->mask[0] || !nfo->name[0]) return FLPERR_SHIT;
1293 int buflen = 0;
1294 P3DskExtentWalker wlk;
1295 if (p3dskInitExtentWalker(&wlk, nfo) != FLPERR_OK) return FLPERR_SHIT;
1296 for (;;) {
1297 int neres = p3dskNextExtent(&wlk);
1298 if (neres < 0) return neres;
1299 if (neres == 0) break;
1300 buflen += (int)wlk.blocksused;
1302 return buflen;
1306 //==========================================================================
1308 // p3dskGetBlockbuf
1310 // get all file blocks; buffer size must be
1311 // calculated with `p3dskFindCalcBlockbufLength()`
1313 //==========================================================================
1314 int p3dskGetBlockbuf (P3DskFileInfo *nfo, uint16_t *dest) {
1315 if (!nfo || !nfo->p3d) return FLPERR_SHIT;
1316 Floppy *flp = nfo->p3d->flp;
1317 P3DskGeometry *geom = &nfo->p3d->geom;
1318 if (!flp || !geom || !flp->insert || !geom->sides || !dest) return FLPERR_SHIT;
1319 if (!nfo->mask[0] || !nfo->name[0]) return FLPERR_SHIT;
1320 P3DskExtentWalker wlk;
1321 if (p3dskInitExtentWalker(&wlk, nfo) != FLPERR_OK) return FLPERR_SHIT;
1322 for (;;) {
1323 int neres = p3dskNextExtent(&wlk);
1324 if (neres < 0) return neres;
1325 if (neres == 0) break;
1326 for (unsigned f = 0; f < wlk.blocksused; ++f) *dest++ = wlk.blocks[f];
1328 return FLPERR_OK;
1332 //==========================================================================
1334 // p3dskReadFile
1336 // read file data
1337 // returns <0 on error, or number of bytes read
1339 //==========================================================================
1340 int p3dskReadFile (const P3DskFileInfo *nfo, void *buf, int ofs, int bytecount) {
1341 if (!nfo || !nfo->p3d) return FLPERR_SHIT;
1342 Floppy *flp = nfo->p3d->flp;
1343 P3DskGeometry *geom = &nfo->p3d->geom;
1344 if (!flp || !geom || !flp->insert || !geom->sides) return FLPERR_SHIT;
1345 if (!nfo->mask[0] || !nfo->name[0]) return FLPERR_SHIT;
1346 if (ofs < 0 || bytecount < 0) return FLPERR_SHIT;
1347 if (bytecount == 0) return 0;
1348 if (!buf) return FLPERR_SHIT;
1349 if ((unsigned)ofs >= nfo->size) return 0; // past EOF
1350 const size_t wantedofs = (unsigned)ofs;
1351 size_t currfpos = 0;
1352 uint8_t *dest = (uint8_t *)buf;
1353 size_t dpos = 0;
1354 const uint16_t blksize = geom->blocksize; // cache it
1355 // walk extents
1356 P3DskExtentWalker wlk;
1357 if (p3dskInitExtentWalker(&wlk, nfo) != FLPERR_OK) return FLPERR_SHIT;
1358 do {
1359 int neres = p3dskNextExtent(&wlk);
1360 if (neres < 0) return neres;
1361 if (neres == 0) break;
1362 #ifdef P3DSK_DEBUG_FILE_READER
1363 fprintf(stderr, ".extent; currfpos=%u; wantedofs=%u; bcount=%u; ebytes=%u\n", (unsigned)currfpos, (unsigned)wantedofs, wlk.blocksused, wlk.extbytes);
1364 #endif
1365 // skip this extent as a whole if it is not interesting
1366 if (currfpos < wantedofs && currfpos+wlk.extbytes <= wantedofs) {
1367 // skip this extent
1368 #ifdef P3DSK_DEBUG_FILE_READER
1369 fprintf(stderr, "..skip extent; currfpos=%u; wantedofs=%u; ebytes=%u\n", (unsigned)currfpos, (unsigned)wantedofs, wlk.extbytes);
1370 #endif
1371 currfpos += wlk.extbytes;
1372 continue;
1374 // universal code here, why not?
1375 #ifdef P3DSK_DEBUG_FILE_READER
1376 fprintf(stderr, "..processing extent; currfpos=%u; wantedofs=%u; ebytes=%u\n", (unsigned)currfpos, (unsigned)wantedofs, wlk.extbytes);
1377 #endif
1378 for (unsigned ebidx = 0; ebidx < wlk.blocksused; ++ebidx) {
1379 if (wlk.extbytes == 0) break;
1380 // process this block
1381 const uint16_t secsize = geom->secsize; // cache it
1382 const uint16_t blkidx = wlk.blocks[ebidx];
1383 for (unsigned bofs = 0; bofs < blksize; bofs += secsize) {
1384 // read block sector
1385 if (wlk.extbytes == 0) break;
1386 size_t seccopy = (secsize <= wlk.extbytes ? secsize : wlk.extbytes);
1387 #ifdef P3DSK_DEBUG_FILE_READER
1388 fprintf(stderr, "....processing block; bofs=%u; seccopy=%u/%u; currfpos=%u; wantedofs=%u; ebytes=%u; dpos=%u; bytecount=%d\n", bofs, seccopy, secsize, (unsigned)currfpos, (unsigned)wantedofs, wlk.extbytes, (unsigned)dpos, bytecount);
1389 #endif
1390 wlk.extbytes -= seccopy;
1391 // skip this sector if we don't need it
1392 if (currfpos+seccopy <= wantedofs) {
1393 currfpos += seccopy;
1394 continue;
1396 // empty block (hole)?
1397 if (blkidx == 0) {
1398 // skip unneeded bytes
1399 if (currfpos < wantedofs) {
1400 P3DSK_ASSERT(currfpos+seccopy > wantedofs);
1401 const size_t skip = wantedofs-currfpos;
1402 seccopy -= skip;
1403 currfpos += skip;
1405 // copy bytes
1406 P3DSK_ASSERT(seccopy != 0);
1407 P3DSK_ASSERT(currfpos >= wantedofs);
1408 if (seccopy >= bytecount) {
1409 memset(dest+dpos, 0, (unsigned)bytecount);
1410 dpos += (unsigned)bytecount;
1411 return (int)dpos; // done with reading
1413 memset(dest+dpos, 0, seccopy);
1414 currfpos += seccopy;
1415 dpos += seccopy;
1416 bytecount -= (int)seccopy;
1417 } else {
1418 // sanity check
1419 if (blkidx < geom->dirblocks || blkidx >= geom->maxblocks) return FLPERR_SHIT;
1420 // calculate logical sector
1421 const int lsidx = p3dskCalcLogicalSectorForBlock(geom, (int)blkidx, (int)bofs);
1422 if (lsidx < 0) return FLPERR_SHIT;
1423 // calculate physical sector offset
1424 int psofs = p3dskCalcPhysSectorOffsetForBlock(geom, (int)blkidx, (int)bofs);
1425 if (psofs < 0) return FLPERR_SHIT;
1426 // read logical sector
1427 const uint8_t *secdata = p3dskGetLogicalSector(nfo->p3d, lsidx);
1428 if (secdata == NULL) return FLPERR_SHIT;
1429 // skip unneeded bytes
1430 if (currfpos < wantedofs) {
1431 P3DSK_ASSERT(currfpos+seccopy > wantedofs);
1432 const size_t skip = wantedofs-currfpos;
1433 seccopy -= skip;
1434 secdata += skip;
1435 currfpos += skip;
1437 // copy bytes
1438 P3DSK_ASSERT(seccopy != 0);
1439 P3DSK_ASSERT(currfpos >= wantedofs);
1440 if (seccopy >= bytecount) {
1441 memcpy(dest+dpos, secdata, (unsigned)bytecount);
1442 dpos += (unsigned)bytecount;
1443 return (int)dpos; // done with reading
1445 memcpy(dest+dpos, secdata, seccopy);
1446 currfpos += seccopy;
1447 dpos += seccopy;
1448 bytecount -= (int)seccopy;
1452 } while (bytecount > 0);
1453 return (int)dpos;
1457 //==========================================================================
1459 // p3dskReadFileHeader
1461 // read and validate +3DOS file header
1462 // returns error code
1464 //==========================================================================
1465 int p3dskReadFileHeader (const P3DskFileInfo *nfo, P3DskFileHeader *hdr) {
1466 if (!hdr) return FLPERR_SHIT;
1467 memset(hdr, 0, sizeof(P3DskFileHeader));
1468 if (!nfo) return FLPERR_SHIT;
1469 uint8_t buf[P3DSK_FILE_HEADER_SIZE];
1470 int rd = p3dskReadFile(nfo, buf, 0, P3DSK_FILE_HEADER_SIZE);
1471 if (rd < 0) return rd;
1472 if (rd != P3DSK_FILE_HEADER_SIZE) return FLPERR_NOHEADER;
1473 // check signature
1474 if (memcmp(buf, p3doshdrsign, 8) != 0) return FLPERR_NOHEADER;
1475 // check checksum
1476 uint8_t csum = 0;
1477 for (unsigned f = 0; f < (unsigned)(P3DSK_FILE_HEADER_SIZE-1); ++f) csum += buf[f];
1478 if (csum != buf[P3DSK_FILE_HEADER_SIZE-1]) return FLPERR_NOHEADER;
1479 // header seems to be ok, copy it to `hdr`
1480 hdr->issue = buf[9];
1481 hdr->version = buf[10];
1482 hdr->filesize = buf[11]|(((uint32_t)buf[12])<<8)|(((uint32_t)buf[13])<<16)|(((uint32_t)buf[14])<<24);
1483 hdr->bastype = buf[15+0];
1484 hdr->baslength = buf[15+1]|(((uint16_t)buf[15+2])<<8);
1485 hdr->basaddr = buf[15+3]|(((uint16_t)buf[15+4])<<8);
1486 hdr->basvarsofs = buf[15+5]|(((uint16_t)buf[15+6])<<8);
1487 hdr->valid = 1;
1488 return FLPERR_OK;
1492 //==========================================================================
1494 // p3dskWriteFileHeader
1496 // write file header to the first 128 bytes of the file
1497 // returns error code
1499 //==========================================================================
1500 int p3dskWriteFileHeader (P3DskFileInfo *nfo, const P3DskFileHeader *hdr) {
1501 if (!nfo || !hdr || !hdr->valid) return FLPERR_SHIT;
1502 uint8_t hbuf[P3DSK_FILE_HEADER_SIZE];
1503 memset(hbuf, 0, sizeof(hbuf));
1504 // put signature
1505 memcpy(hbuf, p3doshdrsign, 8);
1506 hbuf[8] = 0x1Au;
1507 hbuf[9] = hdr->issue;
1508 hbuf[10] = hdr->version;
1509 hbuf[11] = hdr->filesize&0xFFu;
1510 hbuf[12] = (hdr->filesize>>8)&0xFFu;
1511 hbuf[13] = (hdr->filesize>>16)&0xFFu;
1512 hbuf[14] = (hdr->filesize>>24)&0xFFu;
1513 // copy basic header
1514 hbuf[15+0] = hdr->bastype;
1515 hbuf[15+1] = hdr->baslength&0xFFu;
1516 hbuf[15+2] = (hdr->baslength>>8)&0xFFu;
1517 hbuf[15+3] = hdr->basaddr&0xFFu;
1518 hbuf[15+4] = (hdr->basaddr>>8)&0xFFu;
1519 hbuf[15+5] = hdr->basvarsofs&0xFFu;
1520 hbuf[15+6] = (hdr->basvarsofs>>8)&0xFFu;
1521 // checksum
1522 uint8_t csum = 0;
1523 for (unsigned f = 0; f < (unsigned)(P3DSK_FILE_HEADER_SIZE-1); ++f) csum += hbuf[f];
1524 hbuf[P3DSK_FILE_HEADER_SIZE-1] = csum;
1525 // write it
1526 int res = p3dskWriteFile(nfo, hbuf, 0, P3DSK_FILE_HEADER_SIZE);
1527 if (res < 0) return res;
1528 return (res == P3DSK_FILE_HEADER_SIZE ? FLPERR_OK : FLPERR_SHIT);
1533 //**************************************************************************
1535 // +3DOS file grow/shrink
1537 //**************************************************************************
1539 //==========================================================================
1541 // p3dskBuildBlockBitmap
1543 // build bitmap of used disk blocks
1544 // returns dynamically allocated data or NULL on error
1546 //==========================================================================
1547 uint8_t *p3dskBuildBlockBitmap (Floppy *flp, P3DskGeometry *geom) {
1548 if (!flp || !geom || !geom->sides) return NULL;
1549 uint8_t *bmp = malloc(geom->maxblocks);
1550 memset(bmp, 0, geom->maxblocks);
1551 // mark directory as used
1552 memset(bmp, 1, geom->dirblocks);
1553 // scan directory, mark all used blocks
1554 P3DiskInfo p3d;
1555 p3d.flp = flp;
1556 memcpy(&p3d.geom, geom, sizeof(P3DskGeometry));
1557 for (unsigned dirext = 0; dirext < geom->maxdirentries; ++dirext) {
1558 uint8_t *de = p3dskGetDirEntryPtr(&p3d, dirext);
1559 if (!de) { free(bmp); return NULL; }
1560 // 0-15: user #n file
1561 // 16-31: user #n for P2DOS, password extent for CP/M 3+
1562 // 32: disk label
1563 // 33: time stamp
1564 // 0xE5: unused
1565 // users are not supported, but allow files for users 0..15
1566 if (de[0] == 0xE5u) continue; // unused
1567 if (de[0] > 33) { free(bmp); return NULL; } // oops
1568 // ignore allocated blocks for password extents and disk labels
1569 if (de[0] > 16) continue;
1570 // allow holes
1571 uint16_t rn = de[15]*128;
1572 // scan blocks
1573 if (geom->maxblocks < 256) {
1574 if (rn/geom->blocksize > 16) { free(bmp); return NULL; }
1575 const unsigned blkused = (rn+geom->blocksize-1u)/geom->blocksize;
1576 // 8-bit block numbers
1577 for (unsigned bc = 0; bc < blkused; ++bc) {
1578 uint16_t bnum = de[16+bc];
1579 if (!bnum) continue;
1580 if (bnum < geom->dirblocks || bnum >= geom->maxblocks) { free(bmp); return NULL; }
1581 //if (bmp[bnum]) fprintf(stderr, "DISK BLOCK #%d is doubly allocated! deidx=%u\n", bnum, dirext);
1582 bmp[bnum] = 1; // mask as used
1584 } else {
1585 // 16-bit block numbers
1586 if (rn/geom->blocksize > 8) { free(bmp); return NULL; }
1587 const unsigned blkused = (rn+geom->blocksize-1u)/geom->blocksize;
1588 for (unsigned bc = 0; bc < blkused; ++bc) {
1589 uint16_t bnum = de[16+(bc<<1)]|((uint16_t)(de[17+(bc<<1)]));
1590 if (!bnum) continue;
1591 if (bnum < geom->dirblocks || bnum >= geom->maxblocks) { free(bmp); return NULL; }
1592 //if (bmp[bnum]) fprintf(stderr, "DISK BLOCK #%d is doubly allocated! deidx=%u\n", bnum, dirext);
1593 bmp[bnum] = 1; // mask as used
1597 return bmp;
1601 //==========================================================================
1603 // p3dskFindLastExtent
1605 // <0: error
1607 //==========================================================================
1608 static int p3dskFindLastExtent (P3DiskInfo *p3d, const void *namebuf/*[11]*/, uint16_t currextidx) {
1609 if (!p3d) return FLPERR_SHIT;
1610 Floppy *flp = p3d->flp;
1611 P3DskGeometry *geom = &p3d->geom;
1612 if (!flp || !geom || !flp->insert || !geom->sides || !namebuf) return FLPERR_SHIT;
1613 uint16_t res = currextidx;
1614 uint16_t nenum = 0;
1615 for (;;) {
1616 int found = 0;
1617 for (unsigned f = 0; f < geom->maxdirentries; ++f) {
1618 const uint16_t pex = currextidx;
1619 const uint8_t *de = p3dskGetDirEntryPtr(p3d, currextidx);
1620 currextidx = (currextidx+1)%geom->maxdirentries;
1621 if (!de) return FLPERR_SHIT;
1622 if (de[0] != 0) continue; // allow only USER 0 files
1623 // get extent number
1624 // bit 5-7 of Xl are 0, bit 0-4 store the lower bits of the extent number.
1625 // bit 6 and 7 of Xh are 0, bit 0-5 store the higher bits of the extent number.
1626 if (de[12]&0x70u) continue; // check bits 5-7
1627 if (de[14]&0xc0u) continue; // check bits 6-7
1628 const uint16_t eidx = de[12]|(uint16_t)(de[14]<<5);
1629 if (eidx != nenum) continue;
1630 // compare name
1631 if (cpmNameEqu(de+1, namebuf) == 0) continue;
1632 // good extent
1633 res = pex;
1634 ++nenum;
1635 found = 1;
1637 if (!found) break;
1639 return res;
1643 typedef struct {
1644 unsigned maxblk;
1645 unsigned blkused;
1646 uint16_t rn;
1647 uint16_t blocks[16];
1648 } P3DskExtBlocks;
1651 //==========================================================================
1653 // p3dskParseExtentBlocks
1655 //==========================================================================
1656 static int p3dskParseExtentBlocks (P3DskGeometry *geom, const uint8_t *de, P3DskExtBlocks *blist) {
1657 if (!blist || !de || !geom || !geom->sides) return FLPERR_SHIT;
1658 blist->rn = de[15]*128;
1659 if (geom->maxblocks < 256) {
1660 blist->maxblk = 16;
1661 if (blist->rn/geom->blocksize > 16) return FLPERR_SHIT;
1662 blist->blkused = (blist->rn+geom->blocksize-1u)/geom->blocksize;
1663 // 8-bit block numbers
1664 for (unsigned bc = 0; bc < blist->blkused; ++bc) {
1665 const uint16_t bnum = de[16+bc];
1666 if (bnum) {
1667 if (bnum < geom->dirblocks || bnum >= geom->maxblocks) return FLPERR_SHIT;
1669 blist->blocks[bc] = bnum;
1671 } else {
1672 // 16-bit block numbers
1673 blist->maxblk = 8;
1674 if (blist->rn/geom->blocksize > 8) return FLPERR_SHIT;
1675 blist->blkused = (blist->rn+geom->blocksize-1u)/geom->blocksize;
1676 for (unsigned bc = 0; bc < blist->blkused; ++bc) {
1677 uint16_t bnum = de[16+(bc<<1)]|((uint16_t)(de[17+(bc<<1)]));
1678 if (bnum) {
1679 if (bnum < geom->dirblocks || bnum >= geom->maxblocks) return FLPERR_SHIT;
1681 blist->blocks[bc] = bnum;
1684 // +3DOS is not using this, but why not?
1685 if (de[13] && de[13] <= 128 && de[13] <= blist->rn) blist->rn -= 128-de[13];
1686 return FLPERR_OK;
1690 //==========================================================================
1692 // p3dskWriteExtentBlocks
1694 //==========================================================================
1695 static int p3dskWriteExtentBlocks (P3DiskInfo *p3d, uint8_t *de, const P3DskExtBlocks *blist, int lastext) {
1696 if (!p3d) return FLPERR_SHIT;
1697 Floppy *flp = p3d->flp;
1698 P3DskGeometry *geom = &p3d->geom;
1699 if (!flp || !flp->insert || flp->protect || !blist || !de || !geom || !geom->sides) return FLPERR_SHIT;
1700 if (geom->maxblocks < 256) {
1701 if (blist->rn/geom->blocksize > 16) return FLPERR_SHIT;
1702 //blist->blkused = (blist->rn+geom->blocksize-1u)/geom->blocksize;
1703 // 8-bit block numbers
1704 for (unsigned bc = 0; bc < blist->blkused; ++bc) {
1705 de[16+bc] = blist->blocks[bc];
1707 } else {
1708 // 16-bit block numbers
1709 if (blist->rn/geom->blocksize > 8) return FLPERR_SHIT;
1710 //blist->blkused = (blist->rn+geom->blocksize-1u)/geom->blocksize;
1711 for (unsigned bc = 0; bc < blist->blkused; ++bc) {
1712 const uint16_t bnum = blist->blocks[bc];
1713 de[16+(bc<<1)] = bnum&0xffU;
1714 de[17+(bc<<1)] = (bnum>>8)&0xffU;
1717 uint16_t recs = (blist->rn+127u)/128u;
1718 de[15] = recs&0xffU;
1719 // +3DOS is not using this, but why not?
1720 if (blist->rn%128u) {
1721 de[13] = blist->rn%128u;
1722 } else {
1723 de[13] = 0;
1725 return p3dskUpdateDirEntryCRC(p3d, lastext);
1729 //==========================================================================
1731 // p3dskGrowFile
1733 //==========================================================================
1734 int p3dskGrowFile (P3DskFileInfo *nfo, int newsize) {
1735 if (!nfo || !nfo->p3d || newsize < 0) return FLPERR_SHIT;
1736 Floppy *flp = nfo->p3d->flp;
1737 P3DskGeometry *geom = &nfo->p3d->geom;
1738 if (!flp || !geom || !flp->insert || flp->protect || !geom->sides) return FLPERR_SHIT;
1739 if (!nfo->mask[0] || !nfo->name[0]) return FLPERR_SHIT;
1740 if ((unsigned)newsize <= nfo->size) return FLPERR_OK;
1741 // build name buffer
1742 char namebuf[11];
1743 memcpy(namebuf, nfo->nameonly, 8);
1744 memcpy(namebuf+8, nfo->extonly, 3);
1745 // block bitmap (will be requested if necessary)
1746 uint8_t *bmp = NULL;
1747 // find last file extent
1748 int lastext = p3dskFindLastExtent(nfo->p3d, namebuf, nfo->firstextent);
1749 if (lastext < 0) return FLPERR_SHIT;
1750 #ifdef P3DSK_DEBUG_FILE_GROW
1751 fprintf(stderr, "GF: name[%.11s]; firstext=%u; lastext=%u\n", namebuf, nfo->firstextent, lastext);
1752 #endif
1753 uint8_t *de = p3dskGetDirEntryPtr(nfo->p3d, lastext);
1754 if (!de) return FLPERR_SHIT;
1755 // first, check if we can simply use the last record
1756 size_t toadd = (unsigned)newsize-nfo->size;
1757 #ifdef P3DSK_DEBUG_FILE_GROW
1758 fprintf(stderr, "GF: toadd=%u\n", (unsigned)toadd);
1759 #endif
1760 // +3DOS doesn't support this, but meh, why not?
1761 if (de[13] > 0 && de[13] < 128) {
1762 int left = 128-de[13];
1763 #ifdef P3DSK_DEBUG_FILE_GROW
1764 fprintf(stderr, "GF: using Bc; left=%d\n", left);
1765 #endif
1766 if (toadd <= left) {
1767 // yay
1768 nfo->size += toadd;
1769 de[13] += toadd;
1770 if (de[13] >= 128) de[13] = 0; // for compatibility
1771 return p3dskUpdateDirEntryCRC(nfo->p3d, lastext);
1773 // still can add a little
1774 nfo->size += left;
1775 toadd -= left;
1776 de[13] = 0; // for compatibility
1777 if (p3dskUpdateDirEntryCRC(nfo->p3d, lastext) != FLPERR_OK) return FLPERR_SHIT;
1779 P3DSK_ASSERT(toadd != 0);
1780 // ok, partial last recrod is filled, go with more records
1781 P3DskExtBlocks blist;
1782 for (;;) {
1783 // get block list
1784 if (p3dskParseExtentBlocks(geom, de, &blist) != FLPERR_OK) {
1785 if (bmp) free(bmp);
1786 return FLPERR_SHIT;
1788 #ifdef P3DSK_DEBUG_FILE_GROW
1789 fprintf(stderr, "GF: got block list for extent %d; rn=%u; blkused=%u; maxblk=%u\n", lastext, blist.rn, blist.blkused, blist.maxblk);
1790 #endif
1791 while (toadd != 0) {
1792 // see if we have any free records in the last block
1793 if (blist.rn && blist.rn%geom->blocksize != 0) {
1794 // sanity check
1795 if (blist.blkused >= blist.maxblk) {
1796 if (bmp) free(bmp);
1797 return FLPERR_SHIT;
1799 // number of free bytes in the last block
1800 unsigned left = geom->blocksize-blist.rn%geom->blocksize;
1801 P3DSK_ASSERT(left != 0);
1802 #ifdef P3DSK_DEBUG_FILE_GROW
1803 fprintf(stderr, "GF: filling last used extent block; left=%u; toadd=%u\n", left, (unsigned)toadd);
1804 #endif
1805 if (left >= toadd) {
1806 // fits in the block
1807 nfo->size += toadd;
1808 blist.rn += toadd;
1809 int res = p3dskWriteExtentBlocks(nfo->p3d, de, &blist, lastext);
1810 if (bmp) free(bmp);
1811 return res;
1813 // fill last block up to the block boundary
1814 blist.rn += left;
1815 nfo->size += left;
1816 toadd -= left;
1817 P3DSK_ASSERT(left != 0);
1818 P3DSK_ASSERT(blist.rn%geom->blocksize == 0);
1820 // we need a new block; do we have a room for it in the current extent?
1821 if (blist.blkused >= blist.maxblk) {
1822 #ifdef P3DSK_DEBUG_FILE_GROW
1823 fprintf(stderr, "GF: no more free blocks in this extent\n");
1824 #endif
1825 // nope; update extent and get out
1826 if (p3dskWriteExtentBlocks(nfo->p3d, de, &blist, lastext) != FLPERR_OK) {
1827 if (bmp) free(bmp);
1828 return FLPERR_SHIT;
1830 break;
1832 // we have a room for a new block, allocate it
1833 if (!bmp) {
1834 bmp = p3dskBuildBlockBitmap(flp, geom);
1835 if (!bmp) {
1836 // oops
1837 p3dskUpdateDirEntryCRC(nfo->p3d, lastext); // i don't care about errors here
1838 if (bmp) free(bmp);
1839 return FLPERR_SHIT;
1842 unsigned bidx = 0;
1843 for (; bidx < geom->maxblocks; ++bidx) if (!bmp[bidx]) break;
1844 if (bidx >= geom->maxblocks) {
1845 // no blocks available, alas
1846 p3dskUpdateDirEntryCRC(nfo->p3d, lastext); // i don't care about errors here
1847 if (bmp) free(bmp);
1848 return FLPERR_NOSPACE;
1850 #ifdef P3DSK_DEBUG_FILE_GROW
1851 fprintf(stderr, "GF: allocated new block #%u\n", bidx);
1852 #endif
1853 // mark this block as allocated
1854 bmp[bidx] = 1;
1855 // put it into blocks list
1856 blist.blocks[blist.blkused++] = bidx;
1857 // allocate block (or its part)
1858 if (toadd <= geom->blocksize) {
1859 // completely fits
1860 blist.rn += toadd;
1861 nfo->size += toadd;
1862 int res = p3dskWriteExtentBlocks(nfo->p3d, de, &blist, lastext);
1863 if (bmp) free(bmp);
1864 return res;
1866 // need more than one block, use this one completely
1867 blist.rn += geom->blocksize;
1868 nfo->size += geom->blocksize;
1869 toadd -= geom->blocksize;
1870 P3DSK_ASSERT(toadd != 0);
1871 if (p3dskWriteExtentBlocks(nfo->p3d, de, &blist, lastext) != FLPERR_OK) {
1872 if (bmp) free(bmp);
1873 return FLPERR_SHIT;
1876 // we will come here only when we need a new extent
1877 uint16_t nxnum = (de[12]|(uint16_t)(de[14]<<5))+1u;
1878 // look for a free extent (reuse time stamps and other crap too)
1879 int found = 0;
1880 for (unsigned f = 0; f < geom->maxdirentries; ++f) {
1881 lastext = (lastext+1)%geom->maxdirentries;
1882 de = p3dskGetDirEntryPtr(nfo->p3d, lastext);
1883 if (!de) {
1884 if (bmp) free(bmp);
1885 return FLPERR_SHIT;
1887 if (de[0] <= 15) continue; // USER n files, preserve
1888 // this is either unused, or timestamp
1889 // build new file extent
1890 de[0] = 0; // always USER 0
1891 // set name
1892 memcpy(de+1, namebuf, 11);
1893 // set extent number
1894 de[12] = nxnum&0x1fu;
1895 de[14] = (nxnum>>5)&0x3fu;
1896 // set used records (this will be fixed on the next loop iteration)
1897 de[13] = de[15] = 0;
1898 // clear allocated blocks
1899 memset(de+16, 0, 16);
1900 if (p3dskUpdateDirEntryCRC(nfo->p3d, lastext) != FLPERR_OK) {
1901 if (bmp) free(bmp);
1902 return FLPERR_SHIT;
1904 found = 1;
1905 break;
1907 if (!found) {
1908 // alas, no more entries in a directory
1909 if (bmp) free(bmp);
1910 return FLPERR_MANYFILES;
1912 // loop again
1914 // the thing that should not be
1915 abort();
1920 //==========================================================================
1922 // p3dskWriteFile
1924 //==========================================================================
1925 int p3dskWriteFile (P3DskFileInfo *nfo, const void *buf, int ofs, int bytecount) {
1926 if (!nfo || !nfo->p3d) return FLPERR_SHIT;
1927 Floppy *flp = nfo->p3d->flp;
1928 P3DskGeometry *geom = &nfo->p3d->geom;
1929 if (!flp || !geom || !flp->insert || flp->protect || !geom->sides) return FLPERR_SHIT;
1930 if (!nfo->mask[0] || !nfo->name[0]) return FLPERR_SHIT;
1931 if (ofs < 0 || bytecount < 0) return FLPERR_SHIT;
1932 if (bytecount == 0) return 0;
1933 if (!buf) return FLPERR_SHIT;
1934 const int disksize = geom->maxblocks*geom->blocksize;
1935 // some easy checks
1936 if (ofs >= disksize || bytecount > disksize) return FLPERR_NOSPACE; //FIXME: some other error code?
1937 if (ofs+bytecount > disksize) return FLPERR_NOSPACE;
1938 // grow file
1939 int opres = p3dskGrowFile(nfo, ofs+bytecount);
1940 if (opres != FLPERR_OK) return opres;
1941 // walk the file to the required extent, and write from there
1942 // if we will encounter a hole, the writer will fail
1943 const size_t wantedofs = (unsigned)ofs;
1944 size_t currfpos = 0;
1945 const uint8_t *src = (const uint8_t *)buf;
1946 size_t spos = 0;
1947 const uint16_t blksize = geom->blocksize; // cache it
1948 // walk extents
1949 P3DskExtentWalker wlk;
1950 if (p3dskInitExtentWalker(&wlk, nfo) != FLPERR_OK) return FLPERR_SHIT;
1951 do {
1952 int neres = p3dskNextExtent(&wlk);
1953 if (neres < 0) return neres;
1954 if (neres == 0) break;
1955 #ifdef P3DSK_DEBUG_FILE_WRITER
1956 fprintf(stderr, ".extent; currfpos=%u; wantedofs=%u; bcount=%u; ebytes=%u\n", (unsigned)currfpos, (unsigned)wantedofs, wlk.blocksused, wlk.extbytes);
1957 #endif
1958 // skip this extent as a whole if it is not interesting
1959 if (currfpos < wantedofs && currfpos+wlk.extbytes <= wantedofs) {
1960 // skip this extent
1961 #ifdef P3DSK_DEBUG_FILE_WRITER
1962 fprintf(stderr, "..skip extent; currfpos=%u; wantedofs=%u; ebytes=%u\n", (unsigned)currfpos, (unsigned)wantedofs, wlk.extbytes);
1963 #endif
1964 currfpos += wlk.extbytes;
1965 continue;
1967 // universal code here, why not?
1968 #ifdef P3DSK_DEBUG_FILE_WRITER
1969 fprintf(stderr, "..processing extent; currfpos=%u; wantedofs=%u; ebytes=%u\n", (unsigned)currfpos, (unsigned)wantedofs, wlk.extbytes);
1970 #endif
1971 for (unsigned ebidx = 0; ebidx < wlk.blocksused; ++ebidx) {
1972 if (wlk.extbytes == 0) break;
1973 // process this block
1974 const uint16_t secsize = geom->secsize; // cache it
1975 const uint16_t blkidx = wlk.blocks[ebidx];
1976 uint16_t wlkbleft = wlk.extbytes;
1977 for (unsigned bofs = 0; bofs < blksize; bofs += secsize) {
1978 // read block sector
1979 if (wlkbleft == 0) break;
1980 size_t seccopy = (secsize <= wlkbleft ? secsize : wlkbleft);
1981 #ifdef P3DSK_DEBUG_FILE_WRITER
1982 fprintf(stderr, "...processing block; bofs=%u; bidx=%u; seccopy=%u/%u; currfpos=%u; wantedofs=%u; ebytes=%u; spos=%u; bytecount=%d\n", bofs, blkidx, seccopy, secsize, (unsigned)currfpos, (unsigned)wantedofs, wlkbleft, (unsigned)spos, bytecount);
1983 #endif
1984 wlkbleft -= seccopy;
1985 // skip this sector if we don't need it
1986 if (currfpos+seccopy <= wantedofs) {
1987 currfpos += seccopy;
1988 continue;
1990 // empty block (hole)?
1991 if (blkidx == 0) return FLPERR_SHIT; // we cannot work with holes (yet)
1992 // sanity check
1993 if (blkidx < geom->dirblocks || blkidx >= geom->maxblocks) return FLPERR_SHIT;
1994 // calculate logical sector
1995 const int lsidx = p3dskCalcLogicalSectorForBlock(geom, (int)blkidx, (int)bofs);
1996 if (lsidx < 0) return FLPERR_SHIT;
1997 // calculate physical sector offset
1998 int psofs = p3dskCalcPhysSectorOffsetForBlock(geom, (int)blkidx, (int)bofs);
1999 if (psofs < 0) return FLPERR_SHIT;
2000 // read logical sector
2001 #ifdef P3DSK_DEBUG_FILE_WRITER
2002 fprintf(stderr, "....reading lsec #%d (ofs=%d)\n", lsidx, psofs);
2003 #endif
2004 uint8_t *secdata = p3dskGetLogicalSector(nfo->p3d, lsidx);
2005 if (secdata == NULL) return FLPERR_SHIT;
2006 // skip unneeded bytes
2007 if (currfpos < wantedofs) {
2008 P3DSK_ASSERT(currfpos+seccopy > wantedofs);
2009 const size_t skip = wantedofs-currfpos;
2010 seccopy -= skip;
2011 secdata += skip;
2012 currfpos += skip;
2014 // copy bytes
2015 P3DSK_ASSERT(seccopy != 0);
2016 P3DSK_ASSERT(currfpos >= wantedofs);
2017 if (seccopy >= bytecount) {
2018 #ifdef P3DSK_DEBUG_FILE_WRITER
2019 fprintf(stderr, "....writing final #%d bytes\n", bytecount);
2020 #endif
2021 memcpy(secdata, src+spos, (unsigned)bytecount);
2022 spos += (unsigned)bytecount;
2023 // update data CRC
2024 opres = p3dskUpdateLogicalSectorCRC(nfo->p3d, lsidx);
2025 if (opres != FLPERR_OK) return opres;
2026 return (int)spos; // done with writing
2028 #ifdef P3DSK_DEBUG_FILE_WRITER
2029 fprintf(stderr, "....writing #%d bytes\n", seccopy);
2030 #endif
2031 memcpy(secdata, src+spos, seccopy);
2032 currfpos += seccopy;
2033 spos += seccopy;
2034 bytecount -= (int)seccopy;
2035 // update data CRC
2036 opres = p3dskUpdateLogicalSectorCRC(nfo->p3d, lsidx);
2037 if (opres != FLPERR_OK) return opres;
2040 } while (bytecount > 0);
2041 return (int)spos;
2046 //**************************************************************************
2048 // CP/M bootable disks
2050 //**************************************************************************
2052 //==========================================================================
2054 // p3dskIsBootable
2056 // check if the given disk is CP/M bootable (ignores +3DOS boot methods)
2057 // returns error code or boolean
2059 //==========================================================================
2060 int p3dskIsBootable (const P3DiskInfo *p3d) {
2061 if (!p3d) return FLPERR_SHIT;
2062 Floppy *flp = p3d->flp;
2063 const P3DskGeometry *geom = &p3d->geom;
2064 if (!flp || !geom || !flp->insert || !geom->sides) return FLPERR_SHIT;
2065 //if (!geom->restracks) return FLPERR_SHIT; // this disk has no reserved sectors, so it cannot be booted
2066 // read first sector
2067 const uint8_t *bsec = flpGetSectorDataPtr(flp, 0, geom->firstsector);
2068 if (!bsec) return FLPERR_SHIT;
2069 // calc checksum
2070 uint8_t csum = 0;
2071 for (unsigned f = 0; f < geom->secsize; ++f) csum += bsec[f];
2072 return (csum == 3);
2076 //==========================================================================
2078 // p3dskWriteBootableChecksum
2080 // modifies the first disk sector checksum to
2081 // indicate a "CP/M bootable" disk
2082 // returns error code
2084 //==========================================================================
2085 int p3dskWriteBootableChecksum (P3DiskInfo *p3d) {
2086 if (!p3d) return FLPERR_SHIT;
2087 Floppy *flp = p3d->flp;
2088 P3DskGeometry *geom = &p3d->geom;
2089 if (!flp || !geom || !flp->insert || !geom->sides) return FLPERR_SHIT;
2090 if (!geom->restracks) return FLPERR_SHIT; // this disk has no reserved sectors, so it cannot be booted
2091 // read first sector
2092 uint8_t *bsec = flpGetSectorDataPtr(flp, 0, geom->firstsector);
2093 if (!bsec) return FLPERR_SHIT;
2094 // reset csum byte
2095 //const uint8_t ob = bsec[15];
2096 //bsec[15] = 0;
2097 // calc checksum
2098 uint8_t csum = 0;
2099 for (unsigned f = 0; f < geom->secsize; ++f) if (f != 15) csum += bsec[f];
2100 // ensure that it is right
2101 csum = ((csum^0xFFu)+4u)&0xffU;
2102 if (bsec[15] == csum) return FLPERR_OK; // already valid
2103 if (flp->protect) return FLPERR_SHIT;
2104 bsec[15] = csum;
2105 return flpFixSectorDataCRC(flp, 0, geom->firstsector);
2109 //==========================================================================
2111 // p3dskResetBootableChecksum
2113 // modifies the first disk sector checksum to
2114 // indicate a "CP/M non-bootable" disk
2115 // returns error code
2117 //==========================================================================
2118 int p3dskResetBootableChecksum (P3DiskInfo *p3d) {
2119 if (!p3d) return FLPERR_SHIT;
2120 Floppy *flp = p3d->flp;
2121 P3DskGeometry *geom = &p3d->geom;
2122 if (!flp || !geom || !flp->insert || !geom->sides) return FLPERR_SHIT;
2123 if (!geom->restracks) return FLPERR_SHIT; // this disk has no reserved sectors, so it cannot be booted
2124 // read first sector
2125 uint8_t *bsec = flpGetSectorDataPtr(flp, 0, geom->firstsector);
2126 if (!bsec) return FLPERR_SHIT;
2127 // reset csum byte
2128 //const uint8_t ob = bsec[15];
2129 //bsec[15] = 0;
2130 // calc checksum
2131 uint8_t csum = 0;
2132 for (unsigned f = 0; f < geom->secsize; ++f) if (f != 15) csum += bsec[f];
2133 // ensure that it is right
2134 csum = ((csum&0xFFu)+4)&0xffU;
2135 if (bsec[15] != csum) return FLPERR_OK; // already valid
2136 if (flp->protect) return FLPERR_SHIT;
2137 bsec[15] = csum^0xFFu;
2138 return flpFixSectorDataCRC(flp, 0, geom->firstsector);
2144 Directory entries - The directory is a sequence of directory entries
2145 (also called extents), which contain 32 bytes of the following structure:
2147 St F0 F1 F2 F3 F4 F5 F6 F7 E0 E1 E2 Xl Bc Xh Rc
2148 Al Al Al Al Al Al Al Al Al Al Al Al Al Al Al Al
2150 St is the status; possible values are:
2151 0-15: used for file, status is the user number
2152 16-31: used for file, status is the user number (P2DOS) or used for password extent (CP/M 3 or higher)
2153 32: disc label
2154 33: time stamp (P2DOS)
2155 0xE5: unused
2157 F0-E2 are the file name and its extension. They may consist of any printable 7 bit ASCII character but:
2158 <>.,;:=?*[]
2160 The file name must not be empty, the extension may be empty. Both are
2161 padded with blanks. The highest bit of each character of the file name
2162 and extension is used as attribute. The attributes have the following
2163 meaning:
2165 F0: requires set wheel byte (Backgrounder II)
2166 F1: public file (P2DOS, ZSDOS), foreground-only command (Backgrounder II)
2167 F2: date stamp (ZSDOS), background-only commands (Backgrounder II)
2168 F7: wheel protect (ZSDOS)
2169 E0: read-only
2170 E1: system file
2171 E2: archived
2173 Public files (visible under each user number) are not supported by CP/M
2174 2.2, but there is a patch and some free CP/M clones support them
2175 without any patches.
2177 The wheel byte is (by default) the memory location at 0x4b. If it is
2178 zero, only non-privileged commands may be executed.
2180 Xl and Xh store the extent number. A file may use more than one
2181 directory entry, if it contains more blocks than an extent can hold. In
2182 this case, more extents are allocated and each of them is numbered
2183 sequentially with an extent number. If a physical extent stores more
2184 than 16k, it is considered to contain multiple logical extents, each
2185 pointing to 16k data, and the extent number of the last used logical
2186 extent is stored. Note: Some formats decided to always store only one
2187 logical extent in a physical extent, thus wasting extent space. CP/M
2188 2.2 allows 512 extents per file, CP/M 3 and higher allow up to 2048.
2189 Bit 5-7 of Xl are 0, bit 0-4 store the lower bits of the extent number.
2190 Bit 6 and 7 of Xh are 0, bit 0-5 store the higher bits of the extent
2191 number.
2193 Rc and Bc determine the length of the data used by this extent. The
2194 physical extent is divided into logical extents, each of them being 16k
2195 in size (a physical extent must hold at least one logical extent, e.g.
2196 a blocksize of 1024 byte with two-byte block pointers is not allowed).
2197 Rc stores the number of 128 byte records of the last used logical
2198 extent. Bc stores the number of bytes in the last used record. The
2199 value 0 means 128 for backward compatibility with CP/M 2.2, which did
2200 not support Bc. ISX records the number of unused instead of used bytes
2201 in Bc.
2203 Al stores block pointers. If the disk capacity minus boot tracks but
2204 including the directory area is less than 256 blocks, Al is interpreted
2205 as 8-bit values, otherwise as 16-bit little-endian values. Since the
2206 directory area is not subtracted, the directory area starts with block
2207 0 and files can never allocate block 0, which is why this value can be
2208 given a new meaning: A block pointer of 0 marks a hole in the file. If
2209 a hole covers the range of a full extent, the extent will not be allo-
2210 cated. In particular, the first extent of a file does not neccessarily
2211 have extent number 0. A file may not share blocks with other files, as
2212 its blocks would be freed if the other files were erased without a fol-
2213 lowing disk system reset. CP/M returns EOF when it reaches a hole,
2214 whereas UNIX returns zero-value bytes, which makes holes invisible.