1 /* #includes */ /*{{{C}}}*//*{{{*/
15 extern char **environ;
19 static struct tm *cpmtime(char lday, char hday, char hour, char min) /*{{{*/
22 unsigned long days=(lday&0xff)|((hday&0xff)<<8);
24 int md[12]={31,0,31,30,31,30,31,31,30,31,30,31};
27 tm.tm_min=((min>>4)&0xf)*10+(min&0xf);
28 tm.tm_hour=((hour>>4)&0xf)*10+(hour&0xf);
33 while (days>=(d=(((tm.tm_year%400)==0 || ((tm.tm_year%4)==0 && (tm.tm_year%100))) ? 366 : 365)))
38 md[1]=((tm.tm_year%400)==0 || ((tm.tm_year%4)==0 && (tm.tm_year%100))) ? 29 : 28;
39 while (days>=md[tm.tm_mon])
49 static void info(struct cpmSuperBlock *sb, const char *format, const char *image) /*{{{*/
54 msg="File system characteristics";
55 move(0,(COLS-strlen(msg))/2); printw(msg);
56 move(2,0); printw(" Image: %s",image);
57 move(3,0); printw(" Format: %s",format);
58 move(4,0); printw(" File system: ");
61 case CPMFS_DR22: printw("CP/M 2.2"); break;
62 case CPMFS_P2DOS: printw("P2DOS 2.3"); break;
63 case CPMFS_DR3: printw("CP/M Plus"); break;
66 move(6,0); printw(" Sector length: %d",sb->secLength);
67 move(7,0); printw(" Number of tracks: %d",sb->tracks);
68 move(8,0); printw(" Sectors per track: %d",sb->sectrk);
70 move(10,0);printw(" Block size: %d",sb->blksiz);
71 move(11,0);printw("Number of directory entries: %d",sb->maxdir);
72 move(12,0);printw(" Logical sector skew: %d",sb->skew);
73 move(13,0);printw(" Number of system tracks: %d",sb->boottrk);
74 move(14,0);printw(" Logical extents per extent: %d",sb->extents);
75 move(15,0);printw(" Allocatable data blocks: %d",sb->size-(sb->maxdir*32+sb->blksiz-1)/sb->blksiz);
77 msg="Any key to continue";
78 move(23,(COLS-strlen(msg))/2); printw(msg);
82 static void map(struct cpmSuperBlock *sb) /*{{{*/
86 int secmap,pos,sys,directory;
90 move(0,(COLS-strlen(msg))/2); printw(msg);
92 secmap=(sb->tracks*sb->sectrk+80*18-1)/(80*18);
93 memset(bmap,' ',sizeof(bmap));
94 sys=sb->boottrk*sb->sectrk;
95 memset(bmap,'S',sys/secmap);
96 directory=(sb->maxdir*32+sb->secLength-1)/sb->secLength;
97 memset(bmap+sys/secmap,'D',directory/secmap);
98 memset(bmap+(sys+directory)/secmap,'.',sb->sectrk*sb->tracks/secmap);
100 for (pos=0; pos<(sb->maxdir*32+sb->secLength-1)/sb->secLength; ++pos)
104 Device_readSector(&sb->dev,sb->boottrk+pos/(sb->sectrk*sb->secLength),pos/sb->secLength,mapbuf);
105 for (entry=0; entry<sb->secLength/32 && (pos*sb->secLength/32)+entry<sb->maxdir; ++entry)
109 if (mapbuf[entry*32]>=0 && mapbuf[entry*32]<=(sb->type==CPMFS_P2DOS ? 31 : 15))
115 sector=mapbuf[entry*32+16+i]&0xff;
116 if (sb->size>=256) sector|=(((mapbuf[entry*32+16+ ++i]&0xff)<<8));
117 if (sector>0 && sector<=sb->size)
119 /* not entirely correct without the last extent record count */
120 sector=sector*(sb->blksiz/sb->secLength)+sb->sectrk*sb->boottrk;
121 memset(bmap+sector/secmap,'#',sb->blksiz/(sb->secLength*secmap));
128 for (pos=0; pos<sizeof(bmap); ++pos)
130 move(2+pos%18,pos/18);
133 move(21,0); printw("S=System area D=Directory area #=File data .=Free");
134 msg="Any key to continue";
135 move(23,(COLS-strlen(msg))/2); printw(msg);
139 static void data(struct cpmSuperBlock *sb, const char *buf, unsigned long int pos) /*{{{*/
141 int offset=(pos%sb->secLength)&~0x7f;
144 for (i=0; i<128; ++i)
146 move(4+(i>>4),(i&0x0f)*3+!!(i&0x8)); printw("%02x",buf[i+offset]&0xff);
147 if (pos%sb->secLength==i+offset) attron(A_REVERSE);
148 move(4+(i>>4),50+(i&0x0f)); printw("%c",isprint(buf[i+offset]) ? buf[i+offset] : '.');
151 move(4+((pos&0x7f)>>4),((pos&0x7f)&0x0f)*3+!!((pos&0x7f)&0x8)+1);
155 const char cmd[]="fsed.cpm";
157 int main(int argc, char *argv[]) /*{{{*/
159 /* variables */ /*{{{*/
160 const char *devopts=(const char*)0;
163 struct cpmSuperBlock drive;
164 struct cpmInode root;
165 const char *format=FORMAT;
173 /* parse options */ /*{{{*/
174 while ((c=getopt(argc,argv,"f:h?"))!=EOF) switch(c)
176 case 'f': format=optarg; break;
177 case 'T': devopts=optarg; break;
179 case '?': usage=1; break;
182 if (optind!=(argc-1)) usage=1;
183 else image=argv[optind];
187 fprintf(stderr,"Usage: fsed.cpm [-f format] image\n");
191 /* open image */ /*{{{*/
192 if ((err=Device_open(&drive.dev,image,O_RDONLY,devopts)))
194 fprintf(stderr,"%s: can not open %s (%s)\n",cmd,image,err);
197 cpmReadSuper(&drive,&root,format);
199 /* alloc sector buffers */ /*{{{*/
200 if ((buf=malloc(drive.secLength))==(char*)0 || (mapbuf=malloc(drive.secLength))==(char*)0)
202 fprintf(stderr,"fsed.cpm: can not allocate sector buffer (%s).\n",strerror(errno));
206 /* init curses */ /*{{{*/
221 /* display position and load data */ /*{{{*/
223 move(2,0); printw("Byte %8d (0x%08x) ",pos,pos);
224 if (pos<(drive.boottrk*drive.sectrk*drive.secLength))
226 printw("Physical sector %3d ",((pos/drive.secLength)%drive.sectrk)+1);
230 printw("Sector %3d ",((pos/drive.secLength)%drive.sectrk)+1);
231 printw("(physical %3d) ",drive.skewtab[(pos/drive.secLength)%drive.sectrk]+1);
233 printw("Offset %5d ",pos%drive.secLength);
234 printw("Track %5d",pos/(drive.secLength*drive.sectrk));
235 move(LINES-3,0); printw("N)ext track P)revious track");
236 move(LINES-2,0); printw("n)ext record p)revious record f)orward byte b)ackward byte");
237 move(LINES-1,0); printw("i)nfo q)uit");
240 if (pos<(drive.boottrk*drive.sectrk*drive.secLength))
242 err=Device_readSector(&drive.dev,pos/(drive.secLength*drive.sectrk),(pos/drive.secLength)%drive.sectrk,buf);
246 err=Device_readSector(&drive.dev,pos/(drive.secLength*drive.sectrk),drive.skewtab[(pos/drive.secLength)%drive.sectrk],buf);
250 move(4,0); printw("Data can not be read: %s",err);
256 if /* position before end of system area */ /*{{{*/
257 (pos<(drive.boottrk*drive.sectrk*drive.secLength))
261 msg="System area"; move(0,(COLS-strlen(msg))/2); printw(msg);
262 move(LINES-3,36); printw("F)orward 16 byte B)ackward 16 byte");
263 if (!reload) data(&drive,buf,pos);
266 case 'F': /* next 16 byte */ /*{{{*/
268 if (pos+16<(drive.sectrk*drive.tracks*(unsigned long)drive.secLength))
270 if (pos/drive.secLength!=(pos+16)/drive.secLength) reload=1;
276 case 'B': /* previous 16 byte */ /*{{{*/
280 if (pos/drive.secLength!=(pos-16)/drive.secLength) reload=1;
289 else if /* position before end of directory area */ /*{{{*/
290 (pos<(drive.boottrk*drive.sectrk*drive.secLength+drive.maxdir*32))
293 unsigned long entrystart=(pos&~0x1f)%drive.secLength;
294 int entry=(pos-(drive.boottrk*drive.sectrk*drive.secLength))>>5;
297 msg="Directory area"; move(0,(COLS-strlen(msg))/2); printw(msg);
298 move(LINES-3,36); printw("F)orward entry B)ackward entry");
300 move(13,0); printw("Entry %3d: ",entry);
301 if /* free or used directory entry */ /*{{{*/
302 ((buf[entrystart]>=0 && buf[entrystart]<=(drive.type==CPMFS_P2DOS ? 31 : 15)) || buf[entrystart]==(char)0xe5)
306 if (buf[entrystart]==(char)0xe5)
308 if (offset==0) attron(A_REVERSE);
312 else printw("Directory entry");
314 if (buf[entrystart]!=(char)0xe5)
317 if (offset==0) attron(A_REVERSE);
318 printw("%2d",buf[entrystart]);
325 if (offset==1+i) attron(A_REVERSE);
326 printw("%c",buf[entrystart+1+i]&0x7f);
329 printw(" Extension: ");
332 if (offset==9+i) attron(A_REVERSE);
333 printw("%c",buf[entrystart+9+i]&0x7f);
336 move(16,0); printw("Extent: %3d",((buf[entrystart+12]&0xff)+((buf[entrystart+14]&0xff)<<5))/drive.extents);
338 if (offset==12) attron(A_REVERSE);
339 printw("%2d",buf[entrystart+12]&0xff);
342 if (offset==14) attron(A_REVERSE);
343 printw("%2d",buf[entrystart+14]&0xff);
346 move(17,0); printw("Last extent record count: ");
347 if (offset==15) attron(A_REVERSE);
348 printw("%3d",buf[entrystart+15]&0xff);
350 move(18,0); printw("Last record byte count: ");
351 if (offset==13) attron(A_REVERSE);
352 printw("%3d",buf[entrystart+13]&0xff);
354 move(19,0); printw("Data blocks:");
357 unsigned int block=buf[entrystart+16+i]&0xff;
361 if (offset==16+i || offset==16+i+1) attron(A_REVERSE);
362 printw("%5d",block|(((buf[entrystart+16+ ++i]&0xff)<<8)));
368 if (offset==16+i) attron(A_REVERSE);
375 else if /* disc label */ /*{{{*/
376 (buf[entrystart]==0x20 && drive.type==CPMFS_DR3)
382 if (offset==0) attron(A_REVERSE);
383 printw("Disc label");
389 if (i+1==offset) attron(A_REVERSE);
390 printw("%c",buf[entrystart+1+i]&0x7f);
395 if (offset==12) attron(A_REVERSE);
396 printw("Label %s",buf[entrystart+12]&1 ? "set" : "not set");
397 printw(", password protection %s",buf[entrystart+12]&0x80 ? "set" : "not set");
400 printw("Bit 4,5,6: ");
401 if (offset==12) attron(A_REVERSE);
402 printw("Time stamp ");
403 if (buf[entrystart+12]&0x10) printw("on create, ");
404 else printw("not on create, ");
405 if (buf[entrystart+12]&0x20) printw("on modification, ");
406 else printw("not on modifiction, ");
407 if (buf[entrystart+12]&0x40) printw("on access");
408 else printw("not on access");
411 printw("Password: ");
416 if (offset==16+(7-i)) attron(A_REVERSE);
417 printable=(buf[entrystart+16+(7-i)]^buf[entrystart+13])&0x7f;
418 printw("%c",isprint(printable) ? printable : ' ');
421 printw(" XOR value: ");
422 if (offset==13) attron(A_REVERSE);
423 printw("0x%02x",buf[entrystart+13]&0xff);
427 tm=cpmtime(buf[entrystart+24],buf[entrystart+25],buf[entrystart+26],buf[entrystart+27]);
428 if (offset==24 || offset==25) attron(A_REVERSE);
429 strftime(s,sizeof(s),"%x",tm);
433 if (offset==26) attron(A_REVERSE);
434 printw("%2d",tm->tm_hour);
437 if (offset==27) attron(A_REVERSE);
438 printw("%02d",tm->tm_min);
440 printw(" Updated: ");
441 tm=cpmtime(buf[entrystart+28],buf[entrystart+29],buf[entrystart+30],buf[entrystart+31]);
442 if (offset==28 || offset==29) attron(A_REVERSE);
443 strftime(s,sizeof(s),"%x",tm);
447 if (offset==30) attron(A_REVERSE);
448 printw("%2d",tm->tm_hour);
451 if (offset==31) attron(A_REVERSE);
452 printw("%02d",tm->tm_min);
456 else if /* time stamp */ /*{{{*/
457 (buf[entrystart]==0x21 && (drive.type==CPMFS_P2DOS || drive.type==CPMFS_DR3))
462 if (offset==0) attron(A_REVERSE);
463 printw("Time stamps");
466 printw("3rd last extent: Created/Accessed ");
467 tm=cpmtime(buf[entrystart+1],buf[entrystart+2],buf[entrystart+3],buf[entrystart+4]);
468 if (offset==1 || offset==2) attron(A_REVERSE);
469 strftime(s,sizeof(s),"%x",tm);
473 if (offset==3) attron(A_REVERSE);
474 printw("%2d",tm->tm_hour);
477 if (offset==4) attron(A_REVERSE);
478 printw("%02d",tm->tm_min);
480 printw(" Modified ");
481 tm=cpmtime(buf[entrystart+5],buf[entrystart+6],buf[entrystart+7],buf[entrystart+8]);
482 if (offset==5 || offset==6) attron(A_REVERSE);
483 strftime(s,sizeof(s),"%x",tm);
487 if (offset==7) attron(A_REVERSE);
488 printw("%2d",tm->tm_hour);
491 if (offset==8) attron(A_REVERSE);
492 printw("%02d",tm->tm_min);
496 printw("2nd last extent: Created/Accessed ");
497 tm=cpmtime(buf[entrystart+11],buf[entrystart+12],buf[entrystart+13],buf[entrystart+14]);
498 if (offset==11 || offset==12) attron(A_REVERSE);
499 strftime(s,sizeof(s),"%x",tm);
503 if (offset==13) attron(A_REVERSE);
504 printw("%2d",tm->tm_hour);
507 if (offset==14) attron(A_REVERSE);
508 printw("%02d",tm->tm_min);
510 printw(" Modified ");
511 tm=cpmtime(buf[entrystart+15],buf[entrystart+16],buf[entrystart+17],buf[entrystart+18]);
512 if (offset==15 || offset==16) attron(A_REVERSE);
513 strftime(s,sizeof(s),"%x",tm);
517 if (offset==17) attron(A_REVERSE);
518 printw("%2d",tm->tm_hour);
521 if (offset==18) attron(A_REVERSE);
522 printw("%02d",tm->tm_min);
526 printw(" Last extent: Created/Accessed ");
527 tm=cpmtime(buf[entrystart+21],buf[entrystart+22],buf[entrystart+23],buf[entrystart+24]);
528 if (offset==21 || offset==22) attron(A_REVERSE);
529 strftime(s,sizeof(s),"%x",tm);
533 if (offset==23) attron(A_REVERSE);
534 printw("%2d",tm->tm_hour);
537 if (offset==24) attron(A_REVERSE);
538 printw("%02d",tm->tm_min);
540 printw(" Modified ");
541 tm=cpmtime(buf[entrystart+25],buf[entrystart+26],buf[entrystart+27],buf[entrystart+28]);
542 if (offset==25 || offset==26) attron(A_REVERSE);
543 strftime(s,sizeof(s),"%x",tm);
547 if (offset==27) attron(A_REVERSE);
548 printw("%2d",tm->tm_hour);
551 if (offset==28) attron(A_REVERSE);
552 printw("%02d",tm->tm_min);
556 else if /* password */ /*{{{*/
557 (buf[entrystart]>=16 && buf[entrystart]<=31 && drive.type==CPMFS_DR3)
561 if (offset==0) attron(A_REVERSE);
569 if (offset==1+i) attron(A_REVERSE);
570 printw("%c",buf[entrystart+1+i]&0x7f);
573 printw(" Extension: ");
576 if (offset==9+i) attron(A_REVERSE);
577 printw("%c",buf[entrystart+9+i]&0x7f);
582 printw("Password required for: ");
583 if (offset==12) attron(A_REVERSE);
584 if (buf[entrystart+12]&0x80) printw("Reading ");
585 if (buf[entrystart+12]&0x40) printw("Writing ");
586 if (buf[entrystart+12]&0x20) printw("Deleting ");
590 printw("Password: ");
595 if (offset==16+(7-i)) attron(A_REVERSE);
596 printable=(buf[entrystart+16+(7-i)]^buf[entrystart+13])&0x7f;
597 printw("%c",isprint(printable) ? printable : ' ');
600 printw(" XOR value: ");
601 if (offset==13) attron(A_REVERSE);
602 printw("0x%02x",buf[entrystart+13]&0xff);
606 else /* bad status */ /*{{{*/
608 printw("Bad status ");
609 if (offset==0) attron(A_REVERSE);
610 printw("0x%02x",buf[entrystart]);
614 if (!reload) data(&drive,buf,pos);
617 case 'F': /* next entry */ /*{{{*/
619 if (pos+32<(drive.sectrk*drive.tracks*(unsigned long)drive.secLength))
621 if (pos/drive.secLength!=(pos+32)/drive.secLength) reload=1;
627 case 'B': /* previous entry */ /*{{{*/
631 if (pos/drive.secLength!=(pos-32)/drive.secLength) reload=1;
640 else /* data area */ /*{{{*/
644 msg="Data area"; move(0,(COLS-strlen(msg))/2); printw(msg);
645 if (!reload) data(&drive,buf,pos);
650 /* process common commands */ /*{{{*/
653 case 'n': /* next record */ /*{{{*/
655 if (pos+128<(drive.sectrk*drive.tracks*(unsigned long)drive.secLength))
657 if (pos/drive.secLength!=(pos+128)/drive.secLength) reload=1;
663 case 'p': /* previous record */ /*{{{*/
667 if (pos/drive.secLength!=(pos-128)/drive.secLength) reload=1;
673 case 'N': /* next track */ /*{{{*/
675 if ((pos+drive.sectrk*drive.secLength)<(drive.sectrk*drive.tracks*drive.secLength))
677 pos+=drive.sectrk*drive.secLength;
683 case 'P': /* previous track */ /*{{{*/
685 if (pos>=drive.sectrk*drive.secLength)
687 pos-=drive.sectrk*drive.secLength;
693 case 'b': /* byte back */ /*{{{*/
697 if (pos/drive.secLength!=(pos-1)/drive.secLength) reload=1;
703 case 'f': /* byte forward */ /*{{{*/
705 if (pos+1<drive.tracks*drive.sectrk*drive.secLength)
707 if (pos/drive.secLength!=(pos+1)/drive.secLength) reload=1;
713 case 'i': info(&drive,format,image); break;
714 case 'm': map(&drive); break;
719 /* exit curses */ /*{{{*/