Imported Upstream version 2.12
[debian/cpmtools] / cpmfs.c
1 /* #includes */ /*{{{C}}}*//*{{{*/
2 #include "config.h"
3
4 #include <sys/stat.h>
5 #include <assert.h>
6 #include <ctype.h>
7 #include <errno.h>
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include <string.h>
11 #include <time.h>
12
13 #include "cpmdir.h"
14 #include "cpmfs.h"
15
16 #ifdef USE_DMALLOC
17 #include <dmalloc.h>
18 #endif
19 /*}}}*/
20 /* #defines */ /*{{{*/
21 #undef CPMFS_DEBUG
22
23 /* Number of _used_ bits per int */
24
25 #define INTBITS ((int)(sizeof(int)*8))
26
27 /* There are four reserved entries: ., .., [passwd] and [label] */
28
29 #define RESERVED_ENTRIES 4
30
31 /* CP/M does not support any kind of inodes, so they are simulated.
32 Inode 0-(maxdir-1) are used by files which lowest extent is stored in
33 the respective directory entry.  Inode maxdir is the root directory
34 and inode maxdir+1 is the passwd file, if any. */
35
36 #define RESERVED_INODES 3
37
38 #define PASSWD_RECLEN 24
39 /*}}}*/
40
41 extern char **environ;
42 const char *boo;
43 static mode_t s_ifdir=1;
44 static mode_t s_ifreg=1;
45
46 /* memcpy7            -- Copy string, leaving 8th bit alone      */ /*{{{*/
47 static void memcpy7(char *dest, const char *src, int count)
48 {
49   while (count--)
50   {
51     *dest = ((*dest) & 0x80) | ((*src) & 0x7F);
52     ++dest;
53     ++src;
54   }
55 }
56 /*}}}*/
57
58 /* file name conversions */ 
59 /* splitFilename      -- split file name into name and extension */ /*{{{*/
60 static int splitFilename(const char *fullname, int type, char *name, char *ext, int *user) 
61 {
62   int i,j;
63
64   assert(fullname!=(const char*)0);
65   assert(name!=(char*)0);
66   assert(ext!=(char*)0);
67   assert(user!=(int*)0);
68   memset(name,' ',8);
69   memset(ext,' ',3);
70   if (!isdigit(fullname[0]) || !isdigit(fullname[1]))
71   {
72     boo="illegal CP/M filename";
73     return -1;
74   }
75   *user=10*(fullname[0]-'0')+(fullname[1]-'0');
76   fullname+=2;
77   if ((fullname[0]=='\0') || (type==CPMFS_DR22 && *user>=16) || (type==CPMFS_P2DOS && *user>=32))
78   {
79     boo="illegal CP/M filename";
80     return -1;
81   }
82   for (i=0; i<8 && fullname[i] && fullname[i]!='.'; ++i) if (!ISFILECHAR(i,fullname[i]))
83   {
84     boo="illegal CP/M filename";
85     return -1;
86   }
87   else name[i]=toupper(fullname[i]);
88   if (fullname[i]=='.')
89   {
90     ++i;
91     for (j=0; j<3 && fullname[i]; ++i,++j) if (!ISFILECHAR(1,fullname[i]))
92     {
93       boo="illegal CP/M filename";
94       return -1;
95     }
96     else ext[j]=toupper(fullname[i]);
97     if (i==1 && j==0)
98     {
99       boo="illegal CP/M filename";
100       return -1;
101     }
102   }
103   return 0;
104 }
105 /*}}}*/
106 /* isMatching         -- do two file names match?                */ /*{{{*/
107 static int isMatching(int user1, const char *name1, const char *ext1, int user2, const char *name2, const char *ext2)
108 {
109   int i;
110
111   assert(name1!=(const char*)0);
112   assert(ext1!=(const char*)0);
113   assert(name2!=(const char*)0);
114   assert(ext2!=(const char*)0);
115   if (user1!=user2) return 0;
116   for (i=0; i<8; ++i) if ((name1[i]&0x7f)!=(name2[i]&0x7f)) return 0;
117   for (i=0; i<3; ++i) if ((ext1[i]&0x7f)!=(ext2[i]&0x7f)) return 0;
118   return 1;
119 }
120 /*}}}*/
121
122 /* time conversions */
123 /* cpm2unix_time      -- convert CP/M time to UTC                */ /*{{{*/
124 static time_t cpm2unix_time(int days, int hour, int min)
125 {
126   /* CP/M stores timestamps in local time.  We don't know which     */
127   /* timezone was used and if DST was in effect.  Assuming it was   */
128   /* the current offset from UTC is most sensible, but not perfect. */
129
130   int year,days_per_year;
131   static int days_per_month[]={31,0,31,30,31,30,31,31,30,31,30,31};
132   char **old_environ;
133   static char gmt0[]="TZ=GMT0";
134   static char *gmt_env[]={ gmt0, (char*)0 };
135   struct tm tms;
136   time_t lt,t;
137
138   time(&lt);
139   t=lt;
140   tms=*localtime(&lt);
141   old_environ=environ;
142   environ=gmt_env;
143   lt=mktime(&tms);
144   lt-=t;
145   tms.tm_sec=0;
146   tms.tm_min=((min>>4)&0xf)*10+(min&0xf);
147   tms.tm_hour=((hour>>4)&0xf)*10+(hour&0xf);
148   tms.tm_mday=1;
149   tms.tm_mon=0;
150   tms.tm_year=78;
151   tms.tm_isdst=-1;
152   for (;;)
153   {
154     year=tms.tm_year+1900;
155     days_per_year=((year%4)==0 && ((year%100) || (year%400)==0)) ? 366 : 365;
156     if (days>days_per_year)
157     {
158       days-=days_per_year;
159       ++tms.tm_year;
160     }
161     else break;
162   }
163   for (;;)
164   {
165     days_per_month[1]=(days_per_year==366) ? 29 : 28;
166     if (days>days_per_month[tms.tm_mon])
167     {
168       days-=days_per_month[tms.tm_mon];
169       ++tms.tm_mon;
170     }
171     else break;
172   }
173   t=mktime(&tms)+(days-1)*24*3600;
174   environ=old_environ;
175   t-=lt;
176   return t;
177 }
178 /*}}}*/
179 /* unix2cpm_time      -- convert UTC to CP/M time                */ /*{{{*/
180 static void unix2cpm_time(time_t now, int *days, int *hour, int *min) 
181 {
182   struct tm *tms;
183   int i;
184
185   tms=localtime(&now);
186   *min=((tms->tm_min/10)<<4)|(tms->tm_min%10);
187   *hour=((tms->tm_hour/10)<<4)|(tms->tm_hour%10);
188   for (i=1978,*days=0; i<1900+tms->tm_year; ++i)
189   {
190     *days+=365;
191     if (i%4==0 && (i%100!=0 || i%400==0)) ++*days;
192   }
193   *days += tms->tm_yday+1;
194 }
195 /*}}}*/
196
197 /* allocation vector bitmap functions */
198 /* alvInit            -- init allocation vector                  */ /*{{{*/
199 static void alvInit(const struct cpmSuperBlock *d)
200 {
201   int i,j,offset,block;
202
203   assert(d!=(const struct cpmSuperBlock*)0);
204   /* clean bitmap */ /*{{{*/
205   memset(d->alv,0,d->alvSize*sizeof(int));
206   /*}}}*/
207   /* mark directory blocks as used */ /*{{{*/
208   *d->alv=(1<<((d->maxdir*32+d->blksiz-1)/d->blksiz))-1;
209   /*}}}*/
210   for (i=0; i<d->maxdir; ++i) /* mark file blocks as used */ /*{{{*/
211   {
212     if (d->dir[i].status>=0 && d->dir[i].status<=(d->type==CPMFS_P2DOS ? 31 : 15))
213     {
214 #ifdef CPMFS_DEBUG
215       fprintf(stderr,"alvInit: allocate extent %d\n",i);
216 #endif
217       for (j=0; j<16; ++j)
218       {
219         block=(unsigned char)d->dir[i].pointers[j];
220         if (d->size>=256) block+=(((unsigned char)d->dir[i].pointers[++j])<<8);
221         if (block && block<d->size)
222         {
223 #ifdef CPMFS_DEBUG
224           fprintf(stderr,"alvInit: allocate block %d\n",block);
225 #endif
226           offset=block/INTBITS;
227           d->alv[offset]|=(1<<block%INTBITS);
228         }
229       }
230     }
231   }
232   /*}}}*/
233 }
234 /*}}}*/
235 /* allocBlock         -- allocate a new disk block               */ /*{{{*/
236 static int allocBlock(const struct cpmSuperBlock *drive)
237 {
238   int i,j,bits,block;
239
240   assert(drive!=(const struct cpmSuperBlock*)0);
241   for (i=0; i<drive->alvSize; ++i)
242   {
243     for (j=0,bits=drive->alv[i]; j<INTBITS; ++j)
244     {
245       if ((bits&1)==0)
246       {
247         block=i*INTBITS+j;
248         if (block>=drive->size)
249         {
250           boo="device full";
251           return -1;
252         }
253         drive->alv[i] |= (1<<j);
254         return block;
255       }
256       bits >>= 1;
257     }
258   }
259   boo="device full";
260   return -1;
261 }
262 /*}}}*/
263
264 /* logical block I/O */
265 /* readBlock          -- read a (partial) block                  */ /*{{{*/
266 static int readBlock(const struct cpmSuperBlock *d, int blockno, char *buffer, int start, int end)
267 {
268   int sect, track, counter;
269
270   assert(blockno>=0);
271   assert(blockno<d->size);
272   assert(buffer!=(char*)0);
273   if (end<0) end=d->blksiz/d->secLength-1;
274   sect=(blockno*(d->blksiz/d->secLength)+ d->sectrk*d->boottrk)%d->sectrk;
275   track=(blockno*(d->blksiz/d->secLength)+ d->sectrk*d->boottrk)/d->sectrk;
276   for (counter=0; counter<=end; ++counter)
277   {
278     const char *err;
279
280     if (counter>=start && (err=Device_readSector(&d->dev,track,d->skewtab[sect],buffer+(d->secLength*counter))))
281     {
282       boo=err;
283       return -1;
284     }
285     ++sect;
286     if (sect>=d->sectrk) 
287     {
288       sect = 0;
289       ++track;
290     }
291   }
292   return 0;
293 }
294 /*}}}*/
295 /* writeBlock         -- write a (partial) block                 */ /*{{{*/
296 static int writeBlock(const struct cpmSuperBlock *d, int blockno, const char *buffer, int start, int end)
297 {
298   int sect, track, counter;
299
300   assert(blockno>=0);
301   assert(blockno<d->size);
302   assert(buffer!=(const char*)0);
303   if (end < 0) end=d->blksiz/d->secLength-1;
304   sect = (blockno*(d->blksiz/d->secLength))%d->sectrk;
305   track = (blockno*(d->blksiz/d->secLength))/d->sectrk+d->boottrk;
306   for (counter = 0; counter<=end; ++counter)
307   {
308     const char *err;
309
310     if (counter>=start && (err=Device_writeSector(&d->dev,track,d->skewtab[sect],buffer+(d->secLength*counter))))
311     {
312       boo=err;
313       return -1;
314     }
315     ++sect;
316     if (sect>=d->sectrk) 
317     {
318       sect=0;
319       ++track;
320     }
321   }
322   return 0;
323 }
324 /*}}}*/
325
326 /* directory management */
327 /* readPhysDirectory  -- read directory from drive               */ /*{{{*/
328 static int readPhysDirectory(const struct cpmSuperBlock *drive)
329 {
330   int i,blocks,entry;
331
332   blocks=(drive->maxdir*32+drive->blksiz-1)/drive->blksiz;
333   entry=0;
334   for (i=0; i<blocks; ++i) 
335   {
336     if (readBlock(drive,i,(char*)(drive->dir+entry),0,-1)==-1) return -1;
337     entry+=(drive->blksiz/32);
338   }
339   return 0;
340 }
341 /*}}}*/
342 /* writePhysDirectory -- write directory to drive                */ /*{{{*/
343 static int writePhysDirectory(const struct cpmSuperBlock *drive)
344 {
345   int i,blocks,entry;
346
347   blocks=(drive->maxdir*32+drive->blksiz-1)/drive->blksiz;
348   entry=0;
349   for (i=0; i<blocks; ++i) 
350   {
351     if (writeBlock(drive,i,(char*)(drive->dir+entry),0,-1)==-1) return -1;
352     entry+=(drive->blksiz/32);
353   }
354   return 0;
355 }
356 /*}}}*/
357 /* findFileExtent     -- find first/next extent for a file       */ /*{{{*/
358 static int findFileExtent(const struct cpmSuperBlock *sb, int user, const char *name, const char *ext, int start, int extno)
359 {
360   boo="file already exists";
361   for (; start<sb->maxdir; ++start)
362   {
363     if
364     (
365       ((unsigned char)sb->dir[start].status)<=(sb->type==CPMFS_P2DOS ? 31 : 15)
366       && (extno==-1 || (EXTENT(sb->dir[start].extnol,sb->dir[start].extnoh)/sb->extents)==(extno/sb->extents))
367       && isMatching(user,name,ext,sb->dir[start].status,sb->dir[start].name,sb->dir[start].ext)
368     ) return start;
369   }
370   boo="file not found";
371   return -1;
372 }
373 /*}}}*/
374 /* findFreeExtent     -- find first free extent                  */ /*{{{*/
375 static int findFreeExtent(const struct cpmSuperBlock *drive)
376 {
377   int i;
378
379   for (i=0; i<drive->maxdir; ++i) if (drive->dir[i].status==(char)0xe5) return (i);
380   boo="directory full";
381   return -1;
382 }
383 /*}}}*/
384 /* updateTimeStamps   -- convert time stamps to CP/M format      */ /*{{{*/
385 static void updateTimeStamps(const struct cpmInode *ino, int extent)
386 {
387   struct PhysDirectoryEntry *date;
388   int i;
389   int ca_min,ca_hour,ca_days,u_min,u_hour,u_days;
390
391   if (!S_ISREG(ino->mode)) return;
392 #ifdef CPMFS_DEBUG
393   fprintf(stderr,"CPMFS: updating time stamps for inode %d (%d)\n",extent,extent&3);
394 #endif
395   unix2cpm_time(ino->sb->cnotatime ? ino->ctime : ino->atime,&ca_days,&ca_hour,&ca_min);
396   unix2cpm_time(ino->mtime,&u_days,&u_hour,&u_min);
397   if ((ino->sb->type==CPMFS_P2DOS || ino->sb->type==CPMFS_DR3) && (date=ino->sb->dir+(extent|3))->status==0x21)
398   {
399     switch (extent&3)
400     {
401       case 0: /* first entry */ /*{{{*/
402       {
403         date->name[0]=ca_days&0xff; date->name[1]=ca_days>>8;
404         date->name[2]=ca_hour;
405         date->name[3]=ca_min;
406         date->name[4]=u_days&0xff; date->name[5]=u_days>>8;
407         date->name[6]=u_hour;
408         date->name[7]=u_min;
409         break;
410       }
411       /*}}}*/
412       case 1: /* second entry */ /*{{{*/
413       {
414         date->ext[2]=ca_days&0xff; date->extnol=ca_days>>8;
415         date->lrc=ca_hour;
416         date->extnoh=ca_min;
417         date->blkcnt=u_days&0xff; date->pointers[0]=u_days>>8;
418         date->pointers[1]=u_hour;
419         date->pointers[2]=u_min;
420         break;
421       }
422       /*}}}*/
423       case 2: /* third entry */ /*{{{*/
424       {
425         date->pointers[5]=ca_days&0xff; date->pointers[6]=ca_days>>8;
426         date->pointers[7]=ca_hour;
427         date->pointers[8]=ca_min;
428         date->pointers[9]=u_days&0xff; date->pointers[10]=u_days>>8;
429         date->pointers[11]=u_hour;
430         date->pointers[12]=u_min;
431         break;
432       }
433       /*}}}*/
434     }
435   }
436 }
437 /*}}}*/
438
439 /* diskdefReadSuper   -- read super block from diskdefs file     */ /*{{{*/
440 static int diskdefReadSuper(struct cpmSuperBlock *d, const char *format)
441 {
442   char line[256];
443   FILE *fp;
444   int insideDef=0,found=0;
445
446   if ((fp=fopen(DISKDEFS,"r"))==(FILE*)0 && (fp=fopen("diskdefs","r"))==(FILE*)0)
447   {
448     fprintf(stderr,"%s: Neither " DISKDEFS " nor diskdefs could be opened.\n",cmd);
449     exit(1);
450   }
451   while (fgets(line,sizeof(line),fp)!=(char*)0)
452   {
453     int argc;
454     char *argv[2];
455
456     for (argc=0; argc<1 && (argv[argc]=strtok(argc ? (char*)0 : line," \t\n")); ++argc);
457     if ((argv[argc]=strtok((char*)0,"\n"))!=(char*)0) ++argc;
458     if (insideDef)
459     {
460       if (argc==1 && strcmp(argv[0],"end")==0)
461       {
462         insideDef=0;
463         d->size=(d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz;
464         if (d->extents==0) d->extents=((d->size>=256 ? 8 : 16)*d->blksiz)/16384;
465         if (d->extents==0) d->extents=1;
466         if (found) break;
467       }
468       else if (argc==2)
469       {
470         if (strcmp(argv[0],"seclen")==0) d->secLength=strtol(argv[1],(char**)0,0);
471         else if (strcmp(argv[0],"tracks")==0) d->tracks=strtol(argv[1],(char**)0,0);
472         else if (strcmp(argv[0],"sectrk")==0) d->sectrk=strtol(argv[1],(char**)0,0);
473         else if (strcmp(argv[0],"blocksize")==0) d->blksiz=strtol(argv[1],(char**)0,0);
474         else if (strcmp(argv[0],"maxdir")==0) d->maxdir=strtol(argv[1],(char**)0,0);
475         else if (strcmp(argv[0],"skew")==0) d->skew=strtol(argv[1],(char**)0,0);
476         else if (strcmp(argv[0],"skewtab")==0)
477         {
478           int pass,sectors;
479
480           for (pass=0; pass<2; ++pass)
481           {
482             char *s;
483
484             sectors=0;
485             for (s=argv[1]; *s; )
486             {
487               int phys;
488               char *end;
489
490               phys=strtol(s,&end,10);
491               if (pass==1) d->skewtab[sectors]=phys;
492               if (end==s)
493               {
494                 fprintf(stderr,"%s: invalid skewtab `%s' at `%s'\n",cmd,argv[1],s);
495                 exit(1);
496               }
497               s=end;
498               ++sectors;
499               if (*s==',') ++s;
500             }
501             if (pass==0) d->skewtab=malloc(sizeof(int)*sectors);
502           }
503         }
504         else if (strcmp(argv[0],"boottrk")==0) d->boottrk=strtol(argv[1],(char**)0,0);
505         else if (strcmp(argv[0],"logicalextents")==0) d->extents=strtol(argv[1],(char**)0,0);
506         else if (strcmp(argv[0],"os")==0)
507         {
508           if (strcmp(argv[1],"2.2")==0) d->type=CPMFS_DR22;
509           else if (strcmp(argv[1],"3")==0) d->type=CPMFS_DR3;
510           else if (strcmp(argv[1],"p2dos")==0) d->type=CPMFS_P2DOS;
511         }
512       }
513       else if (argc>0 && argv[0][0]!='#')
514       {
515         fprintf(stderr,"%s: invalid keyword `%s'\n",cmd,argv[0]);
516         exit(1);
517       }
518     }
519     else if (argc==2 && strcmp(argv[0],"diskdef")==0)
520     {
521       insideDef=1;
522       d->skew=1;
523       d->extents=0;
524       d->type=CPMFS_DR3;
525       d->skewtab=(int*)0;
526       if (strcmp(argv[1],format)==0) found=1;
527     }
528   }
529   fclose(fp);
530   if (!found)
531   {
532     fprintf(stderr,"%s: unknown format %s\n",cmd,format);
533     exit(1);
534   }
535   return 0;
536 }
537 /*}}}*/
538 /* amsReadSuper       -- read super block from amstrad disk      */ /*{{{*/
539 static int amsReadSuper(struct cpmSuperBlock *d, const char *format)
540 {
541   unsigned char boot_sector[512], *boot_spec;
542   const char *err;
543
544   Device_setGeometry(&d->dev,512,9,40);
545   if ((err=Device_readSector(&d->dev, 0, 0, (char *)boot_sector)))
546   {
547     fprintf(stderr,"%s: Failed to read Amstrad superblock (%s)\n",cmd,err);
548     exit(1);
549   }
550   boot_spec=(boot_sector[0] == 0 || boot_sector[0] == 3)?boot_sector:(unsigned char*)0;
551   /* Check for JCE's extension to allow Amstrad and MSDOS superblocks
552    * in the same sector (for the PCW16)
553    */
554   if
555   (
556     (boot_sector[0] == 0xE9 || boot_sector[0] == 0xEB)
557     && !memcmp(boot_sector + 0x2B, "CP/M", 4)
558     && !memcmp(boot_sector + 0x33, "DSK",  3)
559     && !memcmp(boot_sector + 0x7C, "CP/M", 4)
560   ) boot_spec = boot_sector + 128;
561   if (boot_spec==(unsigned char*)0)
562   {
563     fprintf(stderr,"%s: Amstrad superblock not present\n",cmd);
564     exit(1);
565   }
566   /* boot_spec[0] = format number: 0 for SS SD, 3 for DS DD
567               [1] = single/double sided and density flags
568               [2] = cylinders per side
569               [3] = sectors per cylinder
570               [4] = Physical sector shift, 2 => 512
571               [5] = Reserved track count
572               [6] = Block shift
573               [7] = No. of directory blocks
574    */
575   d->type = CPMFS_DR3;  /* Amstrads are CP/M 3 systems */
576   d->secLength = 128 << boot_spec[4];
577   d->tracks    = boot_spec[2];
578   if (boot_spec[1] & 3) d->tracks *= 2;
579   d->sectrk    = boot_spec[3];
580   d->blksiz    = 128 << boot_spec[6];
581   d->maxdir    = (d->blksiz / 32) * boot_spec[7];
582   d->skew      = 1; /* Amstrads skew at the controller level */
583   d->skewtab   = (int*)0;
584   d->boottrk   = boot_spec[5];
585   d->size      = (d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz;
586   d->extents   = ((d->size>=256 ? 8 : 16)*d->blksiz)/16384;
587  
588   return 0;
589 }
590 /*}}}*/
591
592 /* match              -- match filename against a pattern        */ /*{{{*/
593 static int recmatch(const char *a, const char *pattern)
594 {
595   int first=1;
596
597   while (*pattern)
598   {
599     switch (*pattern)
600     {
601       case '*':
602       {
603         if (*a=='.' && first) return 1;
604         ++pattern;
605         while (*a) if (recmatch(a,pattern)) return 1; else ++a;
606         break;
607       }
608       case '?':
609       {
610         if (*a) { ++a; ++pattern; } else return 0;
611         break;
612       }
613       default: if (tolower(*a)==tolower(*pattern)) { ++a; ++pattern; } else return 0;
614     }
615     first=0;
616   }
617   return (*pattern=='\0' && *a=='\0');
618 }
619
620 int match(const char *a, const char *pattern) 
621 {
622   int user;
623   char pat[255];
624
625   assert(strlen(pattern)<255);
626   if (isdigit(*pattern) && *(pattern+1)==':') { user=(*pattern-'0'); pattern+=2; }
627   else if (isdigit(*pattern) && isdigit(*(pattern+1)) && *(pattern+2)==':') { user=(10*(*pattern-'0')+(*(pattern+1)-'0')); pattern+=3; }
628   else user=-1;
629   if (user==-1) sprintf(pat,"??%s",pattern);
630   else sprintf(pat,"%02d%s",user,pattern);
631   return recmatch(a,pat);
632 }
633
634 /*}}}*/
635 /* cpmglob            -- expand CP/M style wildcards             */ /*{{{*/
636 void cpmglob(int optin, int argc, char * const argv[], struct cpmInode *root, int *gargc, char ***gargv)
637 {
638   struct cpmFile dir;
639   int entries,dirsize=0;
640   struct cpmDirent *dirent=(struct cpmDirent*)0;
641   int gargcap=0,i,j;
642
643   *gargv=(char**)0;
644   *gargc=0;
645   cpmOpendir(root,&dir);
646   entries=0;
647   dirsize=8;
648   dirent=malloc(sizeof(struct cpmDirent)*dirsize);
649   while (cpmReaddir(&dir,&dirent[entries]))
650   {
651     ++entries;
652     if (entries==dirsize) dirent=realloc(dirent,sizeof(struct cpmDirent)*(dirsize*=2));
653   }
654   for (i=optin; i<argc; ++i)
655   {
656     int found;
657
658     for (j=0,found=0; j<entries; ++j)
659     {
660       if (match(dirent[j].name,argv[i]))
661       {
662         if (*gargc==gargcap) *gargv=realloc(*gargv,sizeof(char*)*(gargcap ? (gargcap*=2) : (gargcap=16)));
663         (*gargv)[*gargc]=strcpy(malloc(strlen(dirent[j].name)+1),dirent[j].name);
664         ++*gargc;
665         ++found;
666       }
667     }
668     if (found==0)
669     {
670       char pat[255];
671       char *pattern=argv[i];
672       int user;
673
674       if (isdigit(*pattern) && *(pattern+1)==':') { user=(*pattern-'0'); pattern+=2; }
675       else if (isdigit(*pattern) && isdigit(*(pattern+1)) && *(pattern+2)==':') { user=(10*(*pattern-'0')+(*(pattern+1)-'0')); pattern+=3; }
676       else user=-1;
677       if (user==-1) sprintf(pat,"??%s",pattern);
678       else sprintf(pat,"%02d%s",user,pattern);
679
680       if (*gargc==gargcap) *gargv=realloc(*gargv,sizeof(char*)*(gargcap ? (gargcap*=2) : (gargcap=16)));
681       (*gargv)[*gargc]=strcpy(malloc(strlen(pat)+1),pat);
682       ++*gargc;
683     }
684   }
685   free(dirent);
686 }
687 /*}}}*/
688
689 /* cpmReadSuper       -- get DPB and init in-core data for drive */ /*{{{*/
690 int cpmReadSuper(struct cpmSuperBlock *d, struct cpmInode *root, const char *format)
691 {
692   while (s_ifdir && !S_ISDIR(s_ifdir)) s_ifdir<<=1;
693   assert(s_ifdir);
694   while (s_ifreg && !S_ISREG(s_ifreg)) s_ifreg<<=1;
695   assert(s_ifreg);
696   if (strcmp(format, "amstrad")==0) amsReadSuper(d,format);
697   else diskdefReadSuper(d,format);
698   Device_setGeometry(&d->dev,d->secLength,d->sectrk,d->tracks);
699   if (d->skewtab==(int*)0) /* generate skew table */ /*{{{*/
700   {
701     int i,j,k;
702
703     if (( d->skewtab = malloc(d->sectrk*sizeof(int))) == (int*)0) 
704     {
705       fprintf(stderr,"%s: can not allocate memory for skew sector table\n",cmd);
706       exit(1);
707     }
708     memset(d->skewtab,0,d->sectrk*sizeof(int));
709     for (i=j=0; i<d->sectrk; ++i,j=(j+d->skew)%d->sectrk)
710     {
711       while (1)
712       {
713         for (k=0; k<i && d->skewtab[k]!=j; ++k);
714         if (k<i) j=(j+1)%d->sectrk;
715         else break;
716       }
717       d->skewtab[i]=j;
718     }
719   }
720   /*}}}*/
721   /* initialise allocation vector bitmap */ /*{{{*/
722   {
723     d->alvSize=((d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz+INTBITS-1)/INTBITS;
724     if ((d->alv=malloc(d->alvSize*sizeof(int)))==(int*)0) 
725     {
726       boo="out of memory";
727       return -1;
728     }
729   }
730   /*}}}*/
731   /* allocate directory buffer */ /*{{{*/
732   if ((d->dir=malloc(d->maxdir*32))==(struct PhysDirectoryEntry*)0)
733   {
734     boo="out of memory";
735     return -1;
736   }
737   /*}}}*/
738   if (d->dev.opened==0) memset(d->dir,0xe5,d->maxdir*32);
739   else if (readPhysDirectory(d)==-1) return -1;
740   alvInit(d);
741   if (d->type==CPMFS_DR3) /* read additional superblock information */ /*{{{*/
742   {
743     int i;
744
745     /* passwords */ /*{{{*/
746     {
747       int passwords=0;
748
749       for (i=0; i<d->maxdir; ++i) if (d->dir[i].status>=16 && d->dir[i].status<=31) ++passwords;
750 #ifdef CPMFS_DEBUG
751       fprintf(stderr,"getformat: found %d passwords\n",passwords);
752 #endif
753       if ((d->passwdLength=passwords*PASSWD_RECLEN))
754       {
755         if ((d->passwd=malloc(d->passwdLength))==(char*)0)
756         {
757           boo="out of memory";
758           return -1;
759         }
760         for (i=0,passwords=0; i<d->maxdir; ++i) if (d->dir[i].status>=16 && d->dir[i].status<=31)
761         {
762           int j,pb;
763           char *p=d->passwd+(passwords++*PASSWD_RECLEN);
764
765           p[0]='0'+(d->dir[i].status-16)/10;
766           p[1]='0'+(d->dir[i].status-16)%10;
767           for (j=0; j<8; ++j) p[2+j]=d->dir[i].name[j]&0x7f;
768           p[10]=(d->dir[i].ext[0]&0x7f)==' ' ? ' ' : '.';
769           for (j=0; j<3; ++j) p[11+j]=d->dir[i].ext[j]&0x7f;
770           p[14]=' ';
771           pb=(unsigned char)d->dir[i].lrc;
772           for (j=0; j<8; ++j) p[15+j]=((unsigned char)d->dir[i].pointers[7-j])^pb;
773 #ifdef CPMFS_DEBUG
774           p[23]='\0';
775           fprintf(stderr,"getformat: %s\n",p);
776 #endif        
777           p[23]='\n';
778         }
779       }
780     }
781     /*}}}*/
782     /* disc label */ /*{{{*/
783     for (i=0; i<d->maxdir; ++i) if (d->dir[i].status==(char)0x20)
784     {
785       int j;
786
787       d->cnotatime=d->dir[i].extnol&0x10;
788       if (d->dir[i].extnol&0x1)
789       {
790         d->labelLength=12;
791         if ((d->label=malloc(d->labelLength))==(char*)0)
792         {
793           boo="out of memory";
794           return -1;
795         }
796         for (j=0; j<8; ++j) d->label[j]=d->dir[i].name[j]&0x7f;
797         for (j=0; j<3; ++j) d->label[8+j]=d->dir[i].ext[j]&0x7f;
798         d->label[11]='\n';
799       }
800       else
801       {
802         d->labelLength=0;
803       }
804       break;
805     }
806     if (i==d->maxdir)
807     {
808       d->cnotatime=1;
809       d->labelLength=0;
810     }
811     /*}}}*/
812   }
813   /*}}}*/
814   else
815   {
816     d->passwdLength=0;
817     d->cnotatime=1;
818     d->labelLength=0;
819   }
820   d->root=root;
821   root->ino=d->maxdir;
822   root->sb=d;
823   root->mode=(s_ifdir|0777);
824   root->size=0;
825   root->atime=root->mtime=root->ctime=0;
826   return 0;
827 }
828 /*}}}*/
829 /* cpmNamei           -- map name to inode                       */ /*{{{*/
830 int cpmNamei(const struct cpmInode *dir, const char *filename, struct cpmInode *i)
831 {
832   /* variables */ /*{{{*/
833   int user;
834   char name[8],extension[3];
835   struct PhysDirectoryEntry *date;
836   int highestExtno,highestExt=-1,lowestExtno,lowestExt=-1;
837   int protectMode=0;
838   /*}}}*/
839
840   if (!S_ISDIR(dir->mode))
841   {
842     boo="No such file";
843     return -1;
844   }
845   if (strcmp(filename,".")==0 || strcmp(filename,"..")==0) /* root directory */ /*{{{*/
846   {
847     *i=*dir;
848     return 0;
849   }
850   /*}}}*/
851   else if (strcmp(filename,"[passwd]")==0 && dir->sb->passwdLength) /* access passwords */ /*{{{*/
852   {
853     i->attr=0;
854     i->ino=dir->sb->maxdir+1;
855     i->mode=s_ifreg|0444;
856     i->sb=dir->sb;
857     i->atime=i->mtime=i->ctime=0;
858     i->size=i->sb->passwdLength;
859     return 0;
860   }
861   /*}}}*/
862   else if (strcmp(filename,"[label]")==0 && dir->sb->labelLength) /* access label */ /*{{{*/
863   {
864     i->attr=0;
865     i->ino=dir->sb->maxdir+2;
866     i->mode=s_ifreg|0444;
867     i->sb=dir->sb;
868     i->atime=i->mtime=i->ctime=0;
869     i->size=i->sb->labelLength;
870     return 0;
871   }
872   /*}}}*/
873   if (splitFilename(filename,dir->sb->type,name,extension,&user)==-1) return -1;
874   /* find highest and lowest extent */ /*{{{*/
875   {
876     int extent;
877
878     i->size=0;
879     extent=-1;
880     highestExtno=-1;
881     lowestExtno=2049;
882     while ((extent=findFileExtent(dir->sb,user,name,extension,extent+1,-1))!=-1)
883     {
884       int extno=EXTENT(dir->sb->dir[extent].extnol,dir->sb->dir[extent].extnoh);
885
886       if (extno>highestExtno)
887       {
888         highestExtno=extno;
889         highestExt=extent;
890       }
891       if (extno<lowestExtno)
892       {
893         lowestExtno=extno;
894         lowestExt=extent;
895       }
896     }
897   }
898   /*}}}*/
899   if (highestExtno==-1) return -1;
900   /* calculate size */ /*{{{*/
901   {
902     int block;
903
904     i->size=highestExtno*16384;
905     if (dir->sb->size<256) for (block=15; block>=0; --block)
906     {
907       if (dir->sb->dir[highestExt].pointers[block]) break;
908     }
909     else for (block=7; block>=0; --block)
910     {
911       if (dir->sb->dir[highestExt].pointers[2*block] || dir->sb->dir[highestExt].pointers[2*block+1]) break;
912     }
913     if (dir->sb->dir[highestExt].blkcnt) i->size+=((dir->sb->dir[highestExt].blkcnt&0xff)-1)*128;
914     i->size+=dir->sb->dir[highestExt].lrc ? (dir->sb->dir[highestExt].lrc&0xff) : 128;
915 #ifdef CPMFS_DEBUG
916     fprintf(stderr,"cpmNamei: size=%ld\n",(long)i->size);
917 #endif
918   }
919   /*}}}*/
920   i->ino=lowestExt;
921   i->mode=s_ifreg;
922   i->sb=dir->sb;
923   /* set timestamps */ /*{{{*/
924   if 
925   (
926     (dir->sb->type==CPMFS_P2DOS || dir->sb->type==CPMFS_DR3)
927     && (date=dir->sb->dir+(lowestExt|3))->status==0x21
928   )
929   {
930     /* variables */ /*{{{*/
931     int u_days=0,u_hour=0,u_min=0;
932     int ca_days=0,ca_hour=0,ca_min=0;
933     /*}}}*/
934
935     switch (lowestExt&3)
936     {
937       case 0: /* first entry of the four */ /*{{{*/
938       {
939         ca_days=((unsigned char)date->name[0])+(((unsigned char)date->name[1])<<8);
940         ca_hour=(unsigned char)date->name[2];
941         ca_min=(unsigned char)date->name[3];
942         u_days=((unsigned char)date->name[4])+(((unsigned char)date->name[5])<<8);
943         u_hour=(unsigned char)date->name[6];
944         u_min=(unsigned char)date->name[7];
945         protectMode=(unsigned char)date->ext[0];
946         break;
947       }
948       /*}}}*/
949       case 1: /* second entry */ /*{{{*/
950       {
951         ca_days=((unsigned char)date->ext[2])+(((unsigned char)date->extnol)<<8);
952         ca_hour=(unsigned char)date->lrc;
953         ca_min=(unsigned char)date->extnoh;
954         u_days=((unsigned char)date->blkcnt)+(((unsigned char)date->pointers[0])<<8);
955         u_hour=(unsigned char)date->pointers[1];
956         u_min=(unsigned char)date->pointers[2];
957         protectMode=(unsigned char)date->pointers[3];
958         break;
959       }
960       /*}}}*/
961       case 2: /* third one */ /*{{{*/
962       {
963         ca_days=((unsigned char)date->pointers[5])+(((unsigned char)date->pointers[6])<<8);
964         ca_hour=(unsigned char)date->pointers[7];
965         ca_min=(unsigned char)date->pointers[8];
966         u_days=((unsigned char)date->pointers[9])+(((unsigned char)date->pointers[10])<<8);
967         u_hour=(unsigned char)date->pointers[11];
968         u_min=(unsigned char)date->pointers[12];
969         protectMode=(unsigned char)date->pointers[13];
970         break;
971       }
972       /*}}}*/
973     }
974     if (i->sb->cnotatime)
975     {
976       i->ctime=cpm2unix_time(ca_days,ca_hour,ca_min);
977       i->atime=0;
978     }
979     else
980     {
981       i->ctime=0;
982       i->atime=cpm2unix_time(ca_days,ca_hour,ca_min);
983     }
984     i->mtime=cpm2unix_time(u_days,u_hour,u_min);
985   }
986   else i->atime=i->mtime=i->ctime=0;
987   /*}}}*/
988
989   /* Determine the inode attributes */
990   i->attr = 0;
991   if (dir->sb->dir[lowestExt].name[0]&0x80) i->attr |= CPM_ATTR_F1;
992   if (dir->sb->dir[lowestExt].name[1]&0x80) i->attr |= CPM_ATTR_F2;
993   if (dir->sb->dir[lowestExt].name[2]&0x80) i->attr |= CPM_ATTR_F3;
994   if (dir->sb->dir[lowestExt].name[3]&0x80) i->attr |= CPM_ATTR_F4;
995   if (dir->sb->dir[lowestExt].ext [0]&0x80) i->attr |= CPM_ATTR_RO;
996   if (dir->sb->dir[lowestExt].ext [1]&0x80) i->attr |= CPM_ATTR_SYS;
997   if (dir->sb->dir[lowestExt].ext [2]&0x80) i->attr |= CPM_ATTR_ARCV;
998   if (protectMode&0x20)                     i->attr |= CPM_ATTR_PWDEL;
999   if (protectMode&0x40)                     i->attr |= CPM_ATTR_PWWRITE;
1000   if (protectMode&0x80)                     i->attr |= CPM_ATTR_PWREAD;
1001
1002   if (dir->sb->dir[lowestExt].ext[1]&0x80) i->mode|=01000;
1003   i->mode|=0444;
1004   if (!(dir->sb->dir[lowestExt].ext[0]&0x80)) i->mode|=0222;
1005   if (extension[0]=='C' && extension[1]=='O' && extension[2]=='M') i->mode|=0111;
1006   return 0;
1007 }
1008 /*}}}*/
1009 /* cpmStatFS          -- statfs                                  */ /*{{{*/
1010 void cpmStatFS(const struct cpmInode *ino, struct cpmStatFS *buf)
1011 {
1012   int i;
1013   struct cpmSuperBlock *d;
1014
1015   d=ino->sb;
1016   buf->f_bsize=d->blksiz;
1017   buf->f_blocks=(d->tracks*d->sectrk*d->secLength)/d->blksiz;
1018   buf->f_bfree=0;
1019   buf->f_bused=-(d->maxdir*32+d->blksiz-1)/d->blksiz;
1020   for (i=0; i<d->alvSize; ++i)
1021   {
1022     int temp,j;
1023
1024     temp = *(d->alv+i);
1025     for (j=0; j<INTBITS; ++j)
1026     {
1027       if (i*INTBITS+j < d->size)
1028       {
1029         if (1&temp)
1030         {
1031 #ifdef CPMFS_DEBUG
1032           fprintf(stderr,"cpmStatFS: block %d allocated\n",(i*INTBITS+j));
1033 #endif
1034           ++buf->f_bused;
1035         }
1036         else ++buf->f_bfree;
1037       }
1038       temp >>= 1;
1039     }
1040   }
1041   buf->f_bavail=buf->f_bfree;
1042   buf->f_files=d->maxdir;
1043   buf->f_ffree=0;
1044   for (i=0; i<d->maxdir; ++i)
1045   {
1046     if (d->dir[i].status==(char)0xe5) ++buf->f_ffree;
1047   }
1048   buf->f_namelen=11;
1049 }
1050 /*}}}*/
1051 /* cpmUnlink          -- unlink                                  */ /*{{{*/
1052 int cpmUnlink(const struct cpmInode *dir, const char *fname)
1053 {
1054   int user;
1055   char name[8],extension[3];
1056   int extent;
1057   struct cpmSuperBlock *drive;
1058
1059   if (!S_ISDIR(dir->mode))
1060   {
1061     boo="No such file";
1062     return -1;
1063   }
1064   drive=dir->sb;
1065   if (splitFilename(fname,dir->sb->type,name,extension,&user)==-1) return -1;
1066   if ((extent=findFileExtent(drive,user,name,extension,0,-1))==-1) return -1;
1067   drive->dir[extent].status=(char)0xe5;
1068   do
1069   {
1070     drive->dir[extent].status=(char)0xe5;
1071   } while ((extent=findFileExtent(drive,user,name,extension,extent+1,-1))>=0);
1072   if (writePhysDirectory(drive)==-1) return -1;
1073   alvInit(drive);
1074   return 0;
1075 }
1076 /*}}}*/
1077 /* cpmRename          -- rename                                  */ /*{{{*/
1078 int cpmRename(const struct cpmInode *dir, const char *old, const char *new)
1079 {
1080   struct cpmSuperBlock *drive;
1081   int extent;
1082   int olduser;
1083   char oldname[8], oldext[3];
1084   int newuser;
1085   char newname[8], newext[3];
1086
1087   if (!S_ISDIR(dir->mode))
1088   {
1089     boo="No such file";
1090     return -1;
1091   }
1092   drive=dir->sb;
1093   if (splitFilename(old,dir->sb->type, oldname, oldext,&olduser)==-1) return -1;
1094   if (splitFilename(new,dir->sb->type, newname, newext,&newuser)==-1) return -1;
1095   if ((extent=findFileExtent(drive,olduser,oldname,oldext,0,-1))==-1) return -1;
1096   if (findFileExtent(drive,newuser,newname, newext,0,-1)!=-1) 
1097   {
1098     boo="file already exists";
1099     return -1;
1100   }
1101   do 
1102   {
1103     drive->dir[extent].status=newuser;
1104     memcpy7(drive->dir[extent].name, newname, 8);
1105     memcpy7(drive->dir[extent].ext, newext, 3);
1106   } while ((extent=findFileExtent(drive,olduser,oldname,oldext,extent+1,-1))!=-1);
1107   if (writePhysDirectory(drive)==-1) return -1;
1108   return 0;
1109 }
1110 /*}}}*/
1111 /* cpmOpendir         -- opendir                                 */ /*{{{*/
1112 int cpmOpendir(struct cpmInode *dir, struct cpmFile *dirp)
1113 {
1114   if (!S_ISDIR(dir->mode))
1115   {
1116     boo="No such file";
1117     return -1;
1118   }
1119   dirp->ino=dir;
1120   dirp->pos=0;
1121   dirp->mode=O_RDONLY;
1122   return 0;
1123 }
1124 /*}}}*/
1125 /* cpmReaddir         -- readdir                                 */ /*{{{*/
1126 int cpmReaddir(struct cpmFile *dir, struct cpmDirent *ent)
1127 {
1128   /* variables */ /*{{{*/
1129   struct PhysDirectoryEntry *cur=(struct PhysDirectoryEntry*)0;
1130   char buf[2+8+1+3+1]; /* 00foobarxy.zzy\0 */
1131   int i;
1132   char *bufp;
1133   int hasext;
1134   /*}}}*/
1135
1136   if (!(S_ISDIR(dir->ino->mode))) /* error: not a directory */ /*{{{*/
1137   {
1138     boo="not a directory";
1139     return -1;
1140   }
1141   /*}}}*/
1142   while (1)
1143   {
1144     if (dir->pos==0) /* first entry is . */ /*{{{*/
1145     {
1146       ent->ino=dir->ino->sb->maxdir;
1147       ent->reclen=1;
1148       strcpy(ent->name,".");
1149       ent->off=dir->pos;
1150       ++dir->pos;
1151       return 1;
1152     }
1153     /*}}}*/
1154     else if (dir->pos==1) /* next entry is .. */ /*{{{*/
1155     {
1156       ent->ino=dir->ino->sb->maxdir;
1157       ent->reclen=2;
1158       strcpy(ent->name,"..");
1159       ent->off=dir->pos;
1160       ++dir->pos;
1161       return 1;
1162     }
1163     /*}}}*/
1164     else if (dir->pos==2)
1165     {
1166       if (dir->ino->sb->passwdLength) /* next entry is [passwd] */ /*{{{*/
1167       {
1168         ent->ino=dir->ino->sb->maxdir+1;
1169         ent->reclen=8;
1170         strcpy(ent->name,"[passwd]");
1171         ent->off=dir->pos;
1172         ++dir->pos;
1173         return 1;
1174       }
1175       /*}}}*/
1176     }
1177     else if (dir->pos==3)
1178     {
1179       if (dir->ino->sb->labelLength) /* next entry is [label] */ /*{{{*/
1180       {
1181         ent->ino=dir->ino->sb->maxdir+2;
1182         ent->reclen=7;
1183         strcpy(ent->name,"[label]");
1184         ent->off=dir->pos;
1185         ++dir->pos;
1186         return 1;
1187       }
1188       /*}}}*/
1189     }
1190     else if (dir->pos>=RESERVED_ENTRIES && dir->pos<dir->ino->sb->maxdir+RESERVED_ENTRIES)
1191     {
1192       int first=dir->pos-RESERVED_ENTRIES;
1193
1194       if ((cur=dir->ino->sb->dir+(dir->pos-RESERVED_ENTRIES))->status>=0 && cur->status<=(dir->ino->sb->type==CPMFS_P2DOS ? 31 : 15))
1195       {
1196         /* determine first extent for the current file */ /*{{{*/
1197         for (i=0; i<dir->ino->sb->maxdir; ++i) if (i!=(dir->pos-RESERVED_ENTRIES))
1198         {
1199           if (isMatching(cur->status,cur->name,cur->ext,dir->ino->sb->dir[i].status,dir->ino->sb->dir[i].name,dir->ino->sb->dir[i].ext) && EXTENT(cur->extnol,cur->extnoh)>EXTENT(dir->ino->sb->dir[i].extnol,dir->ino->sb->dir[i].extnoh)) first=i;
1200         }
1201         /*}}}*/
1202         if (first==(dir->pos-RESERVED_ENTRIES))
1203         {
1204           ent->ino=dir->pos-RESERVED_INODES;
1205           /* convert file name to UNIX style */ /*{{{*/
1206           buf[0]='0'+cur->status/10;
1207           buf[1]='0'+cur->status%10;
1208           for (bufp=buf+2,i=0; i<8 && (cur->name[i]&0x7f)!=' '; ++i) *bufp++=tolower(cur->name[i]&0x7f);
1209           for (hasext=0,i=0; i<3 && (cur->ext[i]&0x7f)!=' '; ++i)
1210           {
1211             if (!hasext) { *bufp++='.'; hasext=1; }
1212             *bufp++=tolower(cur->ext[i]&0x7f);
1213           }
1214           *bufp='\0';
1215           /*}}}*/
1216           assert(bufp<=buf+sizeof(buf));
1217           ent->reclen=bufp-buf;
1218           strcpy(ent->name,buf);
1219           ent->off=dir->pos;
1220           ++dir->pos;
1221           return 1;
1222         }
1223       }
1224     }
1225     else return 0;
1226     ++dir->pos;
1227   }
1228 }
1229 /*}}}*/
1230 /* cpmStat            -- stat                                    */ /*{{{*/
1231 void cpmStat(const struct cpmInode *ino, struct cpmStat *buf)
1232 {
1233   buf->ino=ino->ino;
1234   buf->mode=ino->mode;
1235   buf->size=ino->size;
1236   buf->atime=ino->atime;
1237   buf->mtime=ino->mtime;
1238   buf->ctime=ino->ctime;
1239 }
1240 /*}}}*/
1241 /* cpmOpen            -- open                                    */ /*{{{*/
1242 int cpmOpen(struct cpmInode *ino, struct cpmFile *file, mode_t mode)
1243 {
1244   if (S_ISREG(ino->mode))
1245   {
1246     if ((mode&O_WRONLY) && (ino->mode&0222)==0)
1247     {
1248       boo="permission denied";
1249       return -1;
1250     }
1251     file->pos=0;
1252     file->ino=ino;
1253     file->mode=mode;
1254     return 0;
1255   }
1256   else
1257   {
1258     boo="not a regular file";
1259     return -1;
1260   }
1261 }
1262 /*}}}*/
1263 /* cpmRead            -- read                                    */ /*{{{*/
1264 int cpmRead(struct cpmFile *file, char *buf, int count)
1265 {
1266   int findext=1,findblock=1,extent=-1,block=-1,extentno=-1,got=0,nextblockpos=-1,nextextpos=-1;
1267   int blocksize=file->ino->sb->blksiz;
1268   int extcap;
1269
1270   extcap=(file->ino->sb->size<256 ? 16 : 8)*blocksize;
1271   if (extcap>16384) extcap=16384*file->ino->sb->extents;
1272   if (file->ino->ino==file->ino->sb->maxdir+1) /* [passwd] */ /*{{{*/
1273   {
1274     if ((file->pos+count)>file->ino->size) count=file->ino->size-file->pos;
1275     if (count) memcpy(buf,file->ino->sb->passwd+file->pos,count);
1276     file->pos+=count;
1277 #ifdef CPMFS_DEBUG
1278     fprintf(stderr,"cpmRead passwd: read %d bytes, now at position %ld\n",count,(long)file->pos);
1279 #endif
1280     return count;
1281   }
1282   /*}}}*/
1283   else if (file->ino->ino==file->ino->sb->maxdir+2) /* [label] */ /*{{{*/
1284   {
1285     if ((file->pos+count)>file->ino->size) count=file->ino->size-file->pos;
1286     if (count) memcpy(buf,file->ino->sb->label+file->pos,count);
1287     file->pos+=count;
1288 #ifdef CPMFS_DEBUG
1289     fprintf(stderr,"cpmRead label: read %d bytes, now at position %ld\n",count,(long)file->pos);
1290 #endif
1291     return count;
1292   }
1293   /*}}}*/
1294   else while (count>0 && file->pos<file->ino->size)
1295   {
1296     char buffer[16384];
1297
1298     if (findext)
1299     {
1300       extentno=file->pos/16384;
1301       extent=findFileExtent(file->ino->sb,file->ino->sb->dir[file->ino->ino].status,file->ino->sb->dir[file->ino->ino].name,file->ino->sb->dir[file->ino->ino].ext,0,extentno);
1302       nextextpos=(file->pos/extcap)*extcap+extcap;
1303       findext=0;
1304       findblock=1;
1305     }
1306     if (findblock)
1307     {
1308       if (extent!=-1)
1309       {
1310         int start,end,ptr;
1311
1312         ptr=(file->pos%extcap)/blocksize;
1313         if (file->ino->sb->size>=256) ptr*=2;
1314         block=(unsigned char)file->ino->sb->dir[extent].pointers[ptr];
1315         if (file->ino->sb->size>=256) block+=((unsigned char)file->ino->sb->dir[extent].pointers[ptr+1])<<8;
1316         if (block==0)
1317         {
1318           memset(buffer,0,blocksize);
1319         }
1320         else
1321         {
1322           start=(file->pos%blocksize)/file->ino->sb->secLength;
1323           end=((file->pos%blocksize+count)>blocksize ? blocksize-1 : (file->pos%blocksize+count-1))/file->ino->sb->secLength;
1324           readBlock(file->ino->sb,block,buffer,start,end);
1325         }
1326       }
1327       nextblockpos=(file->pos/blocksize)*blocksize+blocksize;
1328       findblock=0;
1329     }
1330     if (file->pos<nextblockpos)
1331     {
1332       if (extent==-1) *buf++='\0'; else *buf++=buffer[file->pos%blocksize];
1333       ++file->pos;
1334       ++got;
1335       --count;
1336     }
1337     else if (file->pos==nextextpos) findext=1; else findblock=1;
1338   }
1339 #ifdef CPMFS_DEBUG
1340   fprintf(stderr,"cpmRead: read %d bytes, now at position %ld\n",got,(long)file->pos);
1341 #endif
1342   return got;
1343 }
1344 /*}}}*/
1345 /* cpmWrite           -- write                                   */ /*{{{*/
1346 int cpmWrite(struct cpmFile *file, const char *buf, int count)
1347 {
1348   int findext=1,findblock=-1,extent=-1,extentno=-1,got=0,nextblockpos=-1,nextextpos=-1;
1349   int blocksize=file->ino->sb->blksiz;
1350   int extcap=(file->ino->sb->size<256 ? 16 : 8)*blocksize;
1351   int block=-1,start=-1,end=-1,ptr=-1,last=-1;
1352   char buffer[16384];
1353
1354   while (count>0)
1355   {
1356     if (findext) /*{{{*/
1357     {
1358       extentno=file->pos/16384;
1359       extent=findFileExtent(file->ino->sb,file->ino->sb->dir[file->ino->ino].status,file->ino->sb->dir[file->ino->ino].name,file->ino->sb->dir[file->ino->ino].ext,0,extentno);
1360       nextextpos=(file->pos/extcap)*extcap+extcap;
1361       if (extent==-1)
1362       {
1363         if ((extent=findFreeExtent(file->ino->sb))==-1) return (got==0 ? -1 : got);
1364         file->ino->sb->dir[extent]=file->ino->sb->dir[file->ino->ino];
1365         memset(file->ino->sb->dir[extent].pointers,0,16);
1366         file->ino->sb->dir[extent].extnol=EXTENTL(extentno);
1367         file->ino->sb->dir[extent].extnoh=EXTENTH(extentno);
1368         file->ino->sb->dir[extent].blkcnt=0;
1369         file->ino->sb->dir[extent].lrc=0;
1370         updateTimeStamps(file->ino,extent);
1371       }
1372       findext=0;
1373       findblock=1;
1374     }
1375     /*}}}*/
1376     if (findblock) /*{{{*/
1377     {
1378       ptr=(file->pos%extcap)/blocksize;
1379       if (file->ino->sb->size>=256) ptr*=2;
1380       block=(unsigned char)file->ino->sb->dir[extent].pointers[ptr];
1381       if (file->ino->sb->size>=256) block+=((unsigned char)file->ino->sb->dir[extent].pointers[ptr+1])<<8;
1382       if (block==0) /* allocate new block, set start/end to cover it */ /*{{{*/
1383       {
1384         if ((block=allocBlock(file->ino->sb))==-1) return (got==0 ? -1 : got);
1385         file->ino->sb->dir[extent].pointers[ptr]=block&0xff;
1386         if (file->ino->sb->size>=256) file->ino->sb->dir[extent].pointers[ptr+1]=(block>>8)&0xff;
1387         start=0;
1388         end=(blocksize-1)/file->ino->sb->secLength;
1389         memset(buffer,0,blocksize);
1390       }
1391       /*}}}*/
1392       else /* read existing block and set start/end to cover modified parts */ /*{{{*/
1393       {
1394         start=(file->pos%blocksize)/file->ino->sb->secLength;
1395         end=((file->pos%blocksize+count)>blocksize ? blocksize-1 : (file->pos%blocksize+count-1))/file->ino->sb->secLength;
1396         if (file->pos%file->ino->sb->secLength) readBlock(file->ino->sb,block,buffer,start,start);
1397         if (end!=start && (file->pos+count-1)<blocksize) readBlock(file->ino->sb,block,buffer+end*file->ino->sb->secLength,end,end);
1398       }
1399       /*}}}*/
1400       nextblockpos=(file->pos/blocksize)*blocksize+blocksize;
1401       findblock=0;
1402     }
1403     /*}}}*/
1404     /* fill block and write it */ /*{{{*/
1405     while (file->pos!=nextblockpos && count)
1406     {
1407       buffer[file->pos%blocksize]=*buf++;
1408       ++file->pos;
1409       if (file->ino->size<file->pos) file->ino->size=file->pos;
1410       ++got;
1411       --count;
1412     }
1413     (void)writeBlock(file->ino->sb,block,buffer,start,end);
1414     if (file->ino->sb->size<256) for (last=15; last>=0; --last)
1415     {
1416       if (file->ino->sb->dir[extent].pointers[last])
1417       {
1418         break;
1419       }
1420     }
1421     else for (last=14; last>0; last-=2)
1422     {
1423       if (file->ino->sb->dir[extent].pointers[last] || file->ino->sb->dir[extent].pointers[last+1])
1424       {
1425         last/=2;
1426         break;
1427       }
1428     }
1429     if (last>0) extentno+=(last*blocksize)/extcap;
1430     file->ino->sb->dir[extent].extnol=EXTENTL(extentno);
1431     file->ino->sb->dir[extent].extnoh=EXTENTH(extentno);
1432     file->ino->sb->dir[extent].blkcnt=((file->pos-1)%16384)/128+1;
1433     file->ino->sb->dir[extent].lrc=file->pos%128;
1434     updateTimeStamps(file->ino,extent);
1435     /*}}}*/
1436     if (file->pos==nextextpos) findext=1;
1437     else if (file->pos==nextblockpos) findblock=1;
1438   }
1439   writePhysDirectory(file->ino->sb);
1440   return got;
1441 }
1442 /*}}}*/
1443 /* cpmClose           -- close                                   */ /*{{{*/
1444 int cpmClose(struct cpmFile *file)
1445 {
1446   if (file->mode&O_WRONLY) return (writePhysDirectory(file->ino->sb));
1447   return 0;
1448 }
1449 /*}}}*/
1450 /* cpmCreat           -- creat                                   */ /*{{{*/
1451 int cpmCreat(struct cpmInode *dir, const char *fname, struct cpmInode *ino, mode_t mode)
1452 {
1453   int user;
1454   char name[8],extension[3];
1455   int extent;
1456   struct cpmSuperBlock *drive;
1457   struct PhysDirectoryEntry *ent;
1458
1459   if (!S_ISDIR(dir->mode))
1460   {
1461     boo="No such file or directory";
1462     return -1;
1463   }
1464   if (splitFilename(fname,dir->sb->type,name,extension,&user)==-1) return -1;
1465 #ifdef CPMFS_DEBUG
1466   fprintf(stderr,"cpmCreat: %s -> %d:%-.8s.%-.3s\n",fname,user,name,extension);
1467 #endif
1468   if (findFileExtent(dir->sb,user,name,extension,0,-1)!=-1) return -1;
1469   drive=dir->sb;
1470   if ((extent=findFreeExtent(dir->sb))==-1) return -1;
1471   ent=dir->sb->dir+extent;
1472   memset(ent,0,32);
1473   ent->status=user;
1474   memcpy(ent->name,name,8);
1475   memcpy(ent->ext,extension,3);
1476   ino->ino=extent;
1477   ino->mode=s_ifreg|mode;
1478   ino->size=0;
1479   time(&ino->atime);
1480   time(&ino->mtime);
1481   time(&ino->ctime);
1482   ino->sb=dir->sb;
1483   updateTimeStamps(ino,extent);
1484   writePhysDirectory(dir->sb);
1485   return 0;
1486 }
1487 /*}}}*/
1488 /* cpmAttrGet         -- get CP/M attributes                     */ /*{{{*/
1489 int cpmAttrGet(struct cpmInode *ino, cpm_attr_t *attrib)
1490 {
1491         *attrib = ino->attr;
1492         return 0;
1493 }
1494 /*}}}*/
1495 /* cpmAttrSet         -- set CP/M attributes                     */ /*{{{*/
1496 int cpmAttrSet(struct cpmInode *ino, cpm_attr_t attrib)
1497 {
1498   struct cpmSuperBlock *drive;
1499   int extent;
1500   int user;
1501   char name[8], extension[3];
1502   
1503   memset(name,      0, sizeof(name));
1504   memset(extension, 0, sizeof(extension));
1505   drive  = ino->sb;
1506   extent = ino->ino;
1507   
1508   /* Strip off existing attribute bits */
1509   memcpy7(name,      drive->dir[extent].name, 8);
1510   memcpy7(extension, drive->dir[extent].ext,  3);
1511   user = drive->dir[extent].status;
1512   
1513   /* And set new ones */
1514   if (attrib & CPM_ATTR_F1)   name[0]      |= 0x80;
1515   if (attrib & CPM_ATTR_F2)   name[1]      |= 0x80;
1516   if (attrib & CPM_ATTR_F3)   name[2]      |= 0x80;
1517   if (attrib & CPM_ATTR_F4)   name[3]      |= 0x80;
1518   if (attrib & CPM_ATTR_RO)   extension[0] |= 0x80;
1519   if (attrib & CPM_ATTR_SYS)  extension[1] |= 0x80;
1520   if (attrib & CPM_ATTR_ARCV) extension[2] |= 0x80;
1521   
1522   do 
1523   {
1524     memcpy(drive->dir[extent].name, name, 8);
1525     memcpy(drive->dir[extent].ext, extension, 3);
1526   } while ((extent=findFileExtent(drive, user,name,extension,extent+1,-1))!=-1);
1527   if (writePhysDirectory(drive)==-1) return -1;
1528
1529   /* Update the stored (inode) copies of the file attributes and mode */
1530   ino->attr=attrib;
1531   if (attrib&CPM_ATTR_RO) ino->mode&=~(S_IWUSR|S_IWGRP|S_IWOTH);
1532   else ino->mode|=(S_IWUSR|S_IWGRP|S_IWOTH);
1533   
1534   return 0;
1535 }
1536 /*}}}*/
1537 /* cpmChmod           -- set CP/M r/o & sys                      */ /*{{{*/
1538 int cpmChmod(struct cpmInode *ino, mode_t mode)
1539 {
1540         /* Convert the chmod() into a chattr() call that affects RO */
1541         int newatt = ino->attr & ~CPM_ATTR_RO;
1542
1543         if (!(mode & (S_IWUSR|S_IWGRP|S_IWOTH))) newatt |= CPM_ATTR_RO;
1544         return cpmAttrSet(ino, newatt);
1545 }
1546 /*}}}*/
1547 /* cpmSync            -- write directory back                    */ /*{{{*/
1548 int cpmSync(struct cpmSuperBlock *sb)
1549 {
1550   return (writePhysDirectory(sb));
1551 }
1552 /*}}}*/
1553 /* cpmUmount          -- free super block                        */ /*{{{*/
1554 void cpmUmount(struct cpmSuperBlock *sb)
1555 {
1556   free(sb->alv);
1557   free(sb->skewtab);
1558   free(sb->dir);
1559   if (sb->passwdLength) free(sb->passwd);
1560 }
1561 /*}}}*/