move to debhelper compat 13, hopefully fixes cross-building
[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 /* #defines */ /*{{{*/
17 #undef CPMFS_DEBUG
18
19 /* Number of _used_ bits per int */
20
21 #define INTBITS ((int)(sizeof(int)*8))
22
23 /* Convert BCD datestamp digits to binary */
24
25 #define BCD2BIN(x) ((((x)>>4)&0xf)*10 + ((x)&0xf))
26
27 #define BIN2BCD(x) (((((x)/10)&0xf)<<4) + (((x)%10)&0xf))
28
29 /* There are four reserved directory entries: ., .., [passwd] and [label].
30 The first two of them refer to the same inode. */
31
32 #define RESERVED_ENTRIES 4
33
34 /* CP/M does not support any kind of inodes, so they are simulated.
35 Inode 0-(maxdir-1) correlate to the lowest extent number (not the first
36 extent of the file in the directory) of a file.  Inode maxdir is the
37 root directory, inode maxdir+1 is the optional passwd file and inode
38 maxdir+2 the optional disk label. */
39
40 #define RESERVED_INODES 3
41
42 #define PASSWD_RECLEN 24
43 /*}}}*/
44
45 char const *boo;
46 static mode_t s_ifdir=1;
47 static mode_t s_ifreg=1;
48
49 /* memcpy7            -- Copy string, leaving 8th bit alone      */ /*{{{*/
50 static void memcpy7(char *dest, char const *src, int count)
51 {
52   while (count--)
53   {
54     *dest = ((*dest) & 0x80) | ((*src) & 0x7F);
55     ++dest;
56     ++src;
57   }
58 }
59 /*}}}*/
60
61 /* file name conversions */ 
62 /* splitFilename      -- split file name into name and extension */ /*{{{*/
63 static int splitFilename(char const *fullname, int type, char *name, char *ext, int *user) 
64 {
65   int i,j;
66
67   assert(fullname!=(char const *)0);
68   assert(name!=(char*)0);
69   assert(ext!=(char*)0);
70   assert(user!=(int*)0);
71   memset(name,' ',8);
72   memset(ext,' ',3);
73   if (!isdigit(fullname[0]) || !isdigit(fullname[1]))
74   {
75     boo="illegal CP/M filename";
76     return -1;
77   }
78   *user=10*(fullname[0]-'0')+(fullname[1]-'0');
79   fullname+=2;
80   if ((fullname[0]=='\0') || *user>=((type&CPMFS_HI_USER) ? 32 : 16))
81   {
82     boo="illegal CP/M filename";
83     return -1;
84   }
85   for (i=0; i<8 && fullname[i] && fullname[i]!='.'; ++i) if (!ISFILECHAR(i,fullname[i]))
86   {
87     boo="illegal CP/M filename";
88     return -1;
89   }
90   else name[i]=toupper(fullname[i]);
91   if (fullname[i]=='.')
92   {
93     ++i;
94     for (j=0; j<3 && fullname[i]; ++i,++j) if (!ISFILECHAR(1,fullname[i]))
95     {
96       boo="illegal CP/M filename";
97       return -1;
98     }
99     else ext[j]=toupper(fullname[i]);
100     if (i==1 && j==0)
101     {
102       boo="illegal CP/M filename";
103       return -1;
104     }
105   }
106   return 0;
107 }
108 /*}}}*/
109 /* isMatching         -- do two file names match?                */ /*{{{*/
110 static int isMatching(int user1, char const *name1, char const *ext1, int user2, char const *name2, char const *ext2)
111 {
112   int i;
113
114   assert(name1!=(char const *)0);
115   assert(ext1!=(char const *)0);
116   assert(name2!=(char const *)0);
117   assert(ext2!=(char const *)0);
118   if (user1!=user2) return 0;
119   for (i=0; i<8; ++i) if ((name1[i]&0x7f)!=(name2[i]&0x7f)) return 0;
120   for (i=0; i<3; ++i) if ((ext1[i]&0x7f)!=(ext2[i]&0x7f)) return 0;
121   return 1;
122 }
123 /*}}}*/
124
125 /* time conversions */
126 /* cpm2unix_time      -- convert CP/M time to UTC                */ /*{{{*/
127 static time_t cpm2unix_time(int days, int hour, int min)
128 {
129   /* CP/M stores timestamps in local time.  We don't know which     */
130   /* timezone was used and if DST was in effect.  Assuming it was   */
131   /* the current offset from UTC is most sensible, but not perfect. */
132
133   int year,days_per_year;
134   static int days_per_month[]={31,0,31,30,31,30,31,31,30,31,30,31};
135   char **old_environ;
136   static char gmt0[]="TZ=GMT0";
137   static char *gmt_env[]={ gmt0, (char*)0 };
138   struct tm tms;
139   time_t lt,t;
140
141   time(&lt);
142   t=lt;
143   tms=*localtime(&lt);
144   old_environ=environ;
145   environ=gmt_env;
146   tms.tm_isdst=0;
147   lt=mktime(&tms);
148   lt-=t;
149   tms.tm_sec=0;
150   tms.tm_min=((min>>4)&0xf)*10+(min&0xf);
151   tms.tm_hour=((hour>>4)&0xf)*10+(hour&0xf);
152   tms.tm_mday=1;
153   tms.tm_mon=0;
154   tms.tm_year=78;
155   tms.tm_isdst=-1;
156   for (;;)
157   {
158     year=tms.tm_year+1900;
159     days_per_year=((year%4)==0 && ((year%100) || (year%400)==0)) ? 366 : 365;
160     if (days>days_per_year)
161     {
162       days-=days_per_year;
163       ++tms.tm_year;
164     }
165     else break;
166   }
167   for (;;)
168   {
169     days_per_month[1]=(days_per_year==366) ? 29 : 28;
170     if (days>days_per_month[tms.tm_mon])
171     {
172       days-=days_per_month[tms.tm_mon];
173       ++tms.tm_mon;
174     }
175     else break;
176   }
177   t=mktime(&tms)+(days-1)*24*3600;
178   environ=old_environ;
179   t-=lt;
180   return t;
181 }
182 /*}}}*/
183 /* unix2cpm_time      -- convert UTC to CP/M time                */ /*{{{*/
184 static void unix2cpm_time(time_t now, int *days, int *hour, int *min) 
185 {
186   struct tm *tms;
187   int i;
188
189   tms=localtime(&now);
190   *min=((tms->tm_min/10)<<4)|(tms->tm_min%10);
191   *hour=((tms->tm_hour/10)<<4)|(tms->tm_hour%10);
192   for (i=1978,*days=0; i<1900+tms->tm_year; ++i)
193   {
194     *days+=365;
195     if (i%4==0 && (i%100!=0 || i%400==0)) ++*days;
196   }
197   *days += tms->tm_yday+1;
198 }
199 /*}}}*/
200 /* ds2unix_time       -- convert DS to Unix time                 */ /*{{{*/
201 static time_t ds2unix_time(const struct dsEntry *entry) 
202 {
203   struct tm tms;
204   int yr;
205
206   if (entry->minute==0 &&
207       entry->hour==0 &&
208       entry->day==0 &&
209       entry->month==0 &&
210       entry->year==0) return 0;
211   
212   tms.tm_isdst = -1;
213   tms.tm_sec = 0;
214   tms.tm_min = BCD2BIN( entry->minute );
215   tms.tm_hour = BCD2BIN( entry->hour );
216   tms.tm_mday = BCD2BIN( entry->day );
217   tms.tm_mon = BCD2BIN( entry->month ) - 1;
218   
219   yr = BCD2BIN(entry->year);
220   if (yr<70) yr+=100;
221   tms.tm_year = yr;
222   
223   return mktime(&tms);
224 }
225 /*}}}*/
226 /* unix2ds_time       -- convert Unix to DS time                 */ /*{{{*/
227 static void unix2ds_time(time_t now, struct dsEntry *entry) 
228 {
229   struct tm *tms;
230   int yr;
231
232   if ( now==0 )
233   {
234     entry->minute=entry->hour=entry->day=entry->month=entry->year = 0;
235   }
236   else
237   {
238     tms=localtime(&now);
239     entry->minute = BIN2BCD( tms->tm_min );
240     entry->hour = BIN2BCD( tms->tm_hour );
241     entry->day = BIN2BCD( tms->tm_mday );
242     entry->month = BIN2BCD( tms->tm_mon + 1 );
243     
244     yr = tms->tm_year;
245     if ( yr>100 ) yr -= 100;
246     entry->year = BIN2BCD( yr );
247   }
248 }
249 /*}}}*/
250
251 /* allocation vector bitmap functions */
252 /* alvInit            -- init allocation vector                  */ /*{{{*/
253 static void alvInit(const struct cpmSuperBlock *d)
254 {
255   int i,j,offset,block;
256
257   assert(d!=(const struct cpmSuperBlock*)0);
258   /* clean bitmap */ /*{{{*/
259   memset(d->alv,0,d->alvSize*sizeof(int));
260   /*}}}*/
261   /* mark directory blocks as used */ /*{{{*/
262   /* A directory may cover more blocks than an int may hold bits,
263    * so a loop is needed.
264    */
265   for (block=0; block<d->dirblks; ++block)
266   {
267     offset = block/INTBITS;
268     d->alv[offset] |= (1<<(block%INTBITS));
269 #ifdef CPMFS_DEBUG
270     fprintf(stderr,"alvInit: allocate directory block %d\n",block);
271 #endif
272   } 
273   /*}}}*/
274   for (i=0; i<d->maxdir; ++i) /* mark file blocks as used */ /*{{{*/
275   {
276     if (d->dir[i].status>=0 && d->dir[i].status<=(d->type&CPMFS_HI_USER ? 31 : 15))
277     {
278 #ifdef CPMFS_DEBUG
279       fprintf(stderr,"alvInit: allocate extent %d\n",i);
280 #endif
281       for (j=0; j<16; ++j)
282       {
283         block=(unsigned char)d->dir[i].pointers[j];
284         if (d->size>256) block+=(((unsigned char)d->dir[i].pointers[++j])<<8);
285         if (block && block<d->size)
286         {
287 #ifdef CPMFS_DEBUG
288           fprintf(stderr,"alvInit: allocate block %d\n",block);
289 #endif
290           offset=block/INTBITS;
291           d->alv[offset]|=(1<<block%INTBITS);
292         }
293       }
294     }
295   }
296   /*}}}*/
297 }
298 /*}}}*/
299 /* allocBlock         -- allocate a new disk block               */ /*{{{*/
300 static int allocBlock(const struct cpmSuperBlock *drive)
301 {
302   int i,j,bits,block;
303
304   assert(drive!=(const struct cpmSuperBlock*)0);
305   for (i=0; i<drive->alvSize; ++i)
306   {
307     for (j=0,bits=drive->alv[i]; j<INTBITS; ++j)
308     {
309       if ((bits&1)==0)
310       {
311         block=i*INTBITS+j;
312         if (block>=drive->size)
313         {
314           boo="device full";
315           return -1;
316         }
317 #ifdef CPMFS_DEBUG
318         fprintf(stderr,"allocBlock: allocate data block %d\n",block);
319 #endif
320         drive->alv[i] |= (1<<j);
321         return block;
322       }
323       bits >>= 1;
324     }
325   }
326   boo="device full";
327   return -1;
328 }
329 /*}}}*/
330
331 /* bootOffset        -- find the logical sector number of the CP/M directory */ /*{{{*/
332 static int bootOffset(const struct cpmSuperBlock *d)
333 {
334         assert(d);
335         if (d->bootsec >= 0) return d->bootsec;
336         return d->boottrk * d->sectrk;
337 }
338 /*}}}*/
339
340 /* logical block I/O */
341 /* readBlock          -- read a (partial) block                  */ /*{{{*/
342 static int readBlock(const struct cpmSuperBlock *d, int blockno, char *buffer, int start, int end)
343 {
344   int sect, track, counter;
345
346   assert(d);
347   assert(blockno>=0);
348   assert(buffer);
349
350 #ifdef CPMFS_DEBUG
351   fprintf(stderr,"readBlock: read block %d %d-%d\n",blockno,start,end);
352 #endif
353   if (blockno>=d->size)
354   {
355     boo="Attempting to access block beyond end of disk";
356     return -1;
357   }
358   if (end<0) end=d->blksiz/d->secLength-1;
359   sect =(blockno*(d->blksiz/d->secLength)+ bootOffset(d))%d->sectrk;
360   track=(blockno*(d->blksiz/d->secLength)+ bootOffset(d))/d->sectrk;
361   for (counter=0; counter<=end; ++counter)
362   {
363     char const *err;
364
365     assert(d->skewtab[sect]>=0);
366     assert(d->skewtab[sect]<d->sectrk);
367     if (counter>=start)
368     {
369 #ifdef CPMFS_DEBUG
370       fprintf(stderr,"readBlock: read sector %d/%d\n",d->skewtab[sect],track);
371 #endif
372       if ((err=Device_readSector(&d->dev,track,d->skewtab[sect],buffer+(d->secLength*counter))))
373       {
374         boo=err;
375         return -1;
376       }
377     }
378     ++sect;
379     if (sect>=d->sectrk) 
380     {
381       sect = 0;
382       ++track;
383     }
384   }
385   return 0;
386 }
387 /*}}}*/
388 /* writeBlock         -- write a (partial) block                 */ /*{{{*/
389 static int writeBlock(const struct cpmSuperBlock *d, int blockno, char const *buffer, int start, int end)
390 {
391   int sect, track, counter;
392
393   assert(blockno>=0);
394   assert(blockno<d->size);
395   assert(buffer!=(char const *)0);
396
397 #ifdef CPMFS_DEBUG
398   fprintf(stderr,"writeBlock: write block %d %d-%d\n",blockno,start,end);
399 #endif
400   if (end < 0) end=d->blksiz/d->secLength-1;
401   sect  = (blockno*(d->blksiz/d->secLength) + bootOffset(d)) % d->sectrk;
402   track = (blockno*(d->blksiz/d->secLength) + bootOffset(d)) / d->sectrk;
403   for (counter = 0; counter<=end; ++counter)
404   {
405     char const *err;
406
407     if (counter>=start && (err=Device_writeSector(&d->dev,track,d->skewtab[sect],buffer+(d->secLength*counter))))
408     {
409       boo=err;
410       return -1;
411     }
412     ++sect;
413     if (sect>=d->sectrk) 
414     {
415       sect=0;
416       ++track;
417     }
418   }
419   return 0;
420 }
421 /*}}}*/
422
423 /* directory management */
424 /* findFileExtent     -- find first/next extent for a file       */ /*{{{*/
425 static int findFileExtent(const struct cpmSuperBlock *sb, int user, char const *name, char const *ext, int start, int extno)
426 {
427   boo="file already exists";
428   for (; start<sb->maxdir; ++start)
429   {
430     if
431     (
432       ((unsigned char)sb->dir[start].status)<=(sb->type&CPMFS_HI_USER ? 31 : 15)
433       && (extno==-1 || (EXTENT(sb->dir[start].extnol,sb->dir[start].extnoh)/sb->extents)==(extno/sb->extents))
434       && isMatching(user,name,ext,sb->dir[start].status,sb->dir[start].name,sb->dir[start].ext)
435     ) return start;
436   }
437   boo="file not found";
438   return -1;
439 }
440 /*}}}*/
441 /* findFreeExtent     -- find first free extent                  */ /*{{{*/
442 static int findFreeExtent(const struct cpmSuperBlock *drive)
443 {
444   int i;
445
446   for (i=0; i<drive->maxdir; ++i) if (drive->dir[i].status==(char)0xe5) return (i);
447   boo="directory full";
448   return -1;
449 }
450 /*}}}*/
451 /* updateTimeStamps   -- convert time stamps to CP/M format      */ /*{{{*/
452 static void updateTimeStamps(const struct cpmInode *ino, int extent)
453 {
454   struct PhysDirectoryEntry *date;
455   int ca_min,ca_hour,ca_days,u_min,u_hour,u_days;
456
457   if (!S_ISREG(ino->mode)) return;
458 #ifdef CPMFS_DEBUG
459   fprintf(stderr,"CPMFS: updating time stamps for inode %d (%d)\n",extent,extent&3);
460 #endif
461   unix2cpm_time(ino->sb->cnotatime ? ino->ctime : ino->atime,&ca_days,&ca_hour,&ca_min);
462   unix2cpm_time(ino->mtime,&u_days,&u_hour,&u_min);
463   if ((ino->sb->type&CPMFS_CPM3_DATES) && (date=ino->sb->dir+(extent|3))->status==0x21)
464   {
465     ino->sb->dirtyDirectory=1;
466     switch (extent&3)
467     {
468       case 0: /* first entry */ /*{{{*/
469       {
470         date->name[0]=ca_days&0xff; date->name[1]=ca_days>>8;
471         date->name[2]=ca_hour;
472         date->name[3]=ca_min;
473         date->name[4]=u_days&0xff; date->name[5]=u_days>>8;
474         date->name[6]=u_hour;
475         date->name[7]=u_min;
476         break;
477       }
478       /*}}}*/
479       case 1: /* second entry */ /*{{{*/
480       {
481         date->ext[2]=ca_days&0xff; date->extnol=ca_days>>8;
482         date->lrc=ca_hour;
483         date->extnoh=ca_min;
484         date->blkcnt=u_days&0xff; date->pointers[0]=u_days>>8;
485         date->pointers[1]=u_hour;
486         date->pointers[2]=u_min;
487         break;
488       }
489       /*}}}*/
490       case 2: /* third entry */ /*{{{*/
491       {
492         date->pointers[5]=ca_days&0xff; date->pointers[6]=ca_days>>8;
493         date->pointers[7]=ca_hour;
494         date->pointers[8]=ca_min;
495         date->pointers[9]=u_days&0xff; date->pointers[10]=u_days>>8;
496         date->pointers[11]=u_hour;
497         date->pointers[12]=u_min;
498         break;
499       }
500       /*}}}*/
501     }
502   }
503 }
504 /*}}}*/
505 /* updateDsStamps     -- set time in datestamper file            */ /*{{{*/
506 static void updateDsStamps(const struct cpmInode *ino, int extent)
507 {
508   struct dsDate *stamp;
509   
510   if (!S_ISREG(ino->mode)) return;
511   if ( !(ino->sb->type&CPMFS_DS_DATES) ) return;
512
513 #ifdef CPMFS_DEBUG
514   fprintf(stderr,"CPMFS: updating ds stamps for inode %d (%d)\n",extent,extent&3);
515 #endif
516   
517   /* Get datestamp struct */
518   stamp = ino->sb->ds+extent;
519   
520   unix2ds_time( ino->mtime, &stamp->modify );
521   unix2ds_time( ino->ctime, &stamp->create );
522   unix2ds_time( ino->atime, &stamp->access );
523
524   ino->sb->dirtyDs = 1;
525 }
526 /*}}}*/
527 /* readTimeStamps     -- read CP/M time stamp                    */ /*{{{*/
528 static int readTimeStamps(struct cpmInode *i, int lowestExt) 
529 {
530   /* variables */ /*{{{*/
531   struct PhysDirectoryEntry *date;
532   int u_days=0,u_hour=0,u_min=0;
533   int ca_days=0,ca_hour=0,ca_min=0;
534   int protectMode=0;
535   /*}}}*/
536   
537   if ( (i->sb->type&CPMFS_CPM3_DATES) && (date=i->sb->dir+(lowestExt|3))->status==0x21 )
538   {
539     switch (lowestExt&3)
540     {
541       case 0: /* first entry of the four */ /*{{{*/
542       {
543         ca_days=((unsigned char)date->name[0])+(((unsigned char)date->name[1])<<8);
544         ca_hour=(unsigned char)date->name[2];
545         ca_min=(unsigned char)date->name[3];
546         u_days=((unsigned char)date->name[4])+(((unsigned char)date->name[5])<<8);
547         u_hour=(unsigned char)date->name[6];
548         u_min=(unsigned char)date->name[7];
549         protectMode=(unsigned char)date->ext[0];
550         break;
551       }
552       /*}}}*/
553       case 1: /* second entry */ /*{{{*/
554       {
555         ca_days=((unsigned char)date->ext[2])+(((unsigned char)date->extnol)<<8);
556         ca_hour=(unsigned char)date->lrc;
557         ca_min=(unsigned char)date->extnoh;
558         u_days=((unsigned char)date->blkcnt)+(((unsigned char)date->pointers[0])<<8);
559         u_hour=(unsigned char)date->pointers[1];
560         u_min=(unsigned char)date->pointers[2];
561         protectMode=(unsigned char)date->pointers[3];
562         break;
563       }
564       /*}}}*/
565       case 2: /* third one */ /*{{{*/
566       {
567         ca_days=((unsigned char)date->pointers[5])+(((unsigned char)date->pointers[6])<<8);
568         ca_hour=(unsigned char)date->pointers[7];
569         ca_min=(unsigned char)date->pointers[8];
570         u_days=((unsigned char)date->pointers[9])+(((unsigned char)date->pointers[10])<<8);
571         u_hour=(unsigned char)date->pointers[11];
572         u_min=(unsigned char)date->pointers[12];
573         protectMode=(unsigned char)date->pointers[13];
574         break;
575       }
576       /*}}}*/
577     }
578     if (i->sb->cnotatime)
579     {
580       i->ctime=cpm2unix_time(ca_days,ca_hour,ca_min);
581       i->atime=0;
582     }
583     else
584     {
585       i->ctime=0;
586       i->atime=cpm2unix_time(ca_days,ca_hour,ca_min);
587     }
588     i->mtime=cpm2unix_time(u_days,u_hour,u_min);
589   }
590   else
591   {
592     i->atime=i->mtime=i->ctime=0;
593     protectMode=0;
594   }
595   
596   return protectMode;
597 }
598 /*}}}*/
599 /* readDsStamps       -- read datestamper time stamp             */ /*{{{*/
600 static void readDsStamps(struct cpmInode *i, int lowestExt) 
601 {
602   struct dsDate *stamp;
603   
604   if ( !(i->sb->type&CPMFS_DS_DATES) ) return;
605
606   /* Get datestamp */
607   stamp = i->sb->ds+lowestExt;
608   
609   i->mtime = ds2unix_time(&stamp->modify);
610   i->ctime = ds2unix_time(&stamp->create);
611   i->atime = ds2unix_time(&stamp->access);
612 }
613 /*}}}*/
614
615 /* match              -- match filename against a pattern        */ /*{{{*/
616 static int recmatch(char const *a, char const *pattern)
617 {
618   int first=1;
619
620   assert(a);
621   assert(pattern);
622   while (*pattern)
623   {
624     switch (*pattern)
625     {
626       case '*':
627       {
628         if (*a=='.' && first) return 1;
629         ++pattern;
630         while (*a) if (recmatch(a,pattern)) return 1; else ++a;
631         break;
632       }
633       case '?':
634       {
635         if (*a) { ++a; ++pattern; } else return 0;
636         break;
637       }
638       default: if (tolower(*a)==tolower(*pattern)) { ++a; ++pattern; } else return 0;
639     }
640     first=0;
641   }
642   return (*pattern=='\0' && *a=='\0');
643 }
644
645 int match(char const *a, char const *pattern) 
646 {
647   int user;
648   char pat[257];
649
650   assert(a);
651   assert(pattern);
652   assert(strlen(pattern)<255);
653   if (isdigit(*pattern) && *(pattern+1)==':') { user=(*pattern-'0'); pattern+=2; }
654   else if (isdigit(*pattern) && isdigit(*(pattern+1)) && *(pattern+2)==':') { user=(10*(*pattern-'0')+(*(pattern+1)-'0')); pattern+=3; }
655   else user=-1;
656   if (user==-1) sprintf(pat,"??%s",pattern);
657   else sprintf(pat,"%02d%s",user,pattern);
658   return recmatch(a,pat);
659 }
660
661 /*}}}*/
662 /* cpmglob            -- expand CP/M style wildcards             */ /*{{{*/
663 void cpmglob(int optin, int argc, char * const argv[], struct cpmInode *root, int *gargc, char ***gargv)
664 {
665   struct cpmFile dir;
666   int entries,dirsize=0;
667   struct cpmDirent *dirent=(struct cpmDirent*)0;
668   int gargcap=0,i,j;
669
670   *gargv=(char**)0;
671   *gargc=0;
672   cpmOpendir(root,&dir);
673   entries=0;
674   dirsize=8;
675   dirent=malloc(sizeof(struct cpmDirent)*dirsize);
676   while (cpmReaddir(&dir,&dirent[entries]))
677   {
678     ++entries;
679     if (entries==dirsize) dirent=realloc(dirent,sizeof(struct cpmDirent)*(dirsize*=2));
680   }
681   for (i=optin; i<argc; ++i)
682   {
683     int found;
684
685     for (j=0,found=0; j<entries; ++j)
686     {
687       if (match(dirent[j].name,argv[i]))
688       {
689         if (*gargc==gargcap) *gargv=realloc(*gargv,sizeof(char*)*(gargcap ? (gargcap*=2) : (gargcap=16)));
690         (*gargv)[*gargc]=strcpy(malloc(strlen(dirent[j].name)+1),dirent[j].name);
691         ++*gargc;
692         ++found;
693       }
694     }
695   }
696   free(dirent);
697 }
698 /*}}}*/
699 /* cpmglobfree        -- free expanded wildcards                 */ /*{{{*/
700 void cpmglobfree(char **dirent, int entries)
701 {
702   int d;
703
704   assert(dirent);
705   assert(entries>=0);
706   for (d=0; d<entries; ++d) free(dirent[d]);
707   free(dirent);
708 }
709 /*}}}*/
710
711 /* superblock management */
712 /* diskdefReadSuper   -- read super block from diskdefs file     */ /*{{{*/
713 static int diskdefReadSuper(struct cpmSuperBlock *d, char const *format)
714 {
715   char line[256];
716   int ln;
717   FILE *fp;
718   int insideDef=0,found=0;
719
720   d->libdskGeometry[0] = '\0';
721   d->type=0;
722   if ((fp=fopen("diskdefs","r"))==(FILE*)0 && (fp=fopen(DISKDEFS,"r"))==(FILE*)0)
723   {
724     fprintf(stderr,"%s: Neither `diskdefs' nor `" DISKDEFS "' could be opened.\n",cmd);
725     exit(1);
726   }
727   ln=1;
728   while (fgets(line,sizeof(line),fp)!=(char*)0)
729   {
730     int argc;
731     char *argv[2];
732     char *s;
733
734     /* Allow inline comments preceded by ; or # */
735     s = strchr(line, '#');
736     if (s) strcpy(s, "\n");
737     s = strchr(line, ';');
738     if (s) strcpy(s, "\n");
739
740     for (argc=0; argc<1 && (argv[argc]=strtok(argc ? (char*)0 : line," \t\n")); ++argc);
741     if ((argv[argc]=strtok((char*)0,"\n"))!=(char*)0) ++argc;
742     if (insideDef)
743     {
744       if (argc==1 && strcmp(argv[0],"end")==0)
745       {
746         insideDef=0;
747         d->size=(d->sectrk*d->tracks-bootOffset(d)) * d->secLength / d->blksiz;
748         if (d->extents==0) d->extents=((d->size>256 ? 8 : 16)*d->blksiz)/16384;
749         if (d->extents==0) d->extents=1;
750         if (found) break;
751       }
752       else if (argc==2)
753       {
754         if (strcmp(argv[0],"seclen")==0) d->secLength=strtol(argv[1],(char**)0,0);
755         else if (strcmp(argv[0],"tracks")==0) d->tracks=strtol(argv[1],(char**)0,0);
756         else if (strcmp(argv[0],"sectrk")==0) d->sectrk=strtol(argv[1],(char**)0,0);
757         else if (strcmp(argv[0],"blocksize")==0)
758         {
759           d->blksiz=strtol(argv[1],(char**)0,0);
760           if (d->blksiz <= 0)
761           {
762             fprintf(stderr,"%s: invalid blocksize `%s' in line %d\n",cmd,argv[1],ln);
763             exit(1);
764           }
765         }
766         else if (strcmp(argv[0],"maxdir")==0) d->maxdir=strtol(argv[1],(char**)0,0);
767         else if (strcmp(argv[0],"dirblks")==0) d->dirblks=strtol(argv[1],(char**)0,0);
768         else if (strcmp(argv[0],"skew")==0) d->skew=strtol(argv[1],(char**)0,0);
769         else if (strcmp(argv[0],"skewtab")==0)
770         {
771           int pass,sectors;
772
773           for (pass=0; pass<2; ++pass)
774           {
775             sectors=0;
776             for (s=argv[1]; *s; )
777             {
778               int phys;
779               char *end;
780
781               phys=strtol(s,&end,10);
782               if (pass==1) d->skewtab[sectors]=phys;
783               if (end==s)
784               {
785                 fprintf(stderr,"%s: invalid skewtab `%s' at `%s' in line %d\n",cmd,argv[1],s,ln);
786                 exit(1);
787               }
788               s=end;
789               ++sectors;
790               if (*s==',') ++s;
791             }
792             if (pass==0) d->skewtab=malloc(sizeof(int)*sectors);
793           }
794         }
795         else if (strcmp(argv[0],"boottrk")==0) d->boottrk=strtol(argv[1],(char**)0,0);
796         else if (strcmp(argv[0],"bootsec")==0) d->bootsec=strtol(argv[1],(char**)0,0);
797         else if (strcmp(argv[0],"offset")==0)  
798         {
799           off_t val;
800           unsigned int multiplier;
801           char *endptr;
802
803           errno=0;
804           multiplier=1;
805           val = strtol(argv[1],&endptr,10);
806           if ((errno==ERANGE && val==LONG_MAX)||(errno!=0 && val<=0))
807           {
808             fprintf(stderr,"%s: invalid offset value `%s' (%s) in line %d\n",cmd,argv[1],strerror(errno),ln);
809             exit(1);
810           }
811           if (endptr==argv[1])
812           {
813             fprintf(stderr,"%s: offset value `%s' is not a number in line %d\n",cmd,argv[1],ln);
814             exit(1);
815           }
816           if (*endptr!='\0')
817           {
818             /* Have a unit specifier */
819             switch (toupper(*endptr))
820             {
821               case 'K':
822                 multiplier=1024;
823                 break;
824               case 'M':
825                 multiplier=1024*1024;
826                 break;
827               case 'T':
828                 if (d->sectrk<0||d->tracks<0||d->secLength<0)
829                 {
830                   fprintf(stderr,"%s: offset must be specified after sectrk, tracks and secLength in line %d\n",cmd,ln);
831                   exit(1);
832                 }
833                 multiplier=d->sectrk*d->secLength;
834                 break;
835               case 'S':
836                 if (d->sectrk<0||d->tracks<0||d->secLength<0)
837                 {
838                   fprintf(stderr,"%s: offset must be specified after sectrk, tracks and secLength in line %d\n",cmd,ln);
839                   exit(1);
840                 }
841                 multiplier=d->secLength;
842                 break;
843               default:
844                 fprintf(stderr,"%s: unknown unit specifier `%c' in line %d\n",cmd,*endptr,ln);
845                 exit(1);
846             }
847           }
848           if (val*multiplier>INT_MAX)
849           {
850             fprintf(stderr,"%s: effective offset is out of range in line %d\n",cmd,ln);
851             exit(1);
852           }
853           d->offset=val*multiplier;
854         }
855         else if (strcmp(argv[0],"logicalextents")==0) d->extents=strtol(argv[1],(char**)0,0);
856         else if (strcmp(argv[0],"os")==0)
857         {
858           if      (strcmp(argv[1],"2.2"  )==0) d->type|=CPMFS_DR22;
859           else if (strcmp(argv[1],"3"    )==0) d->type|=CPMFS_DR3;
860           else if (strcmp(argv[1],"isx"  )==0) d->type|=CPMFS_ISX;
861           else if (strcmp(argv[1],"p2dos")==0) d->type|=CPMFS_P2DOS;
862           else if (strcmp(argv[1],"zsys" )==0) d->type|=CPMFS_ZSYS;
863           else 
864           {
865             fprintf(stderr, "%s: invalid OS type `%s' in line %d\n",cmd,argv[1],ln);
866             exit(1);
867           }
868         }
869         else if (strcmp(argv[0], "libdsk:format")==0)
870         {
871           strncpy(d->libdskGeometry, argv[1], sizeof(d->libdskGeometry) - 1);
872           d->libdskGeometry[sizeof(d->libdskGeometry) - 1] = 0;
873         }
874       }
875       else if (argc>0 && argv[0][0]!='#' && argv[0][0]!=';')
876       {
877         fprintf(stderr,"%s: invalid keyword `%s' in line %d\n",cmd,argv[0],ln);
878         exit(1);
879       }
880     }
881     else if (argc==2 && strcmp(argv[0],"diskdef")==0)
882     {
883       insideDef=1;
884       d->skew=1;
885       d->extents=0;
886       d->type=CPMFS_DR22;
887       d->skewtab=(int*)0;
888       d->offset=0;
889       d->blksiz=d->boottrk=d->bootsec=d->secLength=d->sectrk=d->tracks=d->maxdir=-1;
890       d->dirblks=0;
891       d->libdskGeometry[0] = 0;
892       if (strcmp(argv[1],format)==0) found=1;
893     }
894     ++ln;
895   }
896   fclose(fp);
897   if (!found)
898   {
899     fprintf(stderr,"%s: unknown format %s\n",cmd,format);
900     exit(1);
901   }
902   if (d->boottrk<0 && d->bootsec<0)
903   {
904     fprintf(stderr, "%s: boottrk / bootsec parameter invalid or missing from diskdef\n",cmd);
905     exit(1);
906   }
907   if (d->secLength<0)
908   {
909     fprintf(stderr, "%s: secLength parameter invalid or missing from diskdef\n",cmd);
910     exit(1);
911   }
912   if (d->sectrk<0)
913   {
914     fprintf(stderr, "%s: sectrk parameter invalid or missing from diskdef\n",cmd);
915     exit(1);
916   }
917   if (d->tracks<0)
918   {
919     fprintf(stderr, "%s: tracks parameter invalid or missing from diskdef\n",cmd);
920     exit(1);
921   }
922   if (d->blksiz<0)
923   {
924     fprintf(stderr, "%s: blocksize parameter invalid or missing from diskdef\n",cmd);
925     exit(1);
926   }
927   if (d->maxdir<0)
928   {
929     fprintf(stderr, "%s: maxdir parameter invalid or missing from diskdef\n",cmd);
930     exit(1);
931   }
932   return 0;
933 }
934 /*}}}*/
935 /* amsReadSuper       -- read super block from amstrad disk      */ /*{{{*/
936 static int amsReadSuper(struct cpmSuperBlock *d, char const *format)
937 {
938   unsigned char boot_sector[512], *boot_spec;
939   char const *err;
940
941   Device_setGeometry(&d->dev,512,9,40,0,"pcw180");
942   if ((err=Device_readSector(&d->dev, 0, 0, (char *)boot_sector)))
943   {
944     fprintf(stderr,"%s: Failed to read Amstrad superblock (%s)\n",cmd,err);
945     exit(1);
946   }
947   boot_spec=(boot_sector[0] == 0 || boot_sector[0] == 3)?boot_sector:(unsigned char*)0;
948   /* Check for JCE's extension to allow Amstrad and MSDOS superblocks
949    * in the same sector (for the PCW16)
950    */
951   if
952   (
953     (boot_sector[0] == 0xE9 || boot_sector[0] == 0xEB)
954     && !memcmp(boot_sector + 0x2B, "CP/M", 4)
955     && !memcmp(boot_sector + 0x33, "DSK",  3)
956     && !memcmp(boot_sector + 0x7C, "CP/M", 4)
957   ) boot_spec = boot_sector + 128;
958   if (boot_spec==(unsigned char*)0)
959   {
960     fprintf(stderr,"%s: Amstrad superblock not present\n",cmd);
961     exit(1);
962   }
963   /* boot_spec[0] = format number: 0 for SS SD, 3 for DS DD
964               [1] = single/double sided and density flags
965               [2] = cylinders per side
966               [3] = sectors per cylinder
967               [4] = Physical sector shift, 2 => 512
968               [5] = Reserved track count
969               [6] = Block shift
970               [7] = No. of directory blocks
971    */
972   d->type = 0;
973   d->type |= CPMFS_DR3; /* Amstrads are CP/M 3 systems */
974   d->secLength = 128 << boot_spec[4];
975   d->tracks    = boot_spec[2];
976   if (boot_spec[1] & 3) d->tracks *= 2;
977   d->sectrk    = boot_spec[3];
978   d->blksiz    = 128 << boot_spec[6];
979   d->maxdir    = (d->blksiz / 32) * boot_spec[7];
980   d->dirblks   = 0;
981   d->skew      = 1; /* Amstrads skew at the controller level */
982   d->skewtab   = (int*)0;
983   d->boottrk   = boot_spec[5];
984   d->offset    = 0;
985   d->size      = (d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz;
986   d->extents   = ((d->size>256 ? 8 : 16)*d->blksiz)/16384;
987   d->libdskGeometry[0] = 0; /* LibDsk can recognise an Amstrad superblock 
988                              * and autodect */
989  
990   return 0;
991 }
992 /*}}}*/
993 /* cpmCheckDs         -- read all datestamper timestamps         */ /*{{{*/
994 int cpmCheckDs(struct cpmSuperBlock *sb)
995 {
996   int dsoffset, dsblks, dsrecs, off, i;
997   unsigned char *buf;
998
999   if (!isMatching(0,"!!!TIME&","DAT",sb->dir->status,sb->dir->name,sb->dir->ext)) return -1;
1000
1001   /* Offset to ds file in alloc blocks */
1002   dsoffset=(sb->maxdir*32+(sb->blksiz-1))/sb->blksiz;
1003
1004   dsrecs=(sb->maxdir+7)/8;
1005   dsblks=(dsrecs*128+(sb->blksiz-1))/sb->blksiz;
1006
1007   /* Allocate buffer */
1008   sb->ds=malloc(dsblks*sb->blksiz);
1009
1010   /* Read ds file in its entirety */
1011   off=0;
1012   for (i=dsoffset; i<dsoffset+dsblks; i++)
1013   {
1014     if (readBlock(sb,i,((char*)sb->ds)+off,0,-1)==-1) return -1;
1015     off+=sb->blksiz;
1016   }
1017
1018   /* Verify checksums */
1019   buf = (unsigned char *)sb->ds;
1020   for (i=0; i<dsrecs; i++)
1021   {
1022     unsigned cksum, j;
1023     cksum=0;
1024     for (j=0; j<127; j++) cksum += buf[j];
1025     if (buf[j]!=(cksum&0xff))
1026     {
1027 #ifdef CPMFS_DEBUG
1028       fprintf( stderr, "!!!TIME&.DAT file failed cksum at record %i\n", i );
1029 #endif
1030       free(sb->ds);
1031       sb->ds = (struct dsDate *)0;
1032       return -1;
1033     }
1034     buf += 128;
1035   }
1036   return 0;
1037 }
1038 /*}}}*/
1039 /* cpmReadSuper       -- get DPB and init in-core data for drive */ /*{{{*/
1040 int cpmReadSuper(struct cpmSuperBlock *d, struct cpmInode *root, char const *format, int uppercase)
1041 {
1042   while (s_ifdir && !S_ISDIR(s_ifdir)) s_ifdir<<=1;
1043   assert(s_ifdir);
1044   while (s_ifreg && !S_ISREG(s_ifreg)) s_ifreg<<=1;
1045   assert(s_ifreg);
1046
1047   if (strcmp(format,"amstrad")==0) amsReadSuper(d,format);
1048   else diskdefReadSuper(d,format);
1049
1050   d->uppercase=uppercase;
1051
1052   assert(d->boottrk>=0);
1053   assert(d->secLength>0);
1054   assert(d->sectrk>0);
1055   assert(d->tracks>0);
1056   assert(d->blksiz>0);
1057   assert(d->maxdir>0);
1058   assert(d->dirblks>=0);
1059
1060   if (d->dirblks==0)
1061   {
1062     /* optional field, compute based on directory size */
1063     d->dirblks=(d->maxdir*32+(d->blksiz-1))/d->blksiz;
1064   }
1065
1066   boo = Device_setGeometry(&d->dev,d->secLength,d->sectrk,d->tracks,d->offset,d->libdskGeometry);
1067   if (boo) return -1;
1068
1069   if (d->skewtab==(int*)0) /* generate skew table */ /*{{{*/
1070   {
1071     int i,j,k;
1072
1073     if (( d->skewtab = malloc(d->sectrk*sizeof(int))) == (int*)0) 
1074     {
1075       boo=strerror(errno);
1076       return -1;
1077     }
1078     memset(d->skewtab,0,d->sectrk*sizeof(int));
1079     for (i=j=0; i<d->sectrk; ++i,j=(j+d->skew)%d->sectrk)
1080     {
1081       while (1)
1082       {
1083         assert(i<d->sectrk);
1084         assert(j<d->sectrk);
1085         for (k=0; k<i && d->skewtab[k]!=j; ++k);
1086         if (k<i) j=(j+1)%d->sectrk;
1087         else break;
1088       }
1089       d->skewtab[i]=j;
1090     }
1091   }
1092   /*}}}*/
1093   /* initialise allocation vector bitmap */ /*{{{*/
1094   {
1095     d->alvSize= ((((d->sectrk * d->tracks)-bootOffset(d)) * d->secLength)/d->blksiz+INTBITS-1)/INTBITS;
1096     if ((d->alv=malloc(d->alvSize*sizeof(int)))==(int*)0) 
1097     {
1098       boo=strerror(errno);
1099       return -1;
1100     }
1101   }
1102   /*}}}*/
1103   /* allocate directory buffer */ /*{{{*/
1104   assert(sizeof(struct PhysDirectoryEntry)==32);
1105   if ((d->dir=malloc(((d->maxdir*32+d->blksiz-1)/d->blksiz)*d->blksiz))==(struct PhysDirectoryEntry*)0)
1106   {
1107     boo=strerror(errno);
1108     return -1;
1109   }
1110   /*}}}*/
1111   if (d->dev.opened==0) /* create empty directory in core */ /*{{{*/
1112   {
1113     memset(d->dir,0xe5,d->maxdir*32);
1114   }
1115   /*}}}*/
1116   else /* read directory in core */ /*{{{*/
1117   {
1118     int i,blocks,entry;
1119
1120     blocks=(d->maxdir*32+d->blksiz-1)/d->blksiz;
1121     entry=0;
1122     for (i=0; i<blocks; ++i) 
1123     {
1124       if (readBlock(d,i,(char*)(d->dir+entry),0,-1)==-1) return -1;
1125       entry+=(d->blksiz/32);
1126     }
1127   }
1128   /*}}}*/
1129   alvInit(d);
1130   if (d->type&CPMFS_CPM3_OTHER) /* read additional superblock information */ /*{{{*/
1131   {
1132     int i;
1133
1134     /* passwords */ /*{{{*/
1135     {
1136       int passwords=0;
1137
1138       for (i=0; i<d->maxdir; ++i) if (d->dir[i].status>=16 && d->dir[i].status<=31) ++passwords;
1139 #ifdef CPMFS_DEBUG
1140       fprintf(stderr,"getformat: found %d passwords\n",passwords);
1141 #endif
1142       if ((d->passwdLength=passwords*PASSWD_RECLEN))
1143       {
1144         if ((d->passwd=malloc(d->passwdLength))==(char*)0)
1145         {
1146           boo="out of memory";
1147           return -1;
1148         }
1149         for (i=0,passwords=0; i<d->maxdir; ++i) if (d->dir[i].status>=16 && d->dir[i].status<=31)
1150         {
1151           int j,pb;
1152           char *p=d->passwd+(passwords++*PASSWD_RECLEN);
1153
1154           p[0]='0'+(d->dir[i].status-16)/10;
1155           p[1]='0'+(d->dir[i].status-16)%10;
1156           for (j=0; j<8; ++j) p[2+j]=d->dir[i].name[j]&0x7f;
1157           p[10]=(d->dir[i].ext[0]&0x7f)==' ' ? ' ' : '.';
1158           for (j=0; j<3; ++j) p[11+j]=d->dir[i].ext[j]&0x7f;
1159           p[14]=' ';
1160           pb=(unsigned char)d->dir[i].lrc;
1161           for (j=0; j<8; ++j) p[15+j]=((unsigned char)d->dir[i].pointers[7-j])^pb;
1162 #ifdef CPMFS_DEBUG
1163           p[23]='\0';
1164           fprintf(stderr,"getformat: %s\n",p);
1165 #endif        
1166           p[23]='\n';
1167         }
1168       }
1169     }
1170     /*}}}*/
1171     /* disc label */ /*{{{*/
1172     for (i=0; i<d->maxdir; ++i) if (d->dir[i].status==(char)0x20)
1173     {
1174       int j;
1175
1176       d->cnotatime=d->dir[i].extnol&0x10;
1177       if (d->dir[i].extnol&0x1)
1178       {
1179         d->labelLength=12;
1180         if ((d->label=malloc(d->labelLength))==(char*)0)
1181         {
1182           boo="out of memory";
1183           return -1;
1184         }
1185         for (j=0; j<8; ++j) d->label[j]=d->dir[i].name[j]&0x7f;
1186         for (j=0; j<3; ++j) d->label[8+j]=d->dir[i].ext[j]&0x7f;
1187         d->label[11]='\n';
1188       }
1189       else
1190       {
1191         d->labelLength=0;
1192       }
1193       break;
1194     }
1195     if (i==d->maxdir)
1196     {
1197       d->cnotatime=1;
1198       d->labelLength=0;
1199     }
1200     /*}}}*/
1201   }
1202   /*}}}*/
1203   else
1204   {
1205     d->passwdLength=0;
1206     d->cnotatime=1;
1207     d->labelLength=0;
1208   }
1209   d->root=root;
1210   d->dirtyDirectory = 0;
1211   root->ino=d->maxdir;
1212   root->sb=d;
1213   root->mode=(s_ifdir|0777);
1214   root->size=0;
1215   root->atime=root->mtime=root->ctime=0;
1216
1217   d->dirtyDs=0;
1218   if (cpmCheckDs(d)==0) d->type|=CPMFS_DS_DATES;
1219   else d->ds=(struct dsDate*)0;
1220
1221   return 0;
1222 }
1223 /*}}}*/
1224 /* syncDs             -- write all datestamper timestamps        */ /*{{{*/
1225 static int syncDs(const struct cpmSuperBlock *sb)
1226 {
1227   if (sb->dirtyDs)
1228   {
1229     int dsoffset, dsblks, dsrecs, off, i;
1230     unsigned char *buf;
1231     
1232     dsrecs=(sb->maxdir+7)/8;
1233
1234     /* Re-calculate checksums */
1235     buf = (unsigned char *)sb->ds;
1236     for ( i=0; i<dsrecs; i++ )
1237     {
1238       unsigned cksum, j;
1239       cksum=0;
1240       for ( j=0; j<127; j++ ) cksum += buf[j];
1241       buf[j] = cksum & 0xff;
1242       buf += 128;
1243     }
1244     dsoffset=(sb->maxdir*32+(sb->blksiz-1))/sb->blksiz;
1245     dsblks=(dsrecs*128+(sb->blksiz-1))/sb->blksiz;
1246
1247     off=0;
1248     for (i=dsoffset; i<dsoffset+dsblks; i++)
1249     {
1250       if (writeBlock(sb,i,((char*)(sb->ds))+off,0,-1)==-1) return -1;
1251       off+=sb->blksiz;
1252     }
1253   }
1254   return 0;
1255 }
1256 /*}}}*/
1257 /* cpmSync            -- write directory back                    */ /*{{{*/
1258 int cpmSync(struct cpmSuperBlock *sb)
1259 {
1260   if (sb->dirtyDirectory)
1261   {
1262     int i,blocks,entry;
1263
1264     blocks=(sb->maxdir*32+sb->blksiz-1)/sb->blksiz;
1265     entry=0;
1266     for (i=0; i<blocks; ++i) 
1267     {
1268       if (writeBlock(sb,i,(char*)(sb->dir+entry),0,-1)==-1) return -1;
1269       entry+=(sb->blksiz/32);
1270     }
1271     sb->dirtyDirectory=0;
1272   }
1273   if (sb->type&CPMFS_DS_DATES) syncDs(sb);
1274   return 0;
1275 }
1276 /*}}}*/
1277 /* cpmUmount          -- free super block                        */ /*{{{*/
1278 int cpmUmount(struct cpmSuperBlock *sb)
1279 {
1280   int err_sync;
1281   char const *err_close;
1282
1283   err_sync=cpmSync(sb);
1284   err_close=Device_close(&sb->dev);
1285   if (sb->type&CPMFS_DS_DATES) free(sb->ds);
1286   free(sb->alv);
1287   free(sb->skewtab);
1288   free(sb->dir);
1289   if (sb->passwdLength) free(sb->passwd);
1290   if (err_sync==-1) return err_sync;
1291   if (err_close)
1292   {
1293     boo=err_close;
1294     return -1;
1295   }
1296   return 0;
1297 }
1298 /*}}}*/
1299
1300 /* cpmNamei           -- map name to inode                       */ /*{{{*/
1301 int cpmNamei(const struct cpmInode *dir, char const *filename, struct cpmInode *i)
1302 {
1303   /* variables */ /*{{{*/
1304   int user;
1305   char name[8],extension[3];
1306   int highestExtno,highestExt=-1,lowestExtno,lowestExt=-1;
1307   int protectMode=0;
1308   /*}}}*/
1309
1310 #ifdef CPMFS_DEBUG
1311   fprintf(stderr,"cpmNamei: map %s\n", filename);
1312 #endif
1313   if (!S_ISDIR(dir->mode))
1314   {
1315     boo="No such file";
1316     return -1;
1317   }
1318   if (strcmp(filename,".")==0 || strcmp(filename,"..")==0) /* root directory */ /*{{{*/
1319   {
1320     *i=*dir;
1321     return 0;
1322   }
1323   /*}}}*/
1324   else if (strcmp(filename,"[passwd]")==0 && dir->sb->passwdLength) /* access passwords */ /*{{{*/
1325   {
1326     i->attr=0;
1327     i->ino=dir->sb->maxdir+1;
1328     i->mode=s_ifreg|0444;
1329     i->sb=dir->sb;
1330     i->atime=i->mtime=i->ctime=0;
1331     i->size=i->sb->passwdLength;
1332     return 0;
1333   }
1334   /*}}}*/
1335   else if (strcmp(filename,"[label]")==0 && dir->sb->labelLength) /* access label */ /*{{{*/
1336   {
1337     i->attr=0;
1338     i->ino=dir->sb->maxdir+2;
1339     i->mode=s_ifreg|0444;
1340     i->sb=dir->sb;
1341     i->atime=i->mtime=i->ctime=0;
1342     i->size=i->sb->labelLength;
1343     return 0;
1344   }
1345   /*}}}*/
1346   if (splitFilename(filename,dir->sb->type,name,extension,&user)==-1) return -1;
1347   /* find highest and lowest extent */ /*{{{*/
1348   {
1349     int extent;
1350
1351     i->size=0;
1352     extent=-1;
1353     highestExtno=-1;
1354     lowestExtno=2049;
1355     while ((extent=findFileExtent(dir->sb,user,name,extension,extent+1,-1))!=-1)
1356     {
1357       int extno=EXTENT(dir->sb->dir[extent].extnol,dir->sb->dir[extent].extnoh);
1358
1359       if (extno>highestExtno)
1360       {
1361         highestExtno=extno;
1362         highestExt=extent;
1363       }
1364       if (extno<lowestExtno)
1365       {
1366         lowestExtno=extno;
1367         lowestExt=extent;
1368       }
1369     }
1370   }
1371   /*}}}*/
1372   if (highestExtno==-1) return -1;
1373   /* calculate size */ /*{{{*/
1374   {
1375     int block;
1376
1377     i->size=highestExtno*16384;
1378     if (dir->sb->size<=256) for (block=15; block>=0; --block)
1379     {
1380       if (dir->sb->dir[highestExt].pointers[block]) break;
1381     }
1382     else for (block=7; block>=0; --block)
1383     {
1384       if (dir->sb->dir[highestExt].pointers[2*block] || dir->sb->dir[highestExt].pointers[2*block+1]) break;
1385     }
1386     if (dir->sb->dir[highestExt].blkcnt)
1387     {
1388       i->size+=((dir->sb->dir[highestExt].blkcnt&0xff)-1)*128;
1389       if (dir->sb->type & CPMFS_ISX)
1390       {
1391         i->size += (128 - dir->sb->dir[highestExt].lrc);
1392       }
1393       else
1394       {
1395         i->size+=dir->sb->dir[highestExt].lrc ? (dir->sb->dir[highestExt].lrc&0xff) : 128;
1396       }
1397     }
1398 #ifdef CPMFS_DEBUG
1399     fprintf(stderr,"cpmNamei: size=%ld\n",(long)i->size);
1400 #endif
1401   }
1402   /*}}}*/
1403   i->ino=lowestExt;
1404   i->mode=s_ifreg;
1405   i->sb=dir->sb;
1406
1407   /* read timestamps */ /*{{{*/
1408   protectMode = readTimeStamps(i,lowestExt);
1409   /*}}}*/
1410
1411   /* Determine the inode attributes */
1412   i->attr = 0;
1413   if (dir->sb->dir[lowestExt].name[0]&0x80) i->attr |= CPM_ATTR_F1;
1414   if (dir->sb->dir[lowestExt].name[1]&0x80) i->attr |= CPM_ATTR_F2;
1415   if (dir->sb->dir[lowestExt].name[2]&0x80) i->attr |= CPM_ATTR_F3;
1416   if (dir->sb->dir[lowestExt].name[3]&0x80) i->attr |= CPM_ATTR_F4;
1417   if (dir->sb->dir[lowestExt].ext [0]&0x80) i->attr |= CPM_ATTR_RO;
1418   if (dir->sb->dir[lowestExt].ext [1]&0x80) i->attr |= CPM_ATTR_SYS;
1419   if (dir->sb->dir[lowestExt].ext [2]&0x80) i->attr |= CPM_ATTR_ARCV;
1420   if (protectMode&0x20)                     i->attr |= CPM_ATTR_PWDEL;
1421   if (protectMode&0x40)                     i->attr |= CPM_ATTR_PWWRITE;
1422   if (protectMode&0x80)                     i->attr |= CPM_ATTR_PWREAD;
1423
1424   if (dir->sb->dir[lowestExt].ext[1]&0x80) i->mode|=01000;
1425   i->mode|=0444;
1426   if (!(dir->sb->dir[lowestExt].ext[0]&0x80)) i->mode|=0222;
1427   if (extension[0]=='C' && extension[1]=='O' && extension[2]=='M') i->mode|=0111;
1428
1429   readDsStamps(i,lowestExt);
1430   
1431   return 0;
1432 }
1433 /*}}}*/
1434 /* cpmStatFS          -- statfs                                  */ /*{{{*/
1435 void cpmStatFS(const struct cpmInode *ino, struct cpmStatFS *buf)
1436 {
1437   int i;
1438   struct cpmSuperBlock *d;
1439
1440   d=ino->sb;
1441   buf->f_bsize=d->blksiz;
1442   buf->f_blocks=d->size;
1443   buf->f_bfree=0;
1444   buf->f_bused=-(d->dirblks);
1445   for (i=0; i<d->alvSize; ++i)
1446   {
1447     int temp,j;
1448
1449     temp = *(d->alv+i);
1450     for (j=0; j<INTBITS; ++j)
1451     {
1452       if (i*INTBITS+j < d->size)
1453       {
1454         if (1&temp)
1455         {
1456 #ifdef CPMFS_DEBUG
1457           fprintf(stderr,"cpmStatFS: block %d allocated\n",(i*INTBITS+j));
1458 #endif
1459           ++buf->f_bused;
1460         }
1461         else ++buf->f_bfree;
1462       }
1463       temp >>= 1;
1464     }
1465   }
1466   buf->f_bavail=buf->f_bfree;
1467   buf->f_files=d->maxdir;
1468   buf->f_ffree=0;
1469   for (i=0; i<d->maxdir; ++i)
1470   {
1471     if (d->dir[i].status==(char)0xe5) ++buf->f_ffree;
1472   }
1473   buf->f_namelen=11;
1474 }
1475 /*}}}*/
1476 /* cpmUnlink          -- unlink                                  */ /*{{{*/
1477 int cpmUnlink(const struct cpmInode *dir, char const *fname)
1478 {
1479   int user;
1480   char name[8],extension[3];
1481   int extent;
1482   struct cpmSuperBlock *drive;
1483
1484   if (!S_ISDIR(dir->mode))
1485   {
1486     boo="No such file";
1487     return -1;
1488   }
1489   drive=dir->sb;
1490   if (splitFilename(fname,dir->sb->type,name,extension,&user)==-1) return -1;
1491   if ((extent=findFileExtent(drive,user,name,extension,0,-1))==-1) return -1;
1492   drive->dirtyDirectory=1;
1493   drive->dir[extent].status=(char)0xe5;
1494   do
1495   {
1496     drive->dir[extent].status=(char)0xe5;
1497   } while ((extent=findFileExtent(drive,user,name,extension,extent+1,-1))>=0);
1498   alvInit(drive);
1499   return 0;
1500 }
1501 /*}}}*/
1502 /* cpmRename          -- rename                                  */ /*{{{*/
1503 int cpmRename(const struct cpmInode *dir, char const *old, char const *new)
1504 {
1505   struct cpmSuperBlock *drive;
1506   int extent;
1507   int olduser;
1508   char oldname[8], oldext[3];
1509   int newuser;
1510   char newname[8], newext[3];
1511
1512   if (!S_ISDIR(dir->mode))
1513   {
1514     boo="No such file";
1515     return -1;
1516   }
1517   drive=dir->sb;
1518   if (splitFilename(old,dir->sb->type, oldname, oldext,&olduser)==-1) return -1;
1519   if (splitFilename(new,dir->sb->type, newname, newext,&newuser)==-1) return -1;
1520   if ((extent=findFileExtent(drive,olduser,oldname,oldext,0,-1))==-1) return -1;
1521   if (findFileExtent(drive,newuser,newname, newext,0,-1)!=-1) 
1522   {
1523     boo="file already exists";
1524     return -1;
1525   }
1526   do 
1527   {
1528     drive->dirtyDirectory=1;
1529     drive->dir[extent].status=newuser;
1530     memcpy7(drive->dir[extent].name, newname, 8);
1531     memcpy7(drive->dir[extent].ext, newext, 3);
1532   } while ((extent=findFileExtent(drive,olduser,oldname,oldext,extent+1,-1))!=-1);
1533   return 0;
1534 }
1535 /*}}}*/
1536 /* cpmOpendir         -- opendir                                 */ /*{{{*/
1537 int cpmOpendir(struct cpmInode *dir, struct cpmFile *dirp)
1538 {
1539   if (!S_ISDIR(dir->mode))
1540   {
1541     boo="No such file";
1542     return -1;
1543   }
1544   dirp->ino=dir;
1545   dirp->pos=0;
1546   dirp->mode=O_RDONLY;
1547   return 0;
1548 }
1549 /*}}}*/
1550 /* cpmReaddir         -- readdir                                 */ /*{{{*/
1551 int cpmReaddir(struct cpmFile *dir, struct cpmDirent *ent)
1552 {
1553   /* variables */ /*{{{*/
1554   struct PhysDirectoryEntry *cur=(struct PhysDirectoryEntry*)0;
1555   char buf[2+8+1+3+1]; /* 00foobarxy.zzy\0 */
1556   char *bufp;
1557   int hasext;
1558   /*}}}*/
1559
1560   if (!(S_ISDIR(dir->ino->mode))) /* error: not a directory */ /*{{{*/
1561   {
1562     boo="not a directory";
1563     return -1;
1564   }
1565   /*}}}*/
1566   while (1)
1567   {
1568     int i;
1569
1570     if (dir->pos==0) /* first entry is . */ /*{{{*/
1571     {
1572       ent->ino=dir->ino->sb->maxdir;
1573       ent->reclen=1;
1574       strcpy(ent->name,".");
1575       ent->off=dir->pos;
1576       ++dir->pos;
1577       return 1;
1578     }
1579     /*}}}*/
1580     else if (dir->pos==1) /* next entry is .. */ /*{{{*/
1581     {
1582       ent->ino=dir->ino->sb->maxdir;
1583       ent->reclen=2;
1584       strcpy(ent->name,"..");
1585       ent->off=dir->pos;
1586       ++dir->pos;
1587       return 1;
1588     }
1589     /*}}}*/
1590     else if (dir->pos==2)
1591     {
1592       if (dir->ino->sb->passwdLength) /* next entry is [passwd] */ /*{{{*/
1593       {
1594         ent->ino=dir->ino->sb->maxdir+1;
1595         ent->reclen=8;
1596         strcpy(ent->name,"[passwd]");
1597         ent->off=dir->pos;
1598         ++dir->pos;
1599         return 1;
1600       }
1601       /*}}}*/
1602     }
1603     else if (dir->pos==3)
1604     {
1605       if (dir->ino->sb->labelLength) /* next entry is [label] */ /*{{{*/
1606       {
1607         ent->ino=dir->ino->sb->maxdir+2;
1608         ent->reclen=7;
1609         strcpy(ent->name,"[label]");
1610         ent->off=dir->pos;
1611         ++dir->pos;
1612         return 1;
1613       }
1614       /*}}}*/
1615     }
1616     else if (dir->pos>=RESERVED_ENTRIES && dir->pos<(int)dir->ino->sb->maxdir+RESERVED_ENTRIES)
1617     {
1618       int first=dir->pos-RESERVED_ENTRIES;
1619
1620       if ((cur=dir->ino->sb->dir+(dir->pos-RESERVED_ENTRIES))->status>=0 && cur->status<=(dir->ino->sb->type&CPMFS_HI_USER ? 31 : 15))
1621       {
1622         /* determine first extent for the current file */ /*{{{*/
1623         for (i=0; i<dir->ino->sb->maxdir; ++i) if (i!=(dir->pos-RESERVED_ENTRIES))
1624         {
1625           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;
1626         }
1627         /*}}}*/
1628         if (first==(dir->pos-RESERVED_ENTRIES))
1629         {
1630           ent->ino=dir->pos-RESERVED_INODES;
1631           /* convert file name to UNIX style */ /*{{{*/
1632           buf[0]='0'+cur->status/10;
1633           buf[1]='0'+cur->status%10;
1634           for (bufp=buf+2,i=0; i<8 && (cur->name[i]&0x7f)!=' '; ++i)
1635           {
1636             *bufp++=dir->ino->sb->uppercase ? cur->name[i]&0x7f : tolower(cur->name[i]&0x7f);
1637           }
1638           for (hasext=0,i=0; i<3 && (cur->ext[i]&0x7f)!=' '; ++i)
1639           {
1640             if (!hasext) { *bufp++='.'; hasext=1; }
1641             *bufp++=dir->ino->sb->uppercase ? cur->ext[i]&0x7f : tolower(cur->ext[i]&0x7f);
1642           }
1643           *bufp='\0';
1644           /*}}}*/
1645           assert(bufp<=buf+sizeof(buf));
1646           ent->reclen=bufp-buf;
1647           strcpy(ent->name,buf);
1648           ent->off=dir->pos;
1649           ++dir->pos;
1650           return 1;
1651         }
1652       }
1653     }
1654     else return 0;
1655     ++dir->pos;
1656   }
1657 }
1658 /*}}}*/
1659 /* cpmStat            -- stat                                    */ /*{{{*/
1660 void cpmStat(const struct cpmInode *ino, struct cpmStat *buf)
1661 {
1662   buf->ino=ino->ino;
1663   buf->mode=ino->mode;
1664   buf->size=ino->size;
1665   buf->atime=ino->atime;
1666   buf->mtime=ino->mtime;
1667   buf->ctime=ino->ctime;
1668 }
1669 /*}}}*/
1670 /* cpmOpen            -- open                                    */ /*{{{*/
1671 int cpmOpen(struct cpmInode *ino, struct cpmFile *file, mode_t mode)
1672 {
1673   if (S_ISREG(ino->mode))
1674   {
1675     if ((mode&O_WRONLY) && (ino->mode&0222)==0)
1676     {
1677       boo="permission denied";
1678       return -1;
1679     }
1680     file->pos=0;
1681     file->ino=ino;
1682     file->mode=mode;
1683     return 0;
1684   }
1685   else
1686   {
1687     boo="not a regular file";
1688     return -1;
1689   }
1690 }
1691 /*}}}*/
1692 /* cpmRead            -- read                                    */ /*{{{*/
1693 ssize_t cpmRead(struct cpmFile *file, char *buf, size_t count)
1694 {
1695   int findext=1,findblock=1,extent=-1,block=-1,extentno=-1,got=0,nextblockpos=-1,nextextpos=-1;
1696   struct cpmSuperBlock const *sb=file->ino->sb;
1697   int blocksize=sb->blksiz;
1698   int extcap;
1699
1700   extcap=(sb->size<=256 ? 16 : 8)*blocksize;
1701   if (extcap>16384) extcap=16384*sb->extents;
1702   if (file->ino->ino==(ino_t)sb->maxdir+1) /* [passwd] */ /*{{{*/
1703   {
1704     if ((file->pos+(off_t)count)>file->ino->size) count=file->ino->size-file->pos;
1705     if (count) memcpy(buf,sb->passwd+file->pos,count);
1706     file->pos+=count;
1707 #ifdef CPMFS_DEBUG
1708     fprintf(stderr,"cpmRead passwd: read %d bytes, now at position %ld\n",count,(long)file->pos);
1709 #endif
1710     return count;
1711   }
1712   /*}}}*/
1713   else if (file->ino->ino==(ino_t)sb->maxdir+2) /* [label] */ /*{{{*/
1714   {
1715     if ((file->pos+(off_t)count)>file->ino->size) count=file->ino->size-file->pos;
1716     if (count) memcpy(buf,sb->label+file->pos,count);
1717     file->pos+=count;
1718 #ifdef CPMFS_DEBUG
1719     fprintf(stderr,"cpmRead label: read %d bytes, now at position %ld\n",count,(long)file->pos);
1720 #endif
1721     return count;
1722   }
1723   /*}}}*/
1724   else while (count>0 && file->pos<file->ino->size)
1725   {
1726     char buffer[16384];
1727
1728     if (findext)
1729     {
1730       extentno=file->pos/16384;
1731       extent=findFileExtent(sb,sb->dir[file->ino->ino].status,sb->dir[file->ino->ino].name,sb->dir[file->ino->ino].ext,0,extentno);
1732       nextextpos=(file->pos/extcap)*extcap+extcap;
1733       findext=0;
1734       findblock=1;
1735     }
1736     if (findblock)
1737     {
1738       if (extent!=-1)
1739       {
1740         int ptr;
1741
1742         ptr=(file->pos%extcap)/blocksize;
1743         if (sb->size>256) ptr*=2;
1744         block=(unsigned char)sb->dir[extent].pointers[ptr];
1745         if (sb->size>256) block+=((unsigned char)sb->dir[extent].pointers[ptr+1])<<8;
1746         if (block==0)
1747         {
1748           memset(buffer,0,blocksize);
1749         }
1750         else
1751         {
1752           int start,end;
1753
1754           start=(file->pos%blocksize)
1755                /sb->secLength;
1756           end=((file->pos%blocksize+(off_t)count)>blocksize ? blocksize-1 : (int)(file->pos%blocksize+count-1))
1757               /sb->secLength;
1758           if (block<sb->dirblks)
1759           {
1760             boo="Attempting to access block before beginning of data";
1761             if (got==0) got=-1;
1762             break;
1763           }
1764           if (readBlock(sb,block,buffer,start,end)==-1)
1765           {
1766             if (got==0) got=-1;
1767             break;
1768           }
1769         }
1770       }
1771       nextblockpos=(file->pos/blocksize)*blocksize+blocksize;
1772       findblock=0;
1773     }
1774     if (file->pos<nextblockpos)
1775     {
1776       if (extent==-1) *buf++='\0'; else *buf++=buffer[file->pos%blocksize];
1777       ++file->pos;
1778       ++got;
1779       --count;
1780     }
1781     else if (file->pos==nextextpos) findext=1; else findblock=1;
1782   }
1783 #ifdef CPMFS_DEBUG
1784   fprintf(stderr,"cpmRead: read %d bytes, now at position %ld\n",got,(long)file->pos);
1785 #endif
1786   return got;
1787 }
1788 /*}}}*/
1789 /* cpmWrite           -- write                                   */ /*{{{*/
1790 ssize_t cpmWrite(struct cpmFile *file, char const *buf, size_t count)
1791 {
1792   int findext=1,findblock=-1,extent=-1,extentno=-1,got=0,nextblockpos=-1,nextextpos=-1;
1793   int blocksize=file->ino->sb->blksiz;
1794   int extcap;
1795   int block=-1,start=-1,end=-1,ptr=-1,last=-1;
1796   char buffer[16384];
1797
1798   extcap=(file->ino->sb->size<=256 ? 16 : 8)*blocksize;
1799   if (extcap>16384) extcap=16384*file->ino->sb->extents;
1800   while (count>0)
1801   {
1802     if (findext) /*{{{*/
1803     {
1804       extentno=file->pos/16384;
1805       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);
1806       nextextpos=(file->pos/extcap)*extcap+extcap;
1807       if (extent==-1)
1808       {
1809         if ((extent=findFreeExtent(file->ino->sb))==-1) return (got==0 ? -1 : got);
1810         file->ino->sb->dir[extent]=file->ino->sb->dir[file->ino->ino];
1811         memset(file->ino->sb->dir[extent].pointers,0,16);
1812         file->ino->sb->dir[extent].extnol=EXTENTL(extentno);
1813         file->ino->sb->dir[extent].extnoh=EXTENTH(extentno);
1814         file->ino->sb->dir[extent].blkcnt=0;
1815         file->ino->sb->dir[extent].lrc=0;
1816         time(&file->ino->ctime);
1817         updateTimeStamps(file->ino,extent);
1818         updateDsStamps(file->ino,extent);
1819       }
1820       findext=0;
1821       findblock=1;
1822     }
1823     /*}}}*/
1824     if (findblock) /*{{{*/
1825     {
1826       ptr=(file->pos%extcap)/blocksize;
1827       if (file->ino->sb->size>256) ptr*=2;
1828       block=(unsigned char)file->ino->sb->dir[extent].pointers[ptr];
1829       if (file->ino->sb->size>256) block+=((unsigned char)file->ino->sb->dir[extent].pointers[ptr+1])<<8;
1830       if (block==0) /* allocate new block, set start/end to cover it */ /*{{{*/
1831       {
1832         if ((block=allocBlock(file->ino->sb))==-1) return (got==0 ? -1 : got);
1833         file->ino->sb->dir[extent].pointers[ptr]=block&0xff;
1834         if (file->ino->sb->size>256) file->ino->sb->dir[extent].pointers[ptr+1]=(block>>8)&0xff;
1835         start=0;
1836         /* By setting end to the end of the block and not the end
1837          * of the currently written data, the whole block gets
1838          * wiped from the disk, which is slow, but convenient in
1839          * case of sparse files.
1840          */
1841         end=(blocksize-1)/file->ino->sb->secLength;
1842         memset(buffer,0,blocksize);
1843         time(&file->ino->ctime);
1844         updateTimeStamps(file->ino,extent);
1845         updateDsStamps(file->ino,extent);
1846       }
1847       /*}}}*/
1848       else /* read existing block and set start/end to cover modified parts */ /*{{{*/
1849       {
1850         start=(file->pos%blocksize)/file->ino->sb->secLength;
1851         end=((int)(file->pos%blocksize+count)>=blocksize ? blocksize-1 : (int)(file->pos%blocksize+count-1))/file->ino->sb->secLength;
1852         if (file->pos%file->ino->sb->secLength)
1853         {
1854           if (readBlock(file->ino->sb,block,buffer,start,start)==-1)
1855           {
1856             if (got==0) got=-1;
1857             break;
1858           }
1859         }
1860         if (end!=start && (int)(file->pos%blocksize+count)<blocksize && (file->pos+count)%file->ino->sb->secLength)
1861         {
1862           if (readBlock(file->ino->sb,block,buffer,end,end)==-1)
1863           {
1864             if (got==0) got=-1;
1865             break;
1866           }
1867         }
1868       }
1869       /*}}}*/
1870       nextblockpos=(file->pos/blocksize)*blocksize+blocksize;
1871       findblock=0;
1872     }
1873     /*}}}*/
1874     /* fill block and write it */ /*{{{*/
1875     file->ino->sb->dirtyDirectory=1;
1876     while (file->pos!=nextblockpos && count)
1877     {
1878       buffer[file->pos%blocksize]=*buf++;
1879       ++file->pos;
1880       if (file->ino->size<file->pos) file->ino->size=file->pos;
1881       ++got;
1882       --count;
1883     }
1884     /* In case the data only fills part of a sector, the rest is
1885      * still initialized: A new block was cleared and the boundaries
1886      * of an existing block were read.
1887      */
1888
1889     (void)writeBlock(file->ino->sb,block,buffer,start,end);
1890     time(&file->ino->mtime);
1891     if (file->ino->sb->size<=256) for (last=15; last>=0; --last)
1892     {
1893       if (file->ino->sb->dir[extent].pointers[last])
1894       {
1895         break;
1896       }
1897     }
1898     else for (last=14; last>0; last-=2)
1899     {
1900       if (file->ino->sb->dir[extent].pointers[last] || file->ino->sb->dir[extent].pointers[last+1])
1901       {
1902         last/=2;
1903         break;
1904       }
1905     }
1906     if (last>0) extentno+=(last*blocksize)/extcap;
1907     file->ino->sb->dir[extent].extnol=EXTENTL(extentno);
1908     file->ino->sb->dir[extent].extnoh=EXTENTH(extentno);
1909     file->ino->sb->dir[extent].blkcnt=((file->pos-1)%16384)/128+1;
1910     if (file->ino->sb->type & CPMFS_EXACT_SIZE)
1911     {
1912       file->ino->sb->dir[extent].lrc = (128 - (file->pos%128)) & 0x7F;
1913     }
1914     else
1915     {
1916       file->ino->sb->dir[extent].lrc=file->pos%128;
1917     }
1918     updateTimeStamps(file->ino,extent);
1919     updateDsStamps(file->ino,extent);
1920     /*}}}*/
1921     if (file->pos==nextextpos) findext=1;
1922     else if (file->pos==nextblockpos) findblock=1;
1923   }
1924   return got;
1925 }
1926 /*}}}*/
1927 /* cpmClose           -- close                                   */ /*{{{*/
1928 int cpmClose(struct cpmFile *file)
1929 {
1930   return 0;
1931 }
1932 /*}}}*/
1933 /* cpmCreat           -- creat                                   */ /*{{{*/
1934 int cpmCreat(struct cpmInode *dir, char const *fname, struct cpmInode *ino, mode_t mode)
1935 {
1936   int user;
1937   char name[8],extension[3];
1938   int extent;
1939   struct cpmSuperBlock *drive;
1940   struct PhysDirectoryEntry *ent;
1941
1942   if (!S_ISDIR(dir->mode))
1943   {
1944     boo="No such file or directory";
1945     return -1;
1946   }
1947   if (splitFilename(fname,dir->sb->type,name,extension,&user)==-1) return -1;
1948 #ifdef CPMFS_DEBUG
1949   fprintf(stderr,"cpmCreat: %s -> %d:%-.8s.%-.3s\n",fname,user,name,extension);
1950 #endif
1951   if (findFileExtent(dir->sb,user,name,extension,0,-1)!=-1) return -1;
1952   drive=dir->sb;
1953   if ((extent=findFreeExtent(dir->sb))==-1) return -1;
1954   ent=dir->sb->dir+extent;
1955   drive->dirtyDirectory=1;
1956   memset(ent,0,32);
1957   ent->status=user;
1958   memcpy(ent->name,name,8);
1959   memcpy(ent->ext,extension,3);
1960   ino->ino=extent;
1961   ino->mode=s_ifreg|mode;
1962   ino->size=0;
1963
1964   time(&ino->atime);
1965   time(&ino->mtime);
1966   time(&ino->ctime);
1967   ino->sb=dir->sb;
1968   updateTimeStamps(ino,extent);
1969   updateDsStamps(ino,extent);
1970   return 0;
1971 }
1972 /*}}}*/
1973
1974 /* cpmAttrGet         -- get CP/M attributes                     */ /*{{{*/
1975 int cpmAttrGet(struct cpmInode *ino, cpm_attr_t *attrib)
1976 {
1977   *attrib = ino->attr;
1978   return 0;
1979 }
1980 /*}}}*/
1981 /* cpmAttrSet         -- set CP/M attributes                     */ /*{{{*/
1982 int cpmAttrSet(struct cpmInode *ino, cpm_attr_t attrib)
1983 {
1984   struct cpmSuperBlock *drive;
1985   int extent;
1986   int user;
1987   char name[8], extension[3];
1988   
1989   memset(name,      0, sizeof(name));
1990   memset(extension, 0, sizeof(extension));
1991   drive  = ino->sb;
1992   extent = ino->ino;
1993   
1994   drive->dirtyDirectory=1;
1995   /* Strip off existing attribute bits */
1996   memcpy7(name,      drive->dir[extent].name, 8);
1997   memcpy7(extension, drive->dir[extent].ext,  3);
1998   user = drive->dir[extent].status;
1999   
2000   /* And set new ones */
2001   if (attrib & CPM_ATTR_F1)   name[0]      |= 0x80;
2002   if (attrib & CPM_ATTR_F2)   name[1]      |= 0x80;
2003   if (attrib & CPM_ATTR_F3)   name[2]      |= 0x80;
2004   if (attrib & CPM_ATTR_F4)   name[3]      |= 0x80;
2005   if (attrib & CPM_ATTR_RO)   extension[0] |= 0x80;
2006   if (attrib & CPM_ATTR_SYS)  extension[1] |= 0x80;
2007   if (attrib & CPM_ATTR_ARCV) extension[2] |= 0x80;
2008   
2009   do 
2010   {
2011     memcpy(drive->dir[extent].name, name, 8);
2012     memcpy(drive->dir[extent].ext, extension, 3);
2013   } while ((extent=findFileExtent(drive, user,name,extension,extent+1,-1))!=-1);
2014
2015   /* Update the stored (inode) copies of the file attributes and mode */
2016   ino->attr=attrib;
2017   if (attrib&CPM_ATTR_RO) ino->mode&=~(S_IWUSR|S_IWGRP|S_IWOTH);
2018   else ino->mode|=(S_IWUSR|S_IWGRP|S_IWOTH);
2019   
2020   return 0;
2021 }
2022 /*}}}*/
2023 /* cpmChmod           -- set CP/M r/o & sys                      */ /*{{{*/
2024 int cpmChmod(struct cpmInode *ino, mode_t mode)
2025 {
2026   /* Convert the chmod() into a chattr() call that affects RO */
2027   int newatt = ino->attr & ~CPM_ATTR_RO;
2028
2029   if (!(mode & (S_IWUSR|S_IWGRP|S_IWOTH))) newatt |= CPM_ATTR_RO;
2030   return cpmAttrSet(ino, newatt);
2031 }
2032 /*}}}*/
2033 /* cpmUtime           -- set timestamps                          */ /*{{{*/
2034 void cpmUtime(struct cpmInode *ino, struct utimbuf *times)
2035 {
2036   ino->atime = times->actime;
2037   ino->mtime = times->modtime;
2038   time(&ino->ctime);
2039   updateTimeStamps(ino,ino->ino);
2040   updateDsStamps(ino,ino->ino);
2041 }
2042 /*}}}*/