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