New upstream version 2.20
[debian/cpmtools] / fsed.cpm.c
1 /* #includes */ /*{{{C}}}*//*{{{*/
2 #include "config.h"
3
4 #include <assert.h>
5 #include <ctype.h>
6 #if NEED_NCURSES
7 #if HAVE_NCURSES_NCURSES_H
8 #include <ncurses/ncurses.h>
9 #else
10 #include <ncurses.h>
11 #endif
12 #else
13 #include <curses.h>
14 #endif
15 #include <errno.h>
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <time.h>
20
21 #include "cpmfs.h"
22 #include "getopt_.h"
23
24 #ifdef USE_DMALLOC
25 #include <dmalloc.h>
26 #endif
27 /*}}}*/
28
29 extern char **environ;
30
31 static char *mapbuf;
32
33 static struct tm *cpmtime(char lday, char hday, char hour, char min) /*{{{*/
34 {
35   static struct tm tm;
36   unsigned long days=(lday&0xff)|((hday&0xff)<<8);
37   int d;
38   unsigned int md[12]={31,0,31,30,31,30,31,31,30,31,30,31};
39
40   tm.tm_sec=0;
41   tm.tm_min=((min>>4)&0xf)*10+(min&0xf);
42   tm.tm_hour=((hour>>4)&0xf)*10+(hour&0xf);
43   tm.tm_mon=0;
44   tm.tm_year=1978;
45   tm.tm_isdst=-1;
46   if (days) --days;
47   while (days>=(d=(((tm.tm_year%400)==0 || ((tm.tm_year%4)==0 && (tm.tm_year%100))) ? 366 : 365)))
48   {
49     days-=d;
50     ++tm.tm_year;
51   }
52   md[1]=((tm.tm_year%400)==0 || ((tm.tm_year%4)==0 && (tm.tm_year%100))) ? 29 : 28;
53   while (days>=md[tm.tm_mon])
54   {
55     days-=md[tm.tm_mon];
56     ++tm.tm_mon;
57   }
58   tm.tm_mday=days+1;
59   tm.tm_year-=1900;
60   return &tm;
61 }
62 /*}}}*/
63 static void info(struct cpmSuperBlock *sb, const char *format, const char *image) /*{{{*/
64 {
65   const char *msg;
66
67   clear();
68   msg="File system characteristics";
69   move(0,(COLS-strlen(msg))/2); printw(msg);
70   move(2,0); printw("                      Image: %s",image);
71   move(3,0); printw("                     Format: %s",format);
72   move(4,0); printw("                File system: ");
73   switch (sb->type)
74   {
75     case CPMFS_DR22: printw("CP/M 2.2"); break;
76     case CPMFS_P2DOS: printw("P2DOS 2.3"); break;
77     case CPMFS_DR3: printw("CP/M Plus"); break;
78   }
79
80   move(6,0); printw("              Sector length: %d",sb->secLength);
81   move(7,0); printw("           Number of tracks: %d",sb->tracks);
82   move(8,0); printw("          Sectors per track: %d",sb->sectrk);
83
84   move(10,0);printw("                 Block size: %d",sb->blksiz);
85   move(11,0);printw("Number of directory entries: %d",sb->maxdir);
86   move(12,0);printw("        Logical sector skew: %d",sb->skew);
87   move(13,0);printw("    Number of system tracks: %d",sb->boottrk);
88   move(14,0);printw(" Logical extents per extent: %d",sb->extents);
89   move(15,0);printw("    Allocatable data blocks: %d",sb->size-(sb->maxdir*32+sb->blksiz-1)/sb->blksiz);
90
91   msg="Any key to continue";
92   move(23,(COLS-strlen(msg))/2); printw(msg);
93   getch();
94 }
95 /*}}}*/
96 static void map(struct cpmSuperBlock *sb) /*{{{*/
97 {
98   const char *msg;
99   char bmap[18*80];
100   int secmap,sys,directory;
101   int pos;
102
103   clear();
104   msg="Data map";
105   move(0,(COLS-strlen(msg))/2); printw(msg);
106
107   secmap=(sb->tracks*sb->sectrk+80*18-1)/(80*18);
108   memset(bmap,' ',sizeof(bmap));
109   sys=sb->boottrk*sb->sectrk;
110   memset(bmap,'S',sys/secmap);
111   directory=(sb->maxdir*32+sb->secLength-1)/sb->secLength;
112   memset(bmap+sys/secmap,'D',directory/secmap);
113   memset(bmap+(sys+directory)/secmap,'.',sb->sectrk*sb->tracks/secmap);
114
115   for (pos=0; pos<(sb->maxdir*32+sb->secLength-1)/sb->secLength; ++pos)
116   {
117     int entry;
118
119     Device_readSector(&sb->dev,sb->boottrk+pos/(sb->sectrk*sb->secLength),pos/sb->secLength,mapbuf);
120     for (entry=0; entry<sb->secLength/32 && (pos*sb->secLength/32)+entry<sb->maxdir; ++entry)
121     {
122       int i;
123
124       if (mapbuf[entry*32]>=0 && mapbuf[entry*32]<=(sb->type==CPMFS_P2DOS ? 31 : 15))
125       {
126         for (i=0; i<16; ++i)
127         {
128           int sector;
129
130           sector=mapbuf[entry*32+16+i]&0xff;
131           if (sb->size>=256) sector|=(((mapbuf[entry*32+16+ ++i]&0xff)<<8));
132           if (sector>0 && sector<=sb->size)
133           {
134             /* not entirely correct without the last extent record count */
135             sector=sector*(sb->blksiz/sb->secLength)+sb->sectrk*sb->boottrk;
136             memset(bmap+sector/secmap,'#',sb->blksiz/(sb->secLength*secmap));
137           }
138         }
139       }
140     }
141   }
142
143   for (pos=0; pos<(int)sizeof(bmap); ++pos)
144   {
145     move(2+pos%18,pos/18);
146     addch(bmap[pos]);
147   }
148   move(21,0); printw("S=System area   D=Directory area   #=File data   .=Free");
149   msg="Any key to continue";
150   move(23,(COLS-strlen(msg))/2); printw(msg);
151   getch();
152 }
153 /*}}}*/
154 static void data(struct cpmSuperBlock *sb, const char *buf, unsigned long int pos) /*{{{*/
155 {
156   int offset=(pos%sb->secLength)&~0x7f;
157   unsigned int i;
158
159   for (i=0; i<128; ++i)
160   {
161     move(4+(i>>4),(i&0x0f)*3+!!(i&0x8)); printw("%02x",buf[i+offset]&0xff);
162     if (pos%sb->secLength==i+offset) attron(A_REVERSE);
163     move(4+(i>>4),50+(i&0x0f)); printw("%c",isprint(buf[i+offset]) ? buf[i+offset] : '.');
164     attroff(A_REVERSE);
165   }
166   move(4+((pos&0x7f)>>4),((pos&0x7f)&0x0f)*3+!!((pos&0x7f)&0x8)+1);
167 }
168 /*}}}*/
169
170 const char cmd[]="fsed.cpm";
171
172 int main(int argc, char *argv[]) /*{{{*/
173 {
174   /* variables */ /*{{{*/
175   const char *devopts=(const char*)0;
176   char *image;
177   const char *err;
178   struct cpmSuperBlock drive;
179   struct cpmInode root;
180   const char *format;
181   int c,usage=0;
182   off_t pos;
183   chtype ch;
184   int reload;
185   char *buf;
186   /*}}}*/
187
188   /* parse options */ /*{{{*/
189   if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT;
190   while ((c=getopt(argc,argv,"T:f:h?"))!=EOF) switch(c)
191   {
192     case 'f': format=optarg; break;
193     case 'T': devopts=optarg; break;
194     case 'h':
195     case '?': usage=1; break;
196   }
197
198   if (optind!=(argc-1)) usage=1;
199   else image=argv[optind++];
200
201   if (usage)
202   {
203     fprintf(stderr,"Usage: fsed.cpm [-f format] image\n");
204     exit(1);
205   }
206   /*}}}*/
207   /* open image */ /*{{{*/
208   if ((err=Device_open(&drive.dev,image,O_RDONLY,devopts))) 
209   {
210     fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err);
211     exit(1);
212   }
213   if (cpmReadSuper(&drive,&root,format)==-1)
214   {
215     fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo);
216     exit(1);
217   }
218   /*}}}*/
219   /* alloc sector buffers */ /*{{{*/
220   if ((buf=malloc(drive.secLength))==(char*)0 || (mapbuf=malloc(drive.secLength))==(char*)0)
221   {
222     fprintf(stderr,"fsed.cpm: can not allocate sector buffer (%s).\n",strerror(errno));
223     exit(1);
224   }
225   /*}}}*/
226   /* init curses */ /*{{{*/
227   initscr();
228   noecho();
229   raw();
230   nonl();
231   idlok(stdscr,TRUE);
232   idcok(stdscr,TRUE);
233   keypad(stdscr,TRUE);
234   clear();
235   /*}}}*/
236
237   pos=0;
238   reload=1;
239   do
240   {
241     /* display position and load data */ /*{{{*/
242     clear();
243     move(2,0); printw("Byte %8lu (0x%08lx)  ",pos,pos);
244     if (pos<(drive.boottrk*drive.sectrk*drive.secLength))
245     {
246       printw("Physical sector %3lu  ",((pos/drive.secLength)%drive.sectrk)+1);
247     }
248     else
249     {
250       printw("Sector %3lu ",((pos/drive.secLength)%drive.sectrk)+1);
251       printw("(physical %3d)  ",drive.skewtab[(pos/drive.secLength)%drive.sectrk]+1);
252     }
253     printw("Offset %5lu  ",pos%drive.secLength);
254     printw("Track %5lu",pos/(drive.secLength*drive.sectrk));
255     move(LINES-3,0); printw("N)ext track    P)revious track");
256     move(LINES-2,0); printw("n)ext record   p)revious record     f)orward byte      b)ackward byte");
257     move(LINES-1,0); printw("i)nfo          q)uit");
258     if (reload)
259     {
260       if (pos<(drive.boottrk*drive.sectrk*drive.secLength))
261       {
262         err=Device_readSector(&drive.dev,pos/(drive.secLength*drive.sectrk),(pos/drive.secLength)%drive.sectrk,buf);
263       }
264       else
265       {
266         err=Device_readSector(&drive.dev,pos/(drive.secLength*drive.sectrk),drive.skewtab[(pos/drive.secLength)%drive.sectrk],buf);
267       }
268       if (err)
269       {
270         move(4,0); printw("Data can not be read: %s",err);
271       }
272       else reload=0;
273     }
274     /*}}}*/
275
276     if /* position before end of system area */ /*{{{*/
277     (pos<(drive.boottrk*drive.sectrk*drive.secLength))
278     {
279       const char *msg;
280
281       msg="System area"; move(0,(COLS-strlen(msg))/2); printw(msg);
282       move(LINES-3,36); printw("F)orward 16 byte   B)ackward 16 byte");
283       if (!reload) data(&drive,buf,pos);
284       switch (ch=getch())
285       {
286         case 'F': /* next 16 byte */ /*{{{*/
287         {
288           if (pos+16<(drive.sectrk*drive.tracks*(off_t)drive.secLength))
289           {
290             if (pos/drive.secLength!=(pos+16)/drive.secLength) reload=1;
291             pos+=16;
292           }
293           break;
294         }
295         /*}}}*/
296         case 'B': /* previous 16 byte */ /*{{{*/
297         {
298           if (pos>=16)
299           {
300             if (pos/drive.secLength!=(pos-16)/drive.secLength) reload=1;
301             pos-=16;
302           }
303           break;
304         }
305         /*}}}*/
306       }
307     }
308     /*}}}*/
309     else if /* position before end of directory area */ /*{{{*/
310     (pos<(drive.boottrk*drive.sectrk*drive.secLength+drive.maxdir*32))
311     {
312       const char *msg;
313       unsigned long entrystart=(pos&~0x1f)%drive.secLength;
314       int entry=(pos-(drive.boottrk*drive.sectrk*drive.secLength))>>5;
315       int offset=pos&0x1f;
316
317       msg="Directory area"; move(0,(COLS-strlen(msg))/2); printw(msg);
318       move(LINES-3,36); printw("F)orward entry     B)ackward entry");
319
320       move(13,0); printw("Entry %3d: ",entry);      
321       if /* free or used directory entry */ /*{{{*/
322       ((buf[entrystart]>=0 && buf[entrystart]<=(drive.type==CPMFS_P2DOS ? 31 : 15)) || buf[entrystart]==(char)0xe5)
323       {
324         int i;
325
326         if (buf[entrystart]==(char)0xe5)
327         {
328           if (offset==0) attron(A_REVERSE);
329           printw("Free");
330           attroff(A_REVERSE);
331         }
332         else printw("Directory entry");
333         move(15,0);
334         if (buf[entrystart]!=(char)0xe5)
335         {
336           printw("User: ");
337           if (offset==0) attron(A_REVERSE);
338           printw("%2d",buf[entrystart]);
339           attroff(A_REVERSE);
340           printw(" ");
341         }
342         printw("Name: ");
343         for (i=0; i<8; ++i)
344         {
345           if (offset==1+i) attron(A_REVERSE);
346           printw("%c",buf[entrystart+1+i]&0x7f);
347           attroff(A_REVERSE);
348         }
349         printw(" Extension: ");
350         for (i=0; i<3; ++i)
351         {
352           if (offset==9+i) attron(A_REVERSE);
353           printw("%c",buf[entrystart+9+i]&0x7f);
354           attroff(A_REVERSE);
355         }
356         move(16,0); printw("Extent: %3d",((buf[entrystart+12]&0xff)+((buf[entrystart+14]&0xff)<<5))/drive.extents);
357         printw(" (low: ");
358         if (offset==12) attron(A_REVERSE);
359         printw("%2d",buf[entrystart+12]&0xff);
360         attroff(A_REVERSE);
361         printw(", high: ");
362         if (offset==14) attron(A_REVERSE);
363         printw("%2d",buf[entrystart+14]&0xff);
364         attroff(A_REVERSE);
365         printw(")");
366         move(17,0); printw("Last extent record count: ");
367         if (offset==15) attron(A_REVERSE);
368         printw("%3d",buf[entrystart+15]&0xff);
369         attroff(A_REVERSE);
370         move(18,0); printw("Last record byte count: ");
371         if (offset==13) attron(A_REVERSE);
372         printw("%3d",buf[entrystart+13]&0xff);
373         attroff(A_REVERSE);
374         move(19,0); printw("Data blocks:");
375         for (i=0; i<16; ++i)
376         {
377           unsigned int block=buf[entrystart+16+i]&0xff;
378           if (drive.size>=256)
379           {
380             printw(" ");
381             if (offset==16+i || offset==16+i+1) attron(A_REVERSE);
382             printw("%5d",block|(((buf[entrystart+16+ ++i]&0xff)<<8)));
383             attroff(A_REVERSE);
384           }
385           else
386           {
387             printw(" ");
388             if (offset==16+i) attron(A_REVERSE);
389             printw("%3d",block);
390             attroff(A_REVERSE);
391           }
392         }
393       }
394       /*}}}*/
395       else if /* disc label */ /*{{{*/
396       (buf[entrystart]==0x20 && drive.type==CPMFS_DR3)
397       {
398         int i;
399         const struct tm *tm;
400         char s[30];
401
402         if (offset==0) attron(A_REVERSE);
403         printw("Disc label");
404         attroff(A_REVERSE);
405         move(15,0);
406         printw("Label: ");
407         for (i=0; i<11; ++i)
408         {
409           if (i+1==offset) attron(A_REVERSE);
410           printw("%c",buf[entrystart+1+i]&0x7f);
411           attroff(A_REVERSE);
412         }
413         move(16,0);
414         printw("Bit 0,7: ");
415         if (offset==12) attron(A_REVERSE);
416         printw("Label %s",buf[entrystart+12]&1 ? "set" : "not set");
417         printw(", password protection %s",buf[entrystart+12]&0x80 ? "set" : "not set");
418         attroff(A_REVERSE);
419         move(17,0);
420         printw("Bit 4,5,6: ");
421         if (offset==12) attron(A_REVERSE);
422         printw("Time stamp ");
423         if (buf[entrystart+12]&0x10) printw("on create, ");
424         else printw("not on create, ");
425         if (buf[entrystart+12]&0x20) printw("on modification, ");
426         else printw("not on modifiction, ");
427         if (buf[entrystart+12]&0x40) printw("on access");
428         else printw("not on access");
429         attroff(A_REVERSE); 
430         move(18,0);
431         printw("Password: ");
432         for (i=0; i<8; ++i)
433         {
434           char printable;
435
436           if (offset==16+(7-i)) attron(A_REVERSE);
437           printable=(buf[entrystart+16+(7-i)]^buf[entrystart+13])&0x7f;
438           printw("%c",isprint(printable) ? printable : ' ');
439           attroff(A_REVERSE);
440         }
441         printw(" XOR value: ");
442         if (offset==13) attron(A_REVERSE);
443         printw("0x%02x",buf[entrystart+13]&0xff);
444         attroff(A_REVERSE);
445         move(19,0);
446         printw("Created: ");
447         tm=cpmtime(buf[entrystart+24],buf[entrystart+25],buf[entrystart+26],buf[entrystart+27]);
448         if (offset==24 || offset==25) attron(A_REVERSE);
449         strftime(s,sizeof(s),"%x",tm);
450         printw("%s",s);
451         attroff(A_REVERSE);
452         printw(" ");
453         if (offset==26) attron(A_REVERSE);
454         printw("%2d",tm->tm_hour);
455         attroff(A_REVERSE);
456         printw(":");
457         if (offset==27) attron(A_REVERSE);
458         printw("%02d",tm->tm_min);
459         attroff(A_REVERSE);
460         printw(" Updated: ");
461         tm=cpmtime(buf[entrystart+28],buf[entrystart+29],buf[entrystart+30],buf[entrystart+31]);
462         if (offset==28 || offset==29) attron(A_REVERSE);
463         strftime(s,sizeof(s),"%x",tm);
464         printw("%s",s);
465         attroff(A_REVERSE);
466         printw(" ");
467         if (offset==30) attron(A_REVERSE);
468         printw("%2d",tm->tm_hour);
469         attroff(A_REVERSE);
470         printw(":");
471         if (offset==31) attron(A_REVERSE);
472         printw("%02d",tm->tm_min);
473         attroff(A_REVERSE);
474       }
475       /*}}}*/
476       else if /* time stamp */ /*{{{*/
477       (buf[entrystart]==0x21 && (drive.type==CPMFS_P2DOS || drive.type==CPMFS_DR3))
478       {
479         const struct tm *tm;
480         char s[30];
481
482         if (offset==0) attron(A_REVERSE);
483         printw("Time stamps");
484         attroff(A_REVERSE);
485         move(15,0);
486         printw("3rd last extent: Created/Accessed ");
487         tm=cpmtime(buf[entrystart+1],buf[entrystart+2],buf[entrystart+3],buf[entrystart+4]);
488         if (offset==1 || offset==2) attron(A_REVERSE);
489         strftime(s,sizeof(s),"%x",tm);
490         printw("%s",s);
491         attroff(A_REVERSE);
492         printw(" ");
493         if (offset==3) attron(A_REVERSE);
494         printw("%2d",tm->tm_hour);
495         attroff(A_REVERSE);
496         printw(":");
497         if (offset==4) attron(A_REVERSE);
498         printw("%02d",tm->tm_min);
499         attroff(A_REVERSE);
500         printw(" Modified ");
501         tm=cpmtime(buf[entrystart+5],buf[entrystart+6],buf[entrystart+7],buf[entrystart+8]);
502         if (offset==5 || offset==6) attron(A_REVERSE);
503         strftime(s,sizeof(s),"%x",tm);
504         printw("%s",s);
505         attroff(A_REVERSE);
506         printw(" ");
507         if (offset==7) attron(A_REVERSE);
508         printw("%2d",tm->tm_hour);
509         attroff(A_REVERSE);
510         printw(":");
511         if (offset==8) attron(A_REVERSE);
512         printw("%02d",tm->tm_min);
513         attroff(A_REVERSE);
514
515         move(16,0);
516         printw("2nd last extent: Created/Accessed ");
517         tm=cpmtime(buf[entrystart+11],buf[entrystart+12],buf[entrystart+13],buf[entrystart+14]);
518         if (offset==11 || offset==12) attron(A_REVERSE);
519         strftime(s,sizeof(s),"%x",tm);
520         printw("%s",s);
521         attroff(A_REVERSE);
522         printw(" ");
523         if (offset==13) attron(A_REVERSE);
524         printw("%2d",tm->tm_hour);
525         attroff(A_REVERSE);
526         printw(":");
527         if (offset==14) attron(A_REVERSE);
528         printw("%02d",tm->tm_min);
529         attroff(A_REVERSE);
530         printw(" Modified ");
531         tm=cpmtime(buf[entrystart+15],buf[entrystart+16],buf[entrystart+17],buf[entrystart+18]);
532         if (offset==15 || offset==16) attron(A_REVERSE);
533         strftime(s,sizeof(s),"%x",tm);
534         printw("%s",s);
535         attroff(A_REVERSE);
536         printw(" ");
537         if (offset==17) attron(A_REVERSE);
538         printw("%2d",tm->tm_hour);
539         attroff(A_REVERSE);
540         printw(":");
541         if (offset==18) attron(A_REVERSE);
542         printw("%02d",tm->tm_min);
543         attroff(A_REVERSE);
544
545         move(17,0);
546         printw("    Last extent: Created/Accessed ");
547         tm=cpmtime(buf[entrystart+21],buf[entrystart+22],buf[entrystart+23],buf[entrystart+24]);
548         if (offset==21 || offset==22) attron(A_REVERSE);
549         strftime(s,sizeof(s),"%x",tm);
550         printw("%s",s);
551         attroff(A_REVERSE);
552         printw(" ");
553         if (offset==23) attron(A_REVERSE);
554         printw("%2d",tm->tm_hour);
555         attroff(A_REVERSE);
556         printw(":");
557         if (offset==24) attron(A_REVERSE);
558         printw("%02d",tm->tm_min);
559         attroff(A_REVERSE);
560         printw(" Modified ");
561         tm=cpmtime(buf[entrystart+25],buf[entrystart+26],buf[entrystart+27],buf[entrystart+28]);
562         if (offset==25 || offset==26) attron(A_REVERSE);
563         strftime(s,sizeof(s),"%x",tm);
564         printw("%s",s);
565         attroff(A_REVERSE);
566         printw(" ");
567         if (offset==27) attron(A_REVERSE);
568         printw("%2d",tm->tm_hour);
569         attroff(A_REVERSE);
570         printw(":");
571         if (offset==28) attron(A_REVERSE);
572         printw("%02d",tm->tm_min);
573         attroff(A_REVERSE);
574       }
575       /*}}}*/
576       else if /* password */ /*{{{*/
577       (buf[entrystart]>=16 && buf[entrystart]<=31 && drive.type==CPMFS_DR3)
578       {
579         int i;
580
581         if (offset==0) attron(A_REVERSE);
582         printw("Password");
583         attroff(A_REVERSE);
584
585         move(15,0);
586         printw("Name: ");
587         for (i=0; i<8; ++i)
588         {
589           if (offset==1+i) attron(A_REVERSE);
590           printw("%c",buf[entrystart+1+i]&0x7f);
591           attroff(A_REVERSE);
592         }
593         printw(" Extension: ");
594         for (i=0; i<3; ++i)
595         {
596           if (offset==9+i) attron(A_REVERSE);
597           printw("%c",buf[entrystart+9+i]&0x7f);
598           attroff(A_REVERSE);
599         }
600
601         move(16,0);
602         printw("Password required for: ");
603         if (offset==12) attron(A_REVERSE);
604         if (buf[entrystart+12]&0x80) printw("Reading ");
605         if (buf[entrystart+12]&0x40) printw("Writing ");
606         if (buf[entrystart+12]&0x20) printw("Deleting ");
607         attroff(A_REVERSE);
608
609         move(17,0);
610         printw("Password: ");
611         for (i=0; i<8; ++i)
612         {
613           char printable;
614
615           if (offset==16+(7-i)) attron(A_REVERSE);
616           printable=(buf[entrystart+16+(7-i)]^buf[entrystart+13])&0x7f;
617           printw("%c",isprint(printable) ? printable : ' ');
618           attroff(A_REVERSE);
619         }
620         printw(" XOR value: ");
621         if (offset==13) attron(A_REVERSE);
622         printw("0x%02x",buf[entrystart+13]&0xff);
623         attroff(A_REVERSE);
624       }
625       /*}}}*/
626       else /* bad status */ /*{{{*/
627       {
628         printw("Bad status ");
629         if (offset==0) attron(A_REVERSE);
630         printw("0x%02x",buf[entrystart]);
631         attroff(A_REVERSE);
632       }
633       /*}}}*/
634       if (!reload) data(&drive,buf,pos);
635       switch (ch=getch())
636       {
637         case 'F': /* next entry */ /*{{{*/
638         {
639           if (pos+32<(drive.sectrk*drive.tracks*(off_t)drive.secLength))
640           {
641             if (pos/drive.secLength!=(pos+32)/drive.secLength) reload=1;
642             pos+=32;
643           }
644           break;
645         }
646         /*}}}*/
647         case 'B': /* previous entry */ /*{{{*/
648         {
649           if (pos>=32)
650           {
651             if (pos/drive.secLength!=(pos-32)/drive.secLength) reload=1;
652             pos-=32;
653           }
654           break;
655         }
656         /*}}}*/
657       }
658     }
659     /*}}}*/
660     else /* data area */ /*{{{*/
661     {
662       const char *msg;
663
664       msg="Data area"; move(0,(COLS-strlen(msg))/2); printw(msg);
665       if (!reload) data(&drive,buf,pos);
666       ch=getch();
667     }
668     /*}}}*/
669
670     /* process common commands */ /*{{{*/
671     switch (ch)
672     {
673       case 'n': /* next record */ /*{{{*/
674       {
675         if (pos+128<(drive.sectrk*drive.tracks*(off_t)drive.secLength))
676         {
677           if (pos/drive.secLength!=(pos+128)/drive.secLength) reload=1;
678           pos+=128;
679         }
680         break;
681       }
682       /*}}}*/
683       case 'p': /* previous record */ /*{{{*/
684       {
685         if (pos>=128)
686         {
687           if (pos/drive.secLength!=(pos-128)/drive.secLength) reload=1;
688           pos-=128;
689         }
690         break;
691       }
692       /*}}}*/
693       case 'N': /* next track */ /*{{{*/
694       {
695         if ((pos+drive.sectrk*drive.secLength)<(drive.sectrk*drive.tracks*drive.secLength))
696         {
697           pos+=drive.sectrk*drive.secLength;
698           reload=1;
699         }
700         break;
701       }
702       /*}}}*/
703       case 'P': /* previous track */ /*{{{*/
704       {
705         if (pos>=drive.sectrk*drive.secLength)
706         {
707           pos-=drive.sectrk*drive.secLength;
708           reload=1;
709         }
710         break;
711       }
712       /*}}}*/
713       case 'b': /* byte back */ /*{{{*/
714       {
715         if (pos)
716         {
717           if (pos/drive.secLength!=(pos-1)/drive.secLength) reload=1;
718           --pos;
719         }
720         break;
721       }
722       /*}}}*/
723       case 'f': /* byte forward */ /*{{{*/
724       {
725         if (pos+1<drive.tracks*drive.sectrk*drive.secLength)
726         {
727           if (pos/drive.secLength!=(pos+1)/drive.secLength) reload=1;
728           ++pos;
729         }
730         break;
731       }
732       /*}}}*/
733       case 'i': info(&drive,format,image); break;
734       case 'm': map(&drive); break;
735     }
736     /*}}}*/
737   } while (ch!='q');
738
739   /* exit curses */ /*{{{*/
740   move(LINES-1,0);
741   refresh();
742   echo();
743   noraw();
744   endwin();
745   /*}}}*/
746   exit(0);
747 }
748 /*}}}*/