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