Imported Upstream version 2.1
[debian/cpmtools] / fsck.cpm.c
1 /* #includes */ /*{{{C}}}*//*{{{*/
2 #include <assert.h>
3 #include <ctype.h>
4 #include <stdio.h>
5 #include <string.h>
6 #include <errno.h>
7 #include "config.h"
8
9 extern char *optarg;
10 extern int optind,opterr,optopt;
11 int getopt(int argc, char * const *argv, const char *optstring);
12
13 #include "cpmdir.h"
14 #include "cpmfs.h"
15 /*}}}*/
16 /* #defines */ /*{{{*/
17 /* your favourite password *:-) */
18
19 #define T0 'G'
20 #define T1 'E'
21 #define T2 'H'
22 #define T3 'E'
23 #define T4 'I'
24 #define T5 'M'
25 #define T6 ' '
26 #define T7 ' '
27
28 #define PB ((char)(T0+T1+T2+T3+T4+T5+T6+T7))
29 #define P0 ((char)(T7^PB))
30 #define P1 ((char)(T6^PB))
31 #define P2 ((char)(T5^PB))
32 #define P3 ((char)(T4^PB))
33 #define P4 ((char)(T3^PB))
34 #define P5 ((char)(T2^PB))
35 #define P6 ((char)(T1^PB))
36 #define P7 ((char)(T0^PB))
37 /*}}}*/
38
39 /* types */ /*{{{*/
40 enum Result { OK=0, MODIFIED=1, BROKEN=2 };
41 /*}}}*/
42 /* variables */ /*{{{*/
43 static int norepair=0;
44 /*}}}*/
45
46 /* bcdCheck -- check format and range of BCD digit */ /*{{{*/
47 static int bcdCheck(int n, int max, const char *msg, const char *unit, int extent1, int extent2)
48 {
49   if (((n>>4)&0xf)>10 || (n&0xf)>10 || (((n>>4)&0xf)*10+(n&0xf))>=max)
50   {
51     printf("Error: Bad %s %s (extent=%d/%d, %s=%02x)\n",msg,unit,extent1,extent2,unit,n&0xff);
52     return -1;
53   }
54   else return 0;
55 }
56 /*}}}*/
57 /* pwdCheck -- check password */ /*{{{*/
58 static int pwdCheck(int extent, const char *pwd, char decode)
59 {
60   char c;
61   int i;
62
63   for (i=0; i<8; ++i) if ((c=((char)(pwd[7-i]^decode)))<' ' || c&0x80)
64   {
65     printf("Error: non-printable character in password (extent=%d, password=",extent);
66     for (i=0; i<8; ++i)
67     {
68       c=pwd[7-i]^decode;
69       if (c<' ' || c&0x80)
70       {
71         putchar('\\'); putchar('0'+((c>>6)&0x01));
72         putchar('0'+((c>>3)&0x03));
73         putchar('0'+(c&0x03));
74       }
75       else putchar(c);
76     }
77     printf(")\n");
78     return -1;
79   }
80   return 0;
81 }
82 /*}}}*/
83 /* ask -- ask user and return answer */ /*{{{*/
84 static int ask(const char *msg)
85 {
86   while (1)
87   {
88     char buf[80];
89
90     if (norepair) return 0;
91     printf("%s [Y]? ",msg); fflush(stdout);
92     if (fgets(buf,sizeof(buf),stdin)==(char*)0) exit(1);
93     switch (toupper(buf[0]))
94     {
95       case '\n':
96       case 'Y': return 1;
97       case 'N': return 0;
98     }
99   }
100 }
101 /*}}}*/
102 /* prfile -- print file name */ /*{{{*/
103 static char *prfile(struct cpmSuperBlock *sb, int extent)
104 {
105   struct PhysDirectoryEntry *dir;
106   static char name[80];
107   char *s=name;
108   int i;
109   char c;
110
111   dir=sb->dir+extent;
112   for (i=0; i<8; ++i)
113   {
114     c=dir->name[i];
115     if ((c&0x7f)<' ')
116     {
117       *s++='\\'; *s++=('0'+((c>>6)&0x01));
118       *s++=('0'+((c>>3)&0x03));
119       *s++=('0'+(c&0x03));
120     }
121     else *s++=(c&0x7f);
122   }
123   *s++='.';
124   for (i=0; i<3; ++i)
125   {
126     c=dir->ext[i];
127     if ((c&0x7f)<' ')
128     {
129       *s++='\\'; *s++=('0'+((c>>6)&0x01));
130       *s++=('0'+((c>>3)&0x03));
131       *s++=('0'+(c&0x03));
132     }
133     else *s++=(c&0x7f);
134   }
135   *s='\0';
136   return name;
137 }
138 /*}}}*/
139 /* fsck -- file system check */ /*{{{*/
140 static int fsck(struct cpmInode *root, const char *image)
141 {
142   /* variables */ /*{{{*/
143   enum Result ret=OK;
144   int extent,extent2;
145   struct PhysDirectoryEntry *dir,*dir2;
146   struct cpmSuperBlock *sb=root->sb;
147   /*}}}*/
148
149   /* Phase 1: check extent fields */ /*{{{*/
150   printf("Phase 1: check extent fields\n");
151   for (extent=0; extent<sb->maxdir; ++extent)
152   {
153     char *status;
154     int usedBlocks=0;
155
156     dir=sb->dir+extent;
157     status=&dir->status;
158     if (*status>=0 && *status<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* directory entry */ /*{{{*/
159     {
160       /* check name and extension */ /*{{{*/
161       {
162         int i;
163         char *c;
164
165         for (i=0; i<8; ++i)
166         {
167           c=&(dir->name[i]);
168           if (!ISFILECHAR(i,*c&0x7f) || islower(*c&0x7f))
169           {
170             printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i);
171             if (ask("Remove file"))
172             {
173               *status=(char)0xE5;
174               ret|=MODIFIED;
175               break;
176             }
177             else ret|=BROKEN;
178           }
179         }
180         if (*status==(char)0xe5) continue;
181         for (i=0; i<3; ++i)
182         {
183           c=&(dir->ext[i]);
184           if (!ISFILECHAR(1,*c&0x7f) || islower(*c&0x7f))
185           {
186             printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i);
187             if (ask("Remove file"))
188             {
189               *status=(char)0xE5;
190               ret|=MODIFIED;
191               break;
192             }
193             else ret|=BROKEN;
194           }
195         }
196         if (*status==(char)0xe5) continue;
197       }
198       /*}}}*/
199       /* check extent number */ /*{{{*/
200       if ((dir->extnol&0xff)>0x1f)
201       {
202         printf("Error: Bad lower bits of extent number (extent=%d, name=\"%s\", low bits=%d)\n",extent,prfile(sb,extent),dir->extnol&0xff);
203         if (ask("Remove file"))
204         {
205           *status=(char)0xE5;
206           ret|=MODIFIED;
207         }
208         else ret|=BROKEN;
209       }
210       if (*status==(char)0xe5) continue;
211       if ((dir->extnoh&0xff)>0x3f)
212       {
213         printf("Error: Bad higher bits of extent number (extent=%d, name=\"%s\", high bits=%d)\n",extent,prfile(sb,extent),dir->extnoh&0xff);
214         if (ask("Remove file"))
215         {
216           *status=(char)0xE5;
217           ret|=MODIFIED;
218         }
219         else ret|=BROKEN;
220       }
221       if (*status==(char)0xe5) continue;
222       /*}}}*/
223       /* check last record byte count */ /*{{{*/
224       if ((dir->lrc&0xff)>128)
225       {
226         printf("Error: Bad last record byte count (extent=%d, name=\"%s\", lrc=%d)\n",extent,prfile(sb,extent),dir->lrc&0xff);
227         if (ask("Clear last record byte count"))
228         {
229           dir->lrc=(char)0;
230           ret|=MODIFIED;
231         }
232         else ret|=BROKEN;
233       }
234       if (*status==(char)0xe5) continue;
235       /*}}}*/
236       /* check block number range */ /*{{{*/
237       {
238         int block,min,max,i;
239
240         min=(sb->maxdir*32+sb->blksiz-1)/sb->blksiz;
241         max=sb->size;
242         for (i=0; i<16; ++i)
243         {
244           block=dir->pointers[i]&0xff;
245           if (sb->size>=256) block+=(dir->pointers[++i]&0xff)<<8;
246           if (block>0)
247           {
248             ++usedBlocks;
249             if (block<min || block>max)
250             {
251               printf("Error: Bad block number (extent=%d, name=\"%s\", block=%d)\n",extent,prfile(sb,extent),block);
252               if (ask("Remove file"))
253               {
254                 *status=(char)0xE5;
255                 ret|=MODIFIED;
256                 break;
257               }
258               else ret|=BROKEN;
259             }
260           }
261         }
262         if (*status==(char)0xe5) continue;
263       }
264       /*}}}*/
265       /* check number of used blocks ? */ /*{{{*/
266       /*}}}*/
267       /* check record count */ /*{{{*/
268       {
269         int i,min,max,recordsInBlocks,used=0;
270
271         min=(dir->extnol%sb->extents)*16/sb->extents;
272         max=((dir->extnol%sb->extents)+1)*16/sb->extents;
273         assert(min<max);
274         for (i=min; i<max; ++i)
275         {
276         /* [JCE] Rewritten because the previous implementation didn't work
277          *       properly with Visual C++ */
278           if (dir->pointers[i] || (sb->size>=256 && dir->pointers[i+1])) ++used;
279           if (sb->size >= 256) ++i;
280         }
281         recordsInBlocks=(((unsigned char)dir->blkcnt)*128+sb->blksiz-1)/sb->blksiz;
282         if (recordsInBlocks!=used)
283         {
284           printf("Error: Bad record count (extent=%d, name=\"%s\", record count=%d)\n",extent,prfile(sb,extent),dir->blkcnt&0xff);
285           if (ask("Remove file"))
286           {
287             *status=(char)0xE5;
288             ret|=MODIFIED;
289           }
290           else ret|=BROKEN;
291         }
292         if (*status==(char)0xe5) continue;
293       }
294       /*}}}*/
295       /* check for too large .com files */ /*{{{*/
296       if (((EXTENT(dir->extnol,dir->extnoh)==3 && dir->blkcnt>=126) || EXTENT(dir->extnol,dir->extnoh)>=4) && (dir->ext[0]&0x7f)=='C' && (dir->ext[1]&0x7f)=='O' && (dir->ext[2]&0x7f)=='M')
297       {
298         printf("Warning: Oversized .COM file (extent=%d, name=\"%s\")\n",extent,prfile(sb,extent));
299       }
300       /*}}}*/
301     }
302     /*}}}*/
303     else if ((sb->type==CPMFS_P2DOS || sb->type==CPMFS_DR3) && *status==33) /* check time stamps ? */ /*{{{*/
304     {
305       unsigned long created,modified;
306       char s;
307
308       if ((s=sb->dir[extent2=(extent&~3)].status)>=0 && s<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* time stamps for first of the three extents */ /*{{{*/
309       {
310         bcdCheck(dir->name[2],24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent2);
311         bcdCheck(dir->name[3],60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent2);
312         bcdCheck(dir->name[6],24,"modification date","hour",extent,extent2);
313         bcdCheck(dir->name[7],60,"modification date","minute",extent,extent2);
314         created=(dir->name[4]+(dir->name[1]<<8))*(0x60*0x60)+dir->name[2]*0x60+dir->name[3];
315         modified=(dir->name[0]+(dir->name[5]<<8))*(0x60*0x60)+dir->name[6]*0x60+dir->name[7];
316         if (sb->cnotatime && modified<created)
317         {
318           printf("Warning: Modification date earlier than creation date (extent=%d/%d)\n",extent,extent2);
319         }
320       }
321       /*}}}*/
322       if ((s=sb->dir[extent2=(extent&~3)+1].status)>=0 && s<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* time stamps for second */ /*{{{*/
323       {
324         bcdCheck(dir->lrc,24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent2);
325         bcdCheck(dir->extnoh,60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent2);
326         bcdCheck(dir->pointers[1],24,"modification date","hour",extent,extent2);
327         bcdCheck(dir->pointers[2],60,"modification date","minute",extent,extent2);
328         created=(dir->ext[2]+(dir->extnol<<8))*(0x60*0x60)+dir->lrc*0x60+dir->extnoh;
329         modified=(dir->blkcnt+(dir->pointers[0]<<8))*(0x60*0x60)+dir->pointers[1]*0x60+dir->pointers[2];
330         if (sb->cnotatime && modified<created)
331         {
332           printf("Warning: Modification date earlier than creation date (extent=%d/%d)\n",extent,extent2);
333         }
334       }
335       /*}}}*/
336       if ((s=sb->dir[extent2=(extent&~3)+2].status)>=0 && s<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* time stamps for third */ /*{{{*/
337       {
338         bcdCheck(dir->pointers[7],24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent2);
339         bcdCheck(dir->pointers[8],60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent2);
340         bcdCheck(dir->pointers[11],24,"modification date","hour",extent,extent2);
341         bcdCheck(dir->pointers[12],60,"modification date","minute",extent,extent2);
342         created=(dir->pointers[5]+(dir->pointers[6]<<8))*(0x60*0x60)+dir->pointers[7]*0x60+dir->pointers[8];
343         modified=(dir->pointers[9]+(dir->pointers[10]<<8))*(0x60*0x60)+dir->pointers[11]*0x60+dir->pointers[12];
344         if (sb->cnotatime && modified<created)
345         {
346           printf("Warning: Modification date earlier than creation date (extent=%d/%d)\n",extent,extent2);
347         }
348       }
349       /*}}}*/
350     }
351     /*}}}*/
352     else if (sb->type==CPMFS_DR3 && *status==32) /* disc label */ /*{{{*/
353     {
354       unsigned long created,modified;
355
356       bcdCheck(dir->pointers[10],24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent);
357       bcdCheck(dir->pointers[11],60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent);
358       bcdCheck(dir->pointers[14],24,"modification date","hour",extent,extent);
359       bcdCheck(dir->pointers[15],60,"modification date","minute",extent,extent);
360       created=(dir->pointers[8]+(dir->pointers[9]<<8))*(0x60*0x60)+dir->pointers[10]*0x60+dir->pointers[11];
361       modified=(dir->pointers[12]+(dir->pointers[13]<<8))*(0x60*0x60)+dir->pointers[14]*0x60+dir->pointers[15];
362       if (sb->cnotatime && modified<created)
363       {
364         printf("Warning: Label modification date earlier than creation date (extent=%d)\n",extent);
365       }
366       if (dir->extnol&0x40 && dir->extnol&0x10)
367       {
368         printf("Error: Bit 4 and 6 can only be exclusively be set (extent=%d, label byte=0x%02x)\n",extent,(unsigned char)dir->extnol);
369         if (ask("Time stamp on creation"))
370         {
371           dir->extnol&=~0x40;
372           ret|=MODIFIED;
373         }
374         else if (ask("Time stamp on access"))
375         {
376           dir->extnol&=~0x10;
377           ret|=MODIFIED;
378         }
379         else ret|=BROKEN;
380       }
381       if (dir->extnol&0x80 && pwdCheck(extent,dir->pointers,dir->lrc))
382       {
383         char msg[80];
384
385         sprintf(msg,"Set password to %c%c%c%c%c%c%c%c",T0,T1,T2,T3,T4,T5,T6,T7);
386         if (ask(msg))
387         {
388           dir->pointers[0]=P0;
389           dir->pointers[1]=P1;
390           dir->pointers[2]=P2;
391           dir->pointers[3]=P3;
392           dir->pointers[4]=P4;
393           dir->pointers[5]=P5;
394           dir->pointers[6]=P6;
395           dir->pointers[7]=P7;
396           dir->lrc=PB;
397           ret|=MODIFIED;
398         }
399         else ret|=BROKEN;
400       }
401     }
402     /*}}}*/
403     else if (sb->type==CPMFS_DR3 && *status>=16 && *status<=31) /* password */ /*{{{*/
404     {
405       /* check name and extension */ /*{{{*/
406       {
407         int i;
408         char *c;
409
410         for (i=0; i<8; ++i)
411         {
412           c=&(dir->name[i]);
413           if (!ISFILECHAR(i,*c&0x7f) || islower(*c&0x7f))
414           {
415             printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i);
416             if (ask("Clear password entry"))
417             {
418               *status=(char)0xE5;
419               ret|=MODIFIED;
420               break;
421             }
422             else ret|=BROKEN;
423           }
424         }
425         if (*status==(char)0xe5) continue;
426         for (i=0; i<3; ++i)
427         {
428           c=&(dir->ext[i]);
429           if (!ISFILECHAR(1,*c&0x7f) || islower(*c&0x7f))
430           {
431             printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i);
432             if (ask("Clear password entry"))
433             {
434               *status=(char)0xE5;
435               ret|=MODIFIED;
436               break;
437             }
438             else ret|=BROKEN;
439           }
440         }
441         if (*status==(char)0xe5) continue;
442       }
443       /*}}}*/
444       /* check password */ /*{{{*/
445       if (dir->extnol&(0x80|0x40|0x20) && pwdCheck(extent,dir->pointers,dir->lrc))
446       {
447         char msg[80];
448
449         sprintf(msg,"Set password to %c%c%c%c%c%c%c%c",T0,T1,T2,T3,T4,T5,T6,T7);
450         if (ask(msg))
451         {
452           dir->pointers[0]=P0;
453           dir->pointers[1]=P1;
454           dir->pointers[2]=P2;
455           dir->pointers[3]=P3;
456           dir->pointers[4]=P4;
457           dir->pointers[5]=P5;
458           dir->pointers[6]=P6;
459           dir->pointers[7]=P7;
460           dir->lrc=PB;
461           ret|=MODIFIED;
462         }
463         else ret|=BROKEN;
464       }
465       /*}}}*/
466     }
467     /*}}}*/
468     else if (*status!=(char)0xe5) /* bad status */ /*{{{*/
469     {
470       printf("Error: Bad status (extent=%d, name=\"%s\", status=0x%02x)\n",extent,prfile(sb,extent),*status&0xff);
471       if (ask("Clear entry"))
472       {
473         *status=(char)0xE5;
474         ret|=MODIFIED;
475       }
476       else ret|=BROKEN;
477       continue;
478     }
479     /*}}}*/
480   }
481   /*}}}*/
482   /* Phase 2: check extent connectivity */ /*{{{*/
483   printf("Phase 2: check extent connectivity\n");
484   /* check multiple allocated blocks */ /*{{{*/
485   for (extent=0; extent<sb->maxdir; ++extent) if ((dir=sb->dir+extent)->status>=0 && dir->status<=(sb->type==CPMFS_P2DOS ? 31 : 15))
486   {
487     int i,j,block,block2;
488
489     for (i=0; i<16; ++i)
490     {
491       block=dir->pointers[i]&0xff;
492       if (sb->size>=256) block+=(dir->pointers[++i]&0xff)<<8;
493       for (extent2=0; extent2<sb->maxdir; ++extent2) if ((dir2=sb->dir+extent2)->status>=0 && dir2->status<=(sb->type==CPMFS_P2DOS ? 31 : 15))
494       {
495         for (j=0; j<16; ++j)
496         {
497           block2=dir2->pointers[j]&0xff;
498           if (sb->size>=256) block2+=(dir2->pointers[++j]&0xff)<<8;
499           if (block!=0 && block2!=0 && block==block2 && !(extent==extent2 && i==j))
500           {
501             printf("Error: Multiple allocated block (extent=%d,%d, name=\"%s\"",extent,extent2,prfile(sb,extent));
502             printf(",\"%s\" block=%d)\n",prfile(sb,extent2),block);
503             ret|=BROKEN;
504           }
505         }
506       }
507     }
508   }
509   /*}}}*/
510   /* check multiple extents */ /*{{{*/
511   for (extent=0; extent<sb->maxdir; ++extent) if ((dir=sb->dir+extent)->status>=0 && dir->status<=(sb->type==CPMFS_P2DOS ? 31 : 15))
512   {
513     for (extent2=0; extent2<sb->maxdir; ++extent2) if ((dir2=sb->dir+extent2)->status>=0 && dir2->status<=(sb->type==CPMFS_P2DOS ? 31 : 15))
514     {
515       if (extent!=extent2 && EXTENT(dir->extnol,dir->extnoh)==EXTENT(dir2->extnol,dir2->extnoh) && dir->status==dir2->status)
516       {
517         int i;
518
519         for (i=0; i<8 && (dir->name[i]&0x7f)==(dir2->name[i]&0x7f); ++i);
520         if (i==8)
521         {
522           for (i=0; i<3 && (dir->ext[i]&0x7f)==(dir2->ext[i]&0x7f); ++i);
523           if (i==3)
524           {
525             printf("Error: Duplicate extent (extent=%d,%d)\n",extent,extent2);
526             ret|=BROKEN;
527           }
528         }
529       }
530     }
531   }
532   /*}}}*/
533   /*}}}*/
534   if (ret==0) /* print statistics */ /*{{{*/
535   {
536     struct cpmStatFS statfsbuf;
537     int fragmented=0,borders=0;
538
539     cpmStatFS(root,&statfsbuf);
540     for (extent=0; extent<sb->maxdir; ++extent) if ((dir=sb->dir+extent)->status>=0 && dir->status<=(sb->type==CPMFS_P2DOS ? 31 : 15))
541     {
542       int i,block,previous=-1;
543
544       for (i=0; i<16; ++i)
545       {
546         block=dir->pointers[i]&0xff;
547         if (sb->size>=256) block+=(dir->pointers[++i]&0xff)<<8;
548         if (previous!=-1)
549         {
550           if (block!=0 && block!=(previous+1)) ++fragmented;
551           ++borders;
552         }
553         previous=block;
554       }
555     }
556     fragmented=(borders ? (1000*fragmented)/borders : 0);
557     printf("%s: %ld/%ld files (%d.%d%% non-contigous), %ld/%ld blocks\n",image,statfsbuf.f_files-statfsbuf.f_ffree,statfsbuf.f_files,fragmented/10,fragmented%10,statfsbuf.f_blocks-statfsbuf.f_bfree,statfsbuf.f_blocks);
558   }
559   /*}}}*/
560   return ret;
561 }
562 /*}}}*/
563
564 const char cmd[]="fsck.cpm";
565
566 /* main */ /*{{{*/
567 int main(int argc, char *argv[])
568 {
569   const char *err;
570   const char *image;
571   const char *format=FORMAT;
572   const char *devopts=NULL;
573   int c,usage=0;
574   struct cpmSuperBlock sb;
575   struct cpmInode root;
576   enum Result ret;
577
578   while ((c=getopt(argc,argv,"T:f:nh?"))!=EOF) switch(c)
579   {
580     case 'f': format=optarg; break;
581     case 'T': devopts=optarg; break;
582     case 'n': norepair=1; break;
583     case 'h':
584     case '?': usage=1; break;
585   }
586
587   if (optind!=(argc-1)) usage=1;
588   else image=argv[optind];
589
590   if (usage)
591   {
592     fprintf(stderr,"Usage: %s [-f format] [-n] image\n",cmd);
593     exit(1);
594   }
595   if ((err=Device_open(&sb.dev, image, (norepair ? O_RDONLY : O_RDWR), devopts)))
596   {
597     if ((err=Device_open(&sb.dev, image,O_RDONLY, devopts)))
598     {
599       fprintf(stderr,"%s: can not open %s: %s\n",cmd,image,err);
600       exit(1);
601     }
602     else
603     {
604       fprintf(stderr,"%s: can not open %s for writing, no repair possible\n",cmd,image);
605     }
606   }
607   cpmReadSuper(&sb,&root,format);
608   ret=fsck(&root,image);
609   if (ret&MODIFIED)
610   {
611     if (cpmSync(&sb)==-1)
612     {
613       fprintf(stderr,"%s: write error on %s: %s\n",cmd,image,strerror(errno));
614       ret|=BROKEN;
615     }
616     fprintf(stderr,"%s: FILE SYSTEM ON %s MODIFIED",cmd,image);
617     if (ret&BROKEN) fprintf(stderr,", PLEASE CHECK AGAIN");
618     fprintf(stderr,"\n");
619   }
620   cpmUmount(&sb);
621   if (ret&BROKEN) return 2;
622   else return 0;
623 }
624 /*}}}*/