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