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