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