Imported Upstream version 2.10
[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],"boottrk")==0) d->boottrk=strtol(argv[1],(char**)0,0);
477         else if (strcmp(argv[0],"logicalextents")==0) d->extents=strtol(argv[1],(char**)0,0);
478         else if (strcmp(argv[0],"os")==0)
479         {
480           if (strcmp(argv[1],"2.2")==0) d->type=CPMFS_DR22;
481           else if (strcmp(argv[1],"3")==0) d->type=CPMFS_DR3;
482           else if (strcmp(argv[1],"p2dos")==0) d->type=CPMFS_P2DOS;
483         }
484       }
485       else if (argc>0 && argv[0][0]!='#')
486       {
487         fprintf(stderr,"%s: invalid keyword `%s'\n",cmd,argv[0]);
488         exit(1);
489       }
490     }
491     else if (argc==2 && strcmp(argv[0],"diskdef")==0)
492     {
493       insideDef=1;
494       d->skew=1;
495       d->extents=0;
496       d->type=CPMFS_DR3;
497       if (strcmp(argv[1],format)==0) found=1;
498     }
499   }
500   fclose(fp);
501   if (!found)
502   {
503     fprintf(stderr,"%s: unknown format %s\n",cmd,format);
504     exit(1);
505   }
506   return 0;
507 }
508 /*}}}*/
509 /* amsReadSuper       -- read super block from amstrad disk      */ /*{{{*/
510 static int amsReadSuper(struct cpmSuperBlock *d, const char *format)
511 {
512   unsigned char boot_sector[512], *boot_spec;
513   const char *err;
514
515   Device_setGeometry(&d->dev,512,9,40);
516   if ((err=Device_readSector(&d->dev, 0, 0, (char *)boot_sector)))
517   {
518     fprintf(stderr,"%s: Failed to read Amstrad superblock (%s)\n",cmd,err);
519     exit(1);
520   }
521   boot_spec=(boot_sector[0] == 0 || boot_sector[0] == 3)?boot_sector:(unsigned char*)0;
522   /* Check for JCE's extension to allow Amstrad and MSDOS superblocks
523    * in the same sector (for the PCW16)
524    */
525   if
526   (
527     (boot_sector[0] == 0xE9 || boot_sector[0] == 0xEB)
528     && !memcmp(boot_sector + 0x2B, "CP/M", 4)
529     && !memcmp(boot_sector + 0x33, "DSK",  3)
530     && !memcmp(boot_sector + 0x7C, "CP/M", 4)
531   ) boot_spec = boot_sector + 128;
532   if (boot_spec==(unsigned char*)0)
533   {
534     fprintf(stderr,"%s: Amstrad superblock not present\n",cmd);
535     exit(1);
536   }
537   /* boot_spec[0] = format number: 0 for SS SD, 3 for DS DD
538               [1] = single/double sided and density flags
539               [2] = cylinders per side
540               [3] = sectors per cylinder
541               [4] = Physical sector shift, 2 => 512
542               [5] = Reserved track count
543               [6] = Block shift
544               [7] = No. of directory blocks
545    */
546   d->type = CPMFS_DR3;  /* Amstrads are CP/M 3 systems */
547   d->secLength = 128 << boot_spec[4];
548   d->tracks    = boot_spec[2];
549   if (boot_spec[1] & 3) d->tracks *= 2;
550   d->sectrk    = boot_spec[3];
551   d->blksiz    = 128 << boot_spec[6];
552   d->maxdir    = (d->blksiz / 32) * boot_spec[7];
553   d->skew      = 1; /* Amstrads skew at the controller level */
554   d->boottrk   = boot_spec[5];
555   d->size      = (d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz;
556   d->extents   = ((d->size>=256 ? 8 : 16)*d->blksiz)/16384;
557  
558   return 0;
559 }
560 /*}}}*/
561
562 /* match              -- match filename against a pattern        */ /*{{{*/
563 static int recmatch(const char *a, const char *pattern)
564 {
565   int first=1;
566
567   while (*pattern)
568   {
569     switch (*pattern)
570     {
571       case '*':
572       {
573         if (*a=='.' && first) return 1;
574         ++pattern;
575         while (*a) if (recmatch(a,pattern)) return 1; else ++a;
576         break;
577       }
578       case '?':
579       {
580         if (*a) { ++a; ++pattern; } else return 0;
581         break;
582       }
583       default: if (tolower(*a)==tolower(*pattern)) { ++a; ++pattern; } else return 0;
584     }
585     first=0;
586   }
587   return (*pattern=='\0' && *a=='\0');
588 }
589
590 int match(const char *a, const char *pattern) 
591 {
592   int user;
593   char pat[255];
594
595   assert(strlen(pattern)<255);
596   if (isdigit(*pattern) && *(pattern+1)==':') { user=(*pattern-'0'); pattern+=2; }
597   else if (isdigit(*pattern) && isdigit(*(pattern+1)) && *(pattern+2)==':') { user=(10*(*pattern-'0')+(*(pattern+1)-'0')); pattern+=3; }
598   else user=-1;
599   if (user==-1) sprintf(pat,"??%s",pattern);
600   else sprintf(pat,"%02d%s",user,pattern);
601   return recmatch(a,pat);
602 }
603
604 /*}}}*/
605 /* cpmglob            -- expand CP/M style wildcards             */ /*{{{*/
606 void cpmglob(int optin, int argc, char * const argv[], struct cpmInode *root, int *gargc, char ***gargv)
607 {
608   struct cpmFile dir;
609   int entries,dirsize=0;
610   struct cpmDirent *dirent=(struct cpmDirent*)0;
611   int gargcap=0,i,j;
612
613   *gargv=(char**)0;
614   *gargc=0;
615   cpmOpendir(root,&dir);
616   entries=0;
617   dirsize=8;
618   dirent=malloc(sizeof(struct cpmDirent)*dirsize);
619   while (cpmReaddir(&dir,&dirent[entries]))
620   {
621     ++entries;
622     if (entries==dirsize) dirent=realloc(dirent,sizeof(struct cpmDirent)*(dirsize*=2));
623   }
624   for (i=optin; i<argc; ++i)
625   {
626     int found;
627
628     for (j=0,found=0; j<entries; ++j)
629     {
630       if (match(dirent[j].name,argv[i]))
631       {
632         if (*gargc==gargcap) *gargv=realloc(*gargv,sizeof(char*)*(gargcap ? (gargcap*=2) : (gargcap=16)));
633         (*gargv)[*gargc]=strcpy(malloc(strlen(dirent[j].name)+1),dirent[j].name);
634         ++*gargc;
635         ++found;
636       }
637     }
638     if (found==0)
639     {
640       char pat[255];
641       char *pattern=argv[i];
642       int user;
643
644       if (isdigit(*pattern) && *(pattern+1)==':') { user=(*pattern-'0'); pattern+=2; }
645       else if (isdigit(*pattern) && isdigit(*(pattern+1)) && *(pattern+2)==':') { user=(10*(*pattern-'0')+(*(pattern+1)-'0')); pattern+=3; }
646       else user=-1;
647       if (user==-1) sprintf(pat,"??%s",pattern);
648       else sprintf(pat,"%02d%s",user,pattern);
649
650       if (*gargc==gargcap) *gargv=realloc(*gargv,sizeof(char*)*(gargcap ? (gargcap*=2) : (gargcap=16)));
651       (*gargv)[*gargc]=strcpy(malloc(strlen(pat)+1),pat);
652       ++*gargc;
653     }
654   }
655   free(dirent);
656 }
657 /*}}}*/
658
659 /* cpmReadSuper       -- get DPB and init in-core data for drive */ /*{{{*/
660 int cpmReadSuper(struct cpmSuperBlock *d, struct cpmInode *root, const char *format)
661 {
662   while (s_ifdir && !S_ISDIR(s_ifdir)) s_ifdir<<=1;
663   assert(s_ifdir);
664   while (s_ifreg && !S_ISREG(s_ifreg)) s_ifreg<<=1;
665   assert(s_ifreg);
666   if (strcmp(format, "amstrad")==0) amsReadSuper(d,format);
667   else diskdefReadSuper(d,format);
668   Device_setGeometry(&d->dev,d->secLength,d->sectrk,d->tracks);
669   /* generate skew table */ /*{{{*/
670   if (( d->skewtab = malloc(d->sectrk*sizeof(int))) == (int*)0) 
671   {
672     fprintf(stderr,"%s: can not allocate memory for skew sector table\n",cmd);
673     exit(1);
674   }
675   if (strcmp(format,"apple-do")==0)
676   {
677     static int skew[]={0,6,12,3,9,15,14,5,11,2,8,7,13,4,10,1};
678     memcpy(d->skewtab,skew,d->sectrk*sizeof(int));
679   }
680   else if (strcmp(format,"apple-po")==0)
681   {
682     static int skew[]={0,9,3,12,6,15,1,10,4,13,7,8,2,11,5,14};
683     memcpy(d->skewtab,skew,d->sectrk*sizeof(int));
684   }
685   else
686   {
687     int i,j,k;
688
689     for (i=j=0; i<d->sectrk; ++i,j=(j+d->skew)%d->sectrk)
690     {
691       while (1)
692       {
693         for (k=0; k<i && d->skewtab[k]!=j; ++k);
694         if (k<i) j=(j+1)%d->sectrk;
695         else break;
696       }
697       d->skewtab[i]=j;
698     }
699   }
700   /*}}}*/
701   /* initialise allocation vector bitmap */ /*{{{*/
702   {
703     d->alvSize=((d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz+INTBITS-1)/INTBITS;
704     if ((d->alv=malloc(d->alvSize*sizeof(int)))==(int*)0) 
705     {
706       boo="out of memory";
707       return -1;
708     }
709   }
710   /*}}}*/
711   /* allocate directory buffer */ /*{{{*/
712   if ((d->dir=malloc(d->maxdir*32))==(struct PhysDirectoryEntry*)0)
713   {
714     boo="out of memory";
715     return -1;
716   }
717   /*}}}*/
718   if (d->dev.opened==0) memset(d->dir,0xe5,d->maxdir*32);
719   else if (readPhysDirectory(d)==-1) return -1;
720   alvInit(d);
721   if (d->type==CPMFS_DR3) /* read additional superblock information */ /*{{{*/
722   {
723     int i;
724
725     /* passwords */ /*{{{*/
726     {
727       int passwords=0;
728
729       for (i=0; i<d->maxdir; ++i) if (d->dir[i].status>=16 && d->dir[i].status<=31) ++passwords;
730 #ifdef CPMFS_DEBUG
731       fprintf(stderr,"getformat: found %d passwords\n",passwords);
732 #endif
733       if ((d->passwdLength=passwords*PASSWD_RECLEN))
734       {
735         if ((d->passwd=malloc(d->passwdLength))==(char*)0)
736         {
737           boo="out of memory";
738           return -1;
739         }
740         for (i=0,passwords=0; i<d->maxdir; ++i) if (d->dir[i].status>=16 && d->dir[i].status<=31)
741         {
742           int j,pb;
743           char *p=d->passwd+(passwords++*PASSWD_RECLEN);
744
745           p[0]='0'+(d->dir[i].status-16)/10;
746           p[1]='0'+(d->dir[i].status-16)%10;
747           for (j=0; j<8; ++j) p[2+j]=d->dir[i].name[j]&0x7f;
748           p[10]=(d->dir[i].ext[0]&0x7f)==' ' ? ' ' : '.';
749           for (j=0; j<3; ++j) p[11+j]=d->dir[i].ext[j]&0x7f;
750           p[14]=' ';
751           pb=(unsigned char)d->dir[i].lrc;
752           for (j=0; j<8; ++j) p[15+j]=((unsigned char)d->dir[i].pointers[7-j])^pb;
753 #ifdef CPMFS_DEBUG
754           p[23]='\0';
755           fprintf(stderr,"getformat: %s\n",p);
756 #endif        
757           p[23]='\n';
758         }
759       }
760     }
761     /*}}}*/
762     /* disc label */ /*{{{*/
763     for (i=0; i<d->maxdir; ++i) if (d->dir[i].status==(char)0x20)
764     {
765       int j;
766
767       d->cnotatime=d->dir[i].extnol&0x10;
768       if (d->dir[i].extnol&0x1)
769       {
770         d->labelLength=12;
771         if ((d->label=malloc(d->labelLength))==(char*)0)
772         {
773           boo="out of memory";
774           return -1;
775         }
776         for (j=0; j<8; ++j) d->label[j]=d->dir[i].name[j]&0x7f;
777         for (j=0; j<3; ++j) d->label[8+j]=d->dir[i].ext[j]&0x7f;
778         d->label[11]='\n';
779       }
780       else
781       {
782         d->labelLength=0;
783       }
784       break;
785     }
786     if (i==d->maxdir)
787     {
788       d->cnotatime=1;
789       d->labelLength=0;
790     }
791     /*}}}*/
792   }
793   /*}}}*/
794   else
795   {
796     d->passwdLength=0;
797     d->cnotatime=1;
798     d->labelLength=0;
799   }
800   d->root=root;
801   root->ino=d->maxdir;
802   root->sb=d;
803   root->mode=(s_ifdir|0777);
804   root->size=0;
805   root->atime=root->mtime=root->ctime=0;
806   return 0;
807 }
808 /*}}}*/
809 /* cpmNamei           -- map name to inode                       */ /*{{{*/
810 int cpmNamei(const struct cpmInode *dir, const char *filename, struct cpmInode *i)
811 {
812   /* variables */ /*{{{*/
813   int user;
814   char name[8],extension[3];
815   struct PhysDirectoryEntry *date;
816   int highestExtno,highestExt=-1,lowestExtno,lowestExt=-1;
817   int protectMode=0;
818   /*}}}*/
819
820   if (!S_ISDIR(dir->mode))
821   {
822     boo="No such file";
823     return -1;
824   }
825   if (strcmp(filename,".")==0 || strcmp(filename,"..")==0) /* root directory */ /*{{{*/
826   {
827     *i=*dir;
828     return 0;
829   }
830   /*}}}*/
831   else if (strcmp(filename,"[passwd]")==0 && dir->sb->passwdLength) /* access passwords */ /*{{{*/
832   {
833     i->attr=0;
834     i->ino=dir->sb->maxdir+1;
835     i->mode=s_ifreg|0444;
836     i->sb=dir->sb;
837     i->atime=i->mtime=i->ctime=0;
838     i->size=i->sb->passwdLength;
839     return 0;
840   }
841   /*}}}*/
842   else if (strcmp(filename,"[label]")==0 && dir->sb->labelLength) /* access label */ /*{{{*/
843   {
844     i->attr=0;
845     i->ino=dir->sb->maxdir+2;
846     i->mode=s_ifreg|0444;
847     i->sb=dir->sb;
848     i->atime=i->mtime=i->ctime=0;
849     i->size=i->sb->labelLength;
850     return 0;
851   }
852   /*}}}*/
853   if (splitFilename(filename,dir->sb->type,name,extension,&user)==-1) return -1;
854   /* find highest and lowest extent */ /*{{{*/
855   {
856     int extent;
857
858     i->size=0;
859     extent=-1;
860     highestExtno=-1;
861     lowestExtno=2049;
862     while ((extent=findFileExtent(dir->sb,user,name,extension,extent+1,-1))!=-1)
863     {
864       int extno=EXTENT(dir->sb->dir[extent].extnol,dir->sb->dir[extent].extnoh);
865
866       if (extno>highestExtno)
867       {
868         highestExtno=extno;
869         highestExt=extent;
870       }
871       if (extno<lowestExtno)
872       {
873         lowestExtno=extno;
874         lowestExt=extent;
875       }
876     }
877   }
878   /*}}}*/
879   if (highestExtno==-1) return -1;
880   /* calculate size */ /*{{{*/
881   {
882     int block;
883
884     i->size=highestExtno*16384;
885     if (dir->sb->size<256) for (block=15; block>=0; --block)
886     {
887       if (dir->sb->dir[highestExt].pointers[block]) break;
888     }
889     else for (block=7; block>=0; --block)
890     {
891       if (dir->sb->dir[highestExt].pointers[2*block] || dir->sb->dir[highestExt].pointers[2*block+1]) break;
892     }
893     if (dir->sb->dir[highestExt].blkcnt) i->size+=((dir->sb->dir[highestExt].blkcnt&0xff)-1)*128;
894     i->size+=dir->sb->dir[highestExt].lrc ? (dir->sb->dir[highestExt].lrc&0xff) : 128;
895 #ifdef CPMFS_DEBUG
896     fprintf(stderr,"cpmNamei: size=%ld\n",(long)i->size);
897 #endif
898   }
899   /*}}}*/
900   i->ino=lowestExt;
901   i->mode=s_ifreg;
902   i->sb=dir->sb;
903   /* set timestamps */ /*{{{*/
904   if 
905   (
906     (dir->sb->type==CPMFS_P2DOS || dir->sb->type==CPMFS_DR3)
907     && (date=dir->sb->dir+(lowestExt|3))->status==0x21
908   )
909   {
910     /* variables */ /*{{{*/
911     int u_days=0,u_hour=0,u_min=0;
912     int ca_days=0,ca_hour=0,ca_min=0;
913     /*}}}*/
914
915     switch (lowestExt&3)
916     {
917       case 0: /* first entry of the four */ /*{{{*/
918       {
919         ca_days=((unsigned char)date->name[0])+(((unsigned char)date->name[1])<<8);
920         ca_hour=(unsigned char)date->name[2];
921         ca_min=(unsigned char)date->name[3];
922         u_days=((unsigned char)date->name[4])+(((unsigned char)date->name[5])<<8);
923         u_hour=(unsigned char)date->name[6];
924         u_min=(unsigned char)date->name[7];
925         protectMode=(unsigned char)date->name[8];
926         break;
927       }
928       /*}}}*/
929       case 1: /* second entry */ /*{{{*/
930       {
931         ca_days=((unsigned char)date->ext[2])+(((unsigned char)date->extnol)<<8);
932         ca_hour=(unsigned char)date->lrc;
933         ca_min=(unsigned char)date->extnoh;
934         u_days=((unsigned char)date->blkcnt)+(((unsigned char)date->pointers[0])<<8);
935         u_hour=(unsigned char)date->pointers[1];
936         u_min=(unsigned char)date->pointers[2];
937         protectMode=(unsigned char)date->pointers[3];
938         break;
939       }
940       /*}}}*/
941       case 2: /* third one */ /*{{{*/
942       {
943         ca_days=((unsigned char)date->pointers[5])+(((unsigned char)date->pointers[6])<<8);
944         ca_hour=(unsigned char)date->pointers[7];
945         ca_min=(unsigned char)date->pointers[8];
946         u_days=((unsigned char)date->pointers[9])+(((unsigned char)date->pointers[10])<<8);
947         u_hour=(unsigned char)date->pointers[11];
948         u_min=(unsigned char)date->pointers[12];
949         protectMode=(unsigned char)date->pointers[13];
950         break;
951       }
952       /*}}}*/
953     }
954     if (i->sb->cnotatime)
955     {
956       i->ctime=cpm2unix_time(ca_days,ca_hour,ca_min);
957       i->atime=0;
958     }
959     else
960     {
961       i->ctime=0;
962       i->atime=cpm2unix_time(ca_days,ca_hour,ca_min);
963     }
964     i->mtime=cpm2unix_time(u_days,u_hour,u_min);
965   }
966   else i->atime=i->mtime=i->ctime=0;
967   /*}}}*/
968
969   /* Determine the inode attributes */
970   i->attr = 0;
971   if (dir->sb->dir[lowestExt].name[0]&0x80) i->attr |= CPM_ATTR_F1;
972   if (dir->sb->dir[lowestExt].name[1]&0x80) i->attr |= CPM_ATTR_F2;
973   if (dir->sb->dir[lowestExt].name[2]&0x80) i->attr |= CPM_ATTR_F3;
974   if (dir->sb->dir[lowestExt].name[3]&0x80) i->attr |= CPM_ATTR_F4;
975   if (dir->sb->dir[lowestExt].ext [0]&0x80) i->attr |= CPM_ATTR_RO;
976   if (dir->sb->dir[lowestExt].ext [1]&0x80) i->attr |= CPM_ATTR_SYS;
977   if (dir->sb->dir[lowestExt].ext [2]&0x80) i->attr |= CPM_ATTR_ARCV;
978   if (protectMode&0x20)                     i->attr |= CPM_ATTR_PWDEL;
979   if (protectMode&0x40)                     i->attr |= CPM_ATTR_PWWRITE;
980   if (protectMode&0x80)                     i->attr |= CPM_ATTR_PWREAD;
981
982   if (dir->sb->dir[lowestExt].ext[1]&0x80) i->mode|=01000;
983   i->mode|=0444;
984   if (!(dir->sb->dir[lowestExt].ext[0]&0x80)) i->mode|=0222;
985   if (extension[0]=='C' && extension[1]=='O' && extension[2]=='M') i->mode|=0111;
986   return 0;
987 }
988 /*}}}*/
989 /* cpmStatFS          -- statfs                                  */ /*{{{*/
990 void cpmStatFS(const struct cpmInode *ino, struct cpmStatFS *buf)
991 {
992   int i;
993   struct cpmSuperBlock *d;
994
995   d=ino->sb;
996   buf->f_bsize=d->blksiz;
997   buf->f_blocks=(d->tracks*d->sectrk*d->secLength)/d->blksiz;
998   buf->f_bfree=0;
999   buf->f_bused=-(d->maxdir*32+d->blksiz-1)/d->blksiz;
1000   for (i=0; i<d->alvSize; ++i)
1001   {
1002     int temp,j;
1003
1004     temp = *(d->alv+i);
1005     for (j=0; j<INTBITS; ++j)
1006     {
1007       if (i*INTBITS+j < d->size)
1008       {
1009         if (1&temp)
1010         {
1011 #ifdef CPMFS_DEBUG
1012           fprintf(stderr,"cpmStatFS: block %d allocated\n",(i*INTBITS+j));
1013 #endif
1014           ++buf->f_bused;
1015         }
1016         else ++buf->f_bfree;
1017       }
1018       temp >>= 1;
1019     }
1020   }
1021   buf->f_bavail=buf->f_bfree;
1022   buf->f_files=d->maxdir;
1023   buf->f_ffree=0;
1024   for (i=0; i<d->maxdir; ++i)
1025   {
1026     if (d->dir[i].status==(char)0xe5) ++buf->f_ffree;
1027   }
1028   buf->f_namelen=11;
1029 }
1030 /*}}}*/
1031 /* cpmUnlink          -- unlink                                  */ /*{{{*/
1032 int cpmUnlink(const struct cpmInode *dir, const char *fname)
1033 {
1034   int user;
1035   char name[8],extension[3];
1036   int extent;
1037   struct cpmSuperBlock *drive;
1038
1039   if (!S_ISDIR(dir->mode))
1040   {
1041     boo="No such file";
1042     return -1;
1043   }
1044   drive=dir->sb;
1045   if (splitFilename(fname,dir->sb->type,name,extension,&user)==-1) return -1;
1046   if ((extent=findFileExtent(drive,user,name,extension,0,-1))==-1) return -1;
1047   drive->dir[extent].status=(char)0xe5;
1048   do
1049   {
1050     drive->dir[extent].status=(char)0xe5;
1051   } while ((extent=findFileExtent(drive,user,name,extension,extent+1,-1))>=0);
1052   if (writePhysDirectory(drive)==-1) return -1;
1053   alvInit(drive);
1054   return 0;
1055 }
1056 /*}}}*/
1057 /* cpmRename          -- rename                                  */ /*{{{*/
1058 int cpmRename(const struct cpmInode *dir, const char *old, const char *new)
1059 {
1060   struct cpmSuperBlock *drive;
1061   int extent;
1062   int olduser;
1063   char oldname[8], oldext[3];
1064   int newuser;
1065   char newname[8], newext[3];
1066
1067   if (!S_ISDIR(dir->mode))
1068   {
1069     boo="No such file";
1070     return -1;
1071   }
1072   drive=dir->sb;
1073   if (splitFilename(old,dir->sb->type, oldname, oldext,&olduser)==-1) return -1;
1074   if (splitFilename(new,dir->sb->type, newname, newext,&newuser)==-1) return -1;
1075   if ((extent=findFileExtent(drive,olduser,oldname,oldext,0,-1))==-1) return -1;
1076   if (findFileExtent(drive,newuser,newname, newext,0,-1)!=-1) 
1077   {
1078     boo="file already exists";
1079     return -1;
1080   }
1081   do 
1082   {
1083     drive->dir[extent].status=newuser;
1084     memcpy7(drive->dir[extent].name, newname, 8);
1085     memcpy7(drive->dir[extent].ext, newext, 3);
1086   } while ((extent=findFileExtent(drive,olduser,oldname,oldext,extent+1,-1))!=-1);
1087   if (writePhysDirectory(drive)==-1) return -1;
1088   return 0;
1089 }
1090 /*}}}*/
1091 /* cpmOpendir         -- opendir                                 */ /*{{{*/
1092 int cpmOpendir(struct cpmInode *dir, struct cpmFile *dirp)
1093 {
1094   if (!S_ISDIR(dir->mode))
1095   {
1096     boo="No such file";
1097     return -1;
1098   }
1099   dirp->ino=dir;
1100   dirp->pos=0;
1101   dirp->mode=O_RDONLY;
1102   return 0;
1103 }
1104 /*}}}*/
1105 /* cpmReaddir         -- readdir                                 */ /*{{{*/
1106 int cpmReaddir(struct cpmFile *dir, struct cpmDirent *ent)
1107 {
1108   /* variables */ /*{{{*/
1109   struct PhysDirectoryEntry *cur=(struct PhysDirectoryEntry*)0;
1110   char buf[2+8+1+3+1]; /* 00foobarxy.zzy\0 */
1111   int i;
1112   char *bufp;
1113   int hasext;
1114   /*}}}*/
1115
1116   if (!(S_ISDIR(dir->ino->mode))) /* error: not a directory */ /*{{{*/
1117   {
1118     boo="not a directory";
1119     return -1;
1120   }
1121   /*}}}*/
1122   while (1)
1123   {
1124     if (dir->pos==0) /* first entry is . */ /*{{{*/
1125     {
1126       ent->ino=dir->ino->sb->maxdir;
1127       ent->reclen=1;
1128       strcpy(ent->name,".");
1129       ent->off=dir->pos;
1130       ++dir->pos;
1131       return 1;
1132     }
1133     /*}}}*/
1134     else if (dir->pos==1) /* next entry is .. */ /*{{{*/
1135     {
1136       ent->ino=dir->ino->sb->maxdir;
1137       ent->reclen=2;
1138       strcpy(ent->name,"..");
1139       ent->off=dir->pos;
1140       ++dir->pos;
1141       return 1;
1142     }
1143     /*}}}*/
1144     else if (dir->pos==2)
1145     {
1146       if (dir->ino->sb->passwdLength) /* next entry is [passwd] */ /*{{{*/
1147       {
1148         ent->ino=dir->ino->sb->maxdir+1;
1149         ent->reclen=8;
1150         strcpy(ent->name,"[passwd]");
1151         ent->off=dir->pos;
1152         ++dir->pos;
1153         return 1;
1154       }
1155       /*}}}*/
1156     }
1157     else if (dir->pos==3)
1158     {
1159       if (dir->ino->sb->labelLength) /* next entry is [label] */ /*{{{*/
1160       {
1161         ent->ino=dir->ino->sb->maxdir+2;
1162         ent->reclen=7;
1163         strcpy(ent->name,"[label]");
1164         ent->off=dir->pos;
1165         ++dir->pos;
1166         return 1;
1167       }
1168       /*}}}*/
1169     }
1170     else if (dir->pos>=RESERVED_ENTRIES && dir->pos<dir->ino->sb->maxdir+RESERVED_ENTRIES)
1171     {
1172       int first=dir->pos-RESERVED_ENTRIES;
1173
1174       if ((cur=dir->ino->sb->dir+(dir->pos-RESERVED_ENTRIES))->status>=0 && cur->status<=(dir->ino->sb->type==CPMFS_P2DOS ? 31 : 15))
1175       {
1176         /* determine first extent for the current file */ /*{{{*/
1177         for (i=0; i<dir->ino->sb->maxdir; ++i) if (i!=(dir->pos-RESERVED_ENTRIES))
1178         {
1179           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;
1180         }
1181         /*}}}*/
1182         if (first==(dir->pos-RESERVED_ENTRIES))
1183         {
1184           ent->ino=dir->pos-RESERVED_INODES;
1185           /* convert file name to UNIX style */ /*{{{*/
1186           buf[0]='0'+cur->status/10;
1187           buf[1]='0'+cur->status%10;
1188           for (bufp=buf+2,i=0; i<8 && (cur->name[i]&0x7f)!=' '; ++i) *bufp++=tolower(cur->name[i]&0x7f);
1189           for (hasext=0,i=0; i<3 && (cur->ext[i]&0x7f)!=' '; ++i)
1190           {
1191             if (!hasext) { *bufp++='.'; hasext=1; }
1192             *bufp++=tolower(cur->ext[i]&0x7f);
1193           }
1194           *bufp='\0';
1195           /*}}}*/
1196           ent->reclen=strlen(buf);
1197           strcpy(ent->name,buf);
1198           ent->off=dir->pos;
1199           ++dir->pos;
1200           return 1;
1201         }
1202       }
1203     }
1204     else return 0;
1205     ++dir->pos;
1206   }
1207 }
1208 /*}}}*/
1209 /* cpmStat            -- stat                                    */ /*{{{*/
1210 void cpmStat(const struct cpmInode *ino, struct cpmStat *buf)
1211 {
1212   buf->ino=ino->ino;
1213   buf->mode=ino->mode;
1214   buf->size=ino->size;
1215   buf->atime=ino->atime;
1216   buf->mtime=ino->mtime;
1217   buf->ctime=ino->ctime;
1218 }
1219 /*}}}*/
1220 /* cpmOpen            -- open                                    */ /*{{{*/
1221 int cpmOpen(struct cpmInode *ino, struct cpmFile *file, mode_t mode)
1222 {
1223   if (S_ISREG(ino->mode))
1224   {
1225     if ((mode&O_WRONLY) && (ino->mode&0222)==0)
1226     {
1227       boo="permission denied";
1228       return -1;
1229     }
1230     file->pos=0;
1231     file->ino=ino;
1232     file->mode=mode;
1233     return 0;
1234   }
1235   else
1236   {
1237     boo="not a regular file";
1238     return -1;
1239   }
1240 }
1241 /*}}}*/
1242 /* cpmRead            -- read                                    */ /*{{{*/
1243 int cpmRead(struct cpmFile *file, char *buf, int count)
1244 {
1245   int findext=1,findblock=1,extent=-1,block=-1,extentno=-1,got=0,nextblockpos=-1,nextextpos=-1;
1246   int blocksize=file->ino->sb->blksiz;
1247   int extcap;
1248
1249   extcap=(file->ino->sb->size<256 ? 16 : 8)*blocksize;
1250   if (extcap>16384) extcap=16384*file->ino->sb->extents;
1251   if (file->ino->ino==file->ino->sb->maxdir+1) /* [passwd] */ /*{{{*/
1252   {
1253     if ((file->pos+count)>file->ino->size) count=file->ino->size-file->pos;
1254     if (count) memcpy(buf,file->ino->sb->passwd+file->pos,count);
1255     file->pos+=count;
1256 #ifdef CPMFS_DEBUG
1257     fprintf(stderr,"cpmRead passwd: read %d bytes, now at position %ld\n",count,(long)file->pos);
1258 #endif
1259     return count;
1260   }
1261   /*}}}*/
1262   else if (file->ino->ino==file->ino->sb->maxdir+2) /* [label] */ /*{{{*/
1263   {
1264     if ((file->pos+count)>file->ino->size) count=file->ino->size-file->pos;
1265     if (count) memcpy(buf,file->ino->sb->label+file->pos,count);
1266     file->pos+=count;
1267 #ifdef CPMFS_DEBUG
1268     fprintf(stderr,"cpmRead label: read %d bytes, now at position %ld\n",count,(long)file->pos);
1269 #endif
1270     return count;
1271   }
1272   /*}}}*/
1273   else while (count>0 && file->pos<file->ino->size)
1274   {
1275     char buffer[16384];
1276
1277     if (findext)
1278     {
1279       extentno=file->pos/16384;
1280       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);
1281       nextextpos=(file->pos/extcap)*extcap+extcap;
1282       findext=0;
1283       findblock=1;
1284     }
1285     if (findblock)
1286     {
1287       if (extent!=-1)
1288       {
1289         int start,end,ptr;
1290
1291         ptr=(file->pos%extcap)/blocksize;
1292         if (file->ino->sb->size>=256) ptr*=2;
1293         block=(unsigned char)file->ino->sb->dir[extent].pointers[ptr];
1294         if (file->ino->sb->size>=256) block+=((unsigned char)file->ino->sb->dir[extent].pointers[ptr+1])<<8;
1295         if (block==0)
1296         {
1297           memset(buffer,0,blocksize);
1298         }
1299         else
1300         {
1301           start=(file->pos%blocksize)/file->ino->sb->secLength;
1302           end=((file->pos%blocksize+count)>blocksize ? blocksize-1 : (file->pos%blocksize+count-1))/file->ino->sb->secLength;
1303           readBlock(file->ino->sb,block,buffer,start,end);
1304         }
1305       }
1306       nextblockpos=(file->pos/blocksize)*blocksize+blocksize;
1307       findblock=0;
1308     }
1309     if (file->pos<nextblockpos)
1310     {
1311       if (extent==-1) *buf++='\0'; else *buf++=buffer[file->pos%blocksize];
1312       ++file->pos;
1313       ++got;
1314       --count;
1315     }
1316     else if (file->pos==nextextpos) findext=1; else findblock=1;
1317   }
1318 #ifdef CPMFS_DEBUG
1319   fprintf(stderr,"cpmRead: read %d bytes, now at position %ld\n",got,(long)file->pos);
1320 #endif
1321   return got;
1322 }
1323 /*}}}*/
1324 /* cpmWrite           -- write                                   */ /*{{{*/
1325 int cpmWrite(struct cpmFile *file, const char *buf, int count)
1326 {
1327   int findext=1,findblock=-1,extent=-1,extentno=-1,got=0,nextblockpos=-1,nextextpos=-1;
1328   int blocksize=file->ino->sb->blksiz;
1329   int extcap=(file->ino->sb->size<256 ? 16 : 8)*blocksize;
1330   int block=-1,start=-1,end=-1,ptr=-1,last=-1;
1331   char buffer[16384];
1332
1333   while (count>0)
1334   {
1335     if (findext) /*{{{*/
1336     {
1337       extentno=file->pos/16384;
1338       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);
1339       nextextpos=(file->pos/extcap)*extcap+extcap;
1340       if (extent==-1)
1341       {
1342         if ((extent=findFreeExtent(file->ino->sb))==-1) return (got==0 ? -1 : got);
1343         file->ino->sb->dir[extent]=file->ino->sb->dir[file->ino->ino];
1344         memset(file->ino->sb->dir[extent].pointers,0,16);
1345         file->ino->sb->dir[extent].extnol=EXTENTL(extentno);
1346         file->ino->sb->dir[extent].extnoh=EXTENTH(extentno);
1347         file->ino->sb->dir[extent].blkcnt=0;
1348         file->ino->sb->dir[extent].lrc=0;
1349         updateTimeStamps(file->ino,extent);
1350       }
1351       findext=0;
1352       findblock=1;
1353     }
1354     /*}}}*/
1355     if (findblock) /*{{{*/
1356     {
1357       ptr=(file->pos%extcap)/blocksize;
1358       if (file->ino->sb->size>=256) ptr*=2;
1359       block=(unsigned char)file->ino->sb->dir[extent].pointers[ptr];
1360       if (file->ino->sb->size>=256) block+=((unsigned char)file->ino->sb->dir[extent].pointers[ptr+1])<<8;
1361       if (block==0) /* allocate new block, set start/end to cover it */ /*{{{*/
1362       {
1363         if ((block=allocBlock(file->ino->sb))==-1) return (got==0 ? -1 : got);
1364         file->ino->sb->dir[extent].pointers[ptr]=block&0xff;
1365         if (file->ino->sb->size>=256) file->ino->sb->dir[extent].pointers[ptr+1]=(block>>8)&0xff;
1366         start=0;
1367         end=(blocksize-1)/file->ino->sb->secLength;
1368         memset(buffer,0,blocksize);
1369       }
1370       /*}}}*/
1371       else /* read existing block and set start/end to cover modified parts */ /*{{{*/
1372       {
1373         start=(file->pos%blocksize)/file->ino->sb->secLength;
1374         end=((file->pos%blocksize+count)>blocksize ? blocksize-1 : (file->pos%blocksize+count-1))/file->ino->sb->secLength;
1375         if (file->pos%file->ino->sb->secLength) readBlock(file->ino->sb,block,buffer,start,start);
1376         if (end!=start && (file->pos+count-1)<blocksize) readBlock(file->ino->sb,block,buffer+end*file->ino->sb->secLength,end,end);
1377       }
1378       /*}}}*/
1379       nextblockpos=(file->pos/blocksize)*blocksize+blocksize;
1380       findblock=0;
1381     }
1382     /*}}}*/
1383     /* fill block and write it */ /*{{{*/
1384     while (file->pos!=nextblockpos && count)
1385     {
1386       buffer[file->pos%blocksize]=*buf++;
1387       ++file->pos;
1388       if (file->ino->size<file->pos) file->ino->size=file->pos;
1389       ++got;
1390       --count;
1391     }
1392     (void)writeBlock(file->ino->sb,block,buffer,start,end);
1393     if (file->ino->sb->size<256) for (last=15; last>=0; --last)
1394     {
1395       if (file->ino->sb->dir[extent].pointers[last])
1396       {
1397         break;
1398       }
1399     }
1400     else for (last=14; last>0; last-=2)
1401     {
1402       if (file->ino->sb->dir[extent].pointers[last] || file->ino->sb->dir[extent].pointers[last+1])
1403       {
1404         last/=2;
1405         break;
1406       }
1407     }
1408     if (last>0) extentno+=(last*blocksize)/extcap;
1409     file->ino->sb->dir[extent].extnol=EXTENTL(extentno);
1410     file->ino->sb->dir[extent].extnoh=EXTENTH(extentno);
1411     file->ino->sb->dir[extent].blkcnt=((file->pos-1)%16384)/128+1;
1412     file->ino->sb->dir[extent].lrc=file->pos%128;
1413     updateTimeStamps(file->ino,extent);
1414     /*}}}*/
1415     if (file->pos==nextextpos) findext=1;
1416     else if (file->pos==nextblockpos) findblock=1;
1417   }
1418   writePhysDirectory(file->ino->sb);
1419   return got;
1420 }
1421 /*}}}*/
1422 /* cpmClose           -- close                                   */ /*{{{*/
1423 int cpmClose(struct cpmFile *file)
1424 {
1425   if (file->mode&O_WRONLY) return (writePhysDirectory(file->ino->sb));
1426   return 0;
1427 }
1428 /*}}}*/
1429 /* cpmCreat           -- creat                                   */ /*{{{*/
1430 int cpmCreat(struct cpmInode *dir, const char *fname, struct cpmInode *ino, mode_t mode)
1431 {
1432   int user;
1433   char name[8],extension[3];
1434   int extent;
1435   struct cpmSuperBlock *drive;
1436   struct PhysDirectoryEntry *ent;
1437
1438   if (!S_ISDIR(dir->mode))
1439   {
1440     boo="No such file or directory";
1441     return -1;
1442   }
1443   if (splitFilename(fname,dir->sb->type,name,extension,&user)==-1) return -1;
1444 #ifdef CPMFS_DEBUG
1445   fprintf(stderr,"cpmCreat: %s -> %d:%-.8s.%-.3s\n",fname,user,name,extension);
1446 #endif
1447   if (findFileExtent(dir->sb,user,name,extension,0,-1)!=-1) return -1;
1448   drive=dir->sb;
1449   if ((extent=findFreeExtent(dir->sb))==-1) return -1;
1450   ent=dir->sb->dir+extent;
1451   memset(ent,0,32);
1452   ent->status=user;
1453   memcpy(ent->name,name,8);
1454   memcpy(ent->ext,extension,3);
1455   ino->ino=extent;
1456   ino->mode=s_ifreg|mode;
1457   ino->size=0;
1458   time(&ino->atime);
1459   time(&ino->mtime);
1460   time(&ino->ctime);
1461   ino->sb=dir->sb;
1462   updateTimeStamps(ino,extent);
1463   writePhysDirectory(dir->sb);
1464   return 0;
1465 }
1466 /*}}}*/
1467 /* cpmAttrGet         -- get CP/M attributes                     */ /*{{{*/
1468 int cpmAttrGet(struct cpmInode *ino, cpm_attr_t *attrib)
1469 {
1470         *attrib = ino->attr;
1471         return 0;
1472 }
1473 /*}}}*/
1474 /* cpmAttrSet         -- set CP/M attributes                     */ /*{{{*/
1475 int cpmAttrSet(struct cpmInode *ino, cpm_attr_t attrib)
1476 {
1477   struct cpmSuperBlock *drive;
1478   int extent;
1479   int user;
1480   char name[8], extension[3];
1481   
1482   memset(name,      0, sizeof(name));
1483   memset(extension, 0, sizeof(extension));
1484   drive  = ino->sb;
1485   extent = ino->ino;
1486   
1487   /* Strip off existing attribute bits */
1488   memcpy7(name,      drive->dir[extent].name, 8);
1489   memcpy7(extension, drive->dir[extent].ext,  3);
1490   user = drive->dir[extent].status;
1491   
1492   /* And set new ones */
1493   if (attrib & CPM_ATTR_F1)   name[0]      |= 0x80;
1494   if (attrib & CPM_ATTR_F2)   name[1]      |= 0x80;
1495   if (attrib & CPM_ATTR_F3)   name[2]      |= 0x80;
1496   if (attrib & CPM_ATTR_F4)   name[3]      |= 0x80;
1497   if (attrib & CPM_ATTR_RO)   extension[0] |= 0x80;
1498   if (attrib & CPM_ATTR_SYS)  extension[1] |= 0x80;
1499   if (attrib & CPM_ATTR_ARCV) extension[2] |= 0x80;
1500   
1501   do 
1502   {
1503     memcpy(drive->dir[extent].name, name, 8);
1504     memcpy(drive->dir[extent].ext, extension, 3);
1505   } while ((extent=findFileExtent(drive, user,name,extension,extent+1,-1))!=-1);
1506   if (writePhysDirectory(drive)==-1) return -1;
1507
1508   /* Update the stored (inode) copies of the file attributes and mode */
1509   ino->attr=attrib;
1510   if (attrib&CPM_ATTR_RO) ino->mode&=~(S_IWUSR|S_IWGRP|S_IWOTH);
1511   else ino->mode|=(S_IWUSR|S_IWGRP|S_IWOTH);
1512   
1513   return 0;
1514 }
1515 /*}}}*/
1516 /* cpmChmod           -- set CP/M r/o & sys                      */ /*{{{*/
1517 int cpmChmod(struct cpmInode *ino, mode_t mode)
1518 {
1519         /* Convert the chmod() into a chattr() call that affects RO */
1520         int newatt = ino->attr & ~CPM_ATTR_RO;
1521
1522         if (!(mode & (S_IWUSR|S_IWGRP|S_IWOTH))) newatt |= CPM_ATTR_RO;
1523         return cpmAttrSet(ino, newatt);
1524 }
1525 /*}}}*/
1526 /* cpmSync            -- write directory back                    */ /*{{{*/
1527 int cpmSync(struct cpmSuperBlock *sb)
1528 {
1529   return (writePhysDirectory(sb));
1530 }
1531 /*}}}*/
1532 /* cpmUmount          -- free super block                        */ /*{{{*/
1533 void cpmUmount(struct cpmSuperBlock *sb)
1534 {
1535   free(sb->alv);
1536   free(sb->skewtab);
1537   free(sb->dir);
1538   if (sb->passwdLength) free(sb->passwd);
1539 }
1540 /*}}}*/