/* * MSFIO.C File I/O for MS-DOS formatted diskettes * * This subroutine package provides the ability to read MS-DOS formatted * diskettes under other operating systems. * * It is assumed that the MS-DOS diskette is accessable as a device, or as a * device image contained in a file on the host system. On DEC systems the * RX33 diskette drive is compatible with IBM-PC/AT high density 5.25" (1.2 MB) * diskettes, and the RX23 diskette drive is compatible with IBM PS/2 high * density 3.5" (1.44 MB) diskettes. * * See MSFIO.TXT for additional documentation. * * Copyright (c) 1991 Shal Farley * Cheshire Engineering Corporation * 650 Sierra Madre Villa Avenue, Suite 201 * Pasadena, California 91107 * (818) 351-5493 * (818) 351-8645 FAX * shal@alumni.caltech.edu * * This software may be used and distributed for any purpose without license or * royalty payments so long as the above copyright notice is preserved. If you * have any comments, bug fixes, improvements, or new programs based upon this * software I'd like to hear from you. */ #include #include #ifdef decus # include "msasse.h" # include "errno.h" #else # include # include #endif #include "msport.h" #include "msfio.h" #include "msdir.h" #include "msstat.h" #include "rtdir.h" #include "msdefs.h" #define MS_ERRLIST 1 #include "mserrn.h" #if 0 /* Make this a zero when not debugging */ # define SONAR(s) fprintf(stderr, s) #else # define SONAR(s) #endif extern long time(); int mserrno; static MSFILE *msiov[MSFOPEN_MAX]; /* * Internal routines * * These are the prototype declarations for the routines which are local to * this module. */ /* File */ MSFILE *AllocFile(); /* Allocate MSFILE struct */ int FreeFile(); /* Free MSFILE struct */ /* Device */ MSDEV *DevFind(); /* See if device already open */ MSDEV *DevGetInfo(); /* Fill in MSDEV struct data fields */ /* FAT */ USHORT GetFE_0(); /* Extract lower 12-bit FAT entry */ USHORT GetFE_1(); /* Extract higher 12-bit FAT entry */ USHORT PutFE_0(); /* Insert lower 12-bit FAT entry */ USHORT PutFE_1(); /* Insert higher 12-bit FAT entry */ UCHAR *LoadFAT(); /* Copy the FAT into memory */ int FlushFAT(); /* Write the FAT back to disk */ long NbRemClust(); /* Bytes remaining in current cluster */ int NextClust(); /* Returns next cluster number */ int GetAvailClust(); /* Chains this cluster to next available one */ int *UnAllocFile(); /* Empty a file's FAT chain */ int SeekClust(); /* Seeks to specified cluster */ /* Directory */ UCHAR *LoadDir(); /* Copy the directory into memory */ int FlushDir(); /* Write the directory back to disk */ int isunused(); /* Directory Entry is "unused" */ int islast(); /* This is a marker for the end */ MSDE *FindEntry(); /* Find the file's directory entry */ MSDE *CreateEntry(); /* Create a new file directory entry */ /* Strings */ int fstrncmp(); /* Filename string compare */ void mssFde(); /* Build FILENAME.TYP from MSDE fields */ void msdeFs(); /* Parse MSDE fields from FILENAME.TYP */ char *fncpy(); /* Extract and copy file name */ char *dncpy(); /* Extract and copy device name */ /* Alignment */ USHORT UchFromPch(); /* Get unsigned char from (char *) */ USHORT UsFromPch(); /* Get unsigned short from (char *) */ long UlFromPch(); /* Get unsigned long from (char *) */ void ChFus(); /* Write unsigned short into char array */ void ChFul(); /* Write unsigned long into char array */ #ifdef decus /* * LIE, CHEAT & STEAL. * * The DECUS-C library does not support update operations. So we must reach * into the guts of the library and do what "r+b" mode should have done. * * There are two fundamental problems. First, fopen() doesn't recognize the * "+" character in its mode argument (even though the _flag field in the * FILE struct has a bit for it). Second, fseek() doesn't ever flush the * buffer (it checks for "w" files, but quietly does nothing). I cover over * both problems by wrapping some fixups around both routines. */ # define FOPEN MyFopen # define FSEEK MyFseek FILE * MyFopen(sName, sMode) /* Fake fopen() which enables update mode */ char *sName; char *sMode; { FILE *pf; int fMode; fMode = strcmp(sMode,"r+b") == 0; assert(fMode); pf = fopen(sName, "rn"); if (pf == NULL) return NULL; pf->_flag &= ~_IOREAD; /* Change the mode bits by hand */ pf->_flag |= _IORW; return pf; } int MyFseek(pf,offset,origin) /* Fake fseek() which works with fwrite() */ FILE *pf; long offset; int origin; { int temp; USHORT byte; USHORT block; if ((pf->_flag & IO_BZY) != 0) { fflush(pf); /* Write out any stuff in the buffer */ pf->_flag &= ~IO_BZY; } byte = offset & 0777; /* Byte within buffer */ block = offset >> 9; /* Block number */ assert(origin == 0); /* No other way */ assert(pf->_base != NULL); /* No accidents */ #if 1 /* The real fseek will read in buffer if need be (reading) */ if ((temp = fseek(pf, offset, origin))!= 0) return temp; #else pf->_cnt = pf->io_rbsz - byte; /* Count of empty bytes in the buffer */ pf->_ptr = pf->_base + byte; /* Pointer to first free byte */ #endif pf->io_bnbr = block; /* WHAM -- rewind this parameter */ /* #### * There is a hideous bug dormant here. The DECUS internals assume that * the "current" block has always been read: io_bnbr is always equal to * block+1, not block as above. The fseek() above will cause a $$get() of * the current block, and subsequent reads will read from it correctly. * When that block is exhausted, though, getc() will invoke $$get() without * incrementing io_bnbr -- reads will get the same block repeated! * * The MS-DOS library is "safe", though, because it must always do an * fseek() between blocks (file clusters may be out of order). Safe, that * is, unless some unfortunate clod tries to optimize out the "unnecessary" * seeks between sequential clusters. * * The fix, of course, is to modify $$get() so that it does not increment * io_bnbr, and getc() so that it does. And then track down the zillion * other routines that may have made assumptions about io_bnbr. And while * you're at it, modify fopen() to understand "+" mode, since that's all * there really is to it. */ return 0; } #else /* not decus */ /* * Normal I/O operations should work... */ # define FOPEN fopen # define FSEEK fseek #endif /* decus */ /* * External Interface -- The prototype declarations may be found in MSFIO.H */ /* * msfopen() Open an MS-DOS file * * This procedure must open the device containing the MS-DOS file, read the * device, and set up the MS-DOS file structures accordingly. */ MSFILE * /* Returns a pointer to an MS-DOS file */ msfopen(filename, mode) const char *filename; /* DEV[:RT-FILE]:FILENAME.TYP */ const char *mode; /* "r", "w" */ { MSFILE *pf; /* Pointer to this file */ MSDEV *pd; /* Pointer to its device */ if ( (pf = AllocFile()) == NULL) { return NULL; } if ( (pd = pf->pDEV = msdopen(filename)) == NULL) { FreeFile(pf); return NULL; } if ( !(pd->fValid || pd->fMaybe) ) { mserrno = (pd->fRT11)? MSE_RTDEV : MSE_BADDEV; FreeFile(pf); /* #### Only allow MS-DOS devices */ return NULL; } /* * We've got memory and a device to write on: start filling in the struct */ fncpy(pf->sName, filename); pf->nbPos = 0; pf->nbMax = 0; pf->clust = 0; pf->fRead = (strchr(mode, 'r') != NULL)? 1 : 0; pf->fWrite = (strchr(mode, 'w') != NULL)? 1 : 0; pf->fAppend = (strchr(mode, 'a') != NULL)? 1 : 0; pf->fUpdate = strchr(mode, '+') != NULL; pf->fBinary = strchr(mode, 'b') != NULL; pf->fBinary |= strchr(mode, 'n') != NULL; if (pf->fRead + pf->fWrite + pf->fAppend != 1) { mserrno = MSE_BADMODE; return NULL; } /* * See if the file's already there, and respond accordingly */ if (pf->fUpdate) { mserrno = MSE_NOUPDATE; /* Not implemented */ return NULL; } if ( (pf->pDE = FindEntry(pd, pf->sName)) != NULL) { if (pf->fWrite) { /* Discard prior contents */ pf->nbMax = 0; UnAllocFile(pf); /* Mark all the FAT entries as free */ } if (pf->fAppend) { /* Put file pointer at the end */ pf->nbPos = UlFromPch(&((MSDE *)pf->pDE)->FileSize[0]); pf->nbMax = pf->nbPos; /* #### Chase down the FAT to set pf->clust */ mserrno = MSE_NOAPPEND; /* Not implemented */ return NULL; } } else { /* Not there */ if (pf->fRead) { mserrno = MSE_FNF; /* Can't read what we can't find */ FreeFile(pf); return NULL; } else { /* Write or Append */ pf->pDE = CreateEntry(pd, pf->sName); } } return pf; } /* * msffirst() Get infomation about first matching file */ unsigned msffirst(filename, attrib, fileinfo) char *filename; USHORT attrib; MSSTAT *fileinfo; { MSFILE *pf; /* Pointer to this file */ MSDEV *pd; /* Pointer to the opened device */ /* Use a fake file so that the device is "cached" in the msiov array */ if ( (pf = AllocFile()) == NULL) { return (mserrno != 0)? mserrno : -1; } /* Get us a device to work on */ if ( (pd = pf->pDEV = fileinfo->pDEV = msdopen(filename) ) == NULL) { FreeFile(pf); return (mserrno != 0)? mserrno : -1; } if ( !(pd->fValid || pd->fMaybe) ) { FreeFile(pf); /* #### Only allow MS-DOS devices */ return mserrno = (pd->fRT11)? MSE_RTDEV : MSE_BADDEV; } /* Initialize the fields */ fncpy(fileinfo->wname, filename); fileinfo->di = 0; fileinfo->attrib = attrib; /* Go get the first file */ return msfnext(fileinfo); } /* * msfnext() Get information about next matching file. */ unsigned msfnext(pFS) MSSTAT *pFS; { /* Very much like FindEntry(), but fills in a struct * #### If we didn't cache the whole directory this routine would have * to read in the pieces! */ MSDE *pDE; /* pointer to a directory entry */ int nde; /* Total number of directory entries */ /* * Search for the file name & type */ pDE = (MSDE *)((MSDEV *)pFS->pDEV)->pDir; /* First entry */ pDE += pFS->di; /* Index into directory */ nde = ((MSDEV *)pFS->pDEV)->nDirEntries; do { if (pFS->di++ >= nde) return mserrno = MSE_FNF; if (islast(pDE)) return mserrno = MSE_FNF; if (!isunused(pDE)) { mssFde(pFS->name, pDE->Name, pDE->Extension); if (WfnMatch(pFS->wname, pFS->name)) break; } pDE++; } while ( TRUE ); /* Copy all the fields over */ pFS->size = UlFromPch(&pDE->FileSize); pFS->time = UsFromPch(&pDE->Time); pFS->date = UsFromPch(&pDE->Date); pFS->cluster = UsFromPch(&pDE->StartCluster); pFS->stat = UsFromPch(&pDE->Attributes); return 0; } /* * msfclose() Close an MS-DOS file */ int /* Returns 0 on success, else EOF */ msfclose(pMSF) MSFILE *pMSF; /* Pointer to this file */ { /* * Flush the file & write its size into the directory */ if ( pMSF->fWrite ) { fflush(((MSDEV *)pMSF->pDEV)->pFILE); ChFul(((MSDE *)pMSF->pDE)->FileSize, pMSF->nbMax); } /* * Unlink our data structures */ return FreeFile(pMSF); } /* * msfstat() Get information about an open file. */ /* #### int msfstat(pF, pSTAT) MSFILE *pF; MSSTAT *pSTAT; { return fillstat( pSTAT, (MSDE *)pF->pMSDE); } */ /* * msfilelength() Get length, in bytes, of an open file. */ long msfilelength(pF) MSFILE *pF; { return UlFromPch( &((MSDE *)pF->pDE)->FileSize[0] ); } /* * msgetc() Read a character */ int msgetc(pf) MSFILE *pf; { MSDEV *pd; /* Pointer to the device info */ pd = (MSDEV *)pf->pDEV; if (pf->nbPos == msfilelength(pf)) { return EOF; } if (pf->nbPos % (SECTOR * pd->nsCluster) == 0) { pf->clust = NextClust(pf); /* Advance to the next one */ if (!IsValidClust(pf->clust)) { return EOF; } if (SeekClust(pd, pf->clust) != 0) { mserrno = MSE_SEEKCLST; return EOF; } } pf->nbPos++; return getc(pd->pFILE); } /* * msputc() Write a character */ int msputc(ch, pf) char ch; MSFILE *pf; { MSDEV *pd; /* Pointer to the device info */ int temp; pd = (MSDEV *)pf->pDEV; if (pf->nbPos % (SECTOR * pd->nsCluster) == 0) { pd->pFILE->_flag |= IO_BZY; /* Write out what we had */ pf->clust = GetAvailClust(pf); /* Advance to the next one */ SONAR(("msputc: seek to cluster 0x%03X\n", pf->clust)); if (!IsValidClust(pf->clust)) { return EOF; } if (SeekClust(pd, pf->clust) != 0) { mserrno = MSE_SEEKCLST; return EOF; } } SONAR(("msputc: put 0x%2X at position %ld\n", ch, pf->nbPos)); pf->nbPos++; if (pf->nbPos > pf->nbMax) pf->nbMax = pf->nbPos; temp = putc(ch, pd->pFILE); return temp; } /* * msfread() Read a buffer full */ int /* Returns the number of objects read */ msfread(ptr, size, nobj, pF) void *ptr; /* Where to put them */ int size; /* Size of each */ int nobj; /* How many */ MSFILE *pF; { int bytesleft; /* The amount left to copy */ int readsize; /* The amount to read from this cluster */ char ch; bytesleft = size * nobj; while (bytesleft > 0) { readsize = MIN( NbRemClust(pF), msfilelength(pF) - pF->nbPos); if ( readsize <= 0) { if ((ch = msgetc(pF)) == EOF) break; *ptr++ = ch; bytesleft--; } else { readsize = MIN(bytesleft, readsize); if ( fread(ptr, 1, readsize, ((MSDEV *)pF->pDEV)->pFILE) != readsize || ferror( ((MSDEV *)pF->pDEV)->pFILE) ) { mserrno = MSE_READFAIL; return 0; } pF->nbPos += readsize; ptr += readsize; bytesleft -= readsize; } } return((size * nobj - bytesleft) / size); } /* * msfwrite() Write a buffer full */ int /* Returns the number of objects written */ msfwrite(ptr, size, nobj, pf) void *ptr; /* Where to put them */ int size; /* Size of each */ int nobj; /* How many */ MSFILE *pf; { int bytesleft; /* The amount left to copy */ int writesize; /* The amount to write to this cluster */ char ch; FILE *pFILE; if (pf->fRead) { mserrno = MSE_READONLY; return 0; } pFILE = ((MSDEV *)pf->pDEV)->pFILE; bytesleft = size * nobj; while (bytesleft > 0) { SONAR(("msfwrite: bytesleft == %d\n", bytesleft)); writesize = NbRemClust(pf); SONAR(("msfwrite: nbRemClust == %d\n", writesize)); if ( writesize <= 0) { ch = *ptr++; if ((ch = msputc(ch, pf)) == EOF) break; bytesleft--; } else { writesize = MIN(bytesleft, writesize); SONAR(("msfwrite: writesize == %d\n", writesize)); if ( fwrite(ptr, 1, writesize, pFILE) != writesize || ferror(pFILE) ) { mserrno = MSE_WRITEFAIL; return 0; } pf->nbPos += writesize; if (pf->nbPos > pf->nbMax) pf->nbMax = pf->nbPos; ptr += writesize; bytesleft -= writesize; } } return((size * nobj - bytesleft) / size); } extern int $$ferr; /* * msperror() Print an error message * */ void msperror(pCallerMsg) char *pCallerMsg; /* Additional text */ { if ($$ferr != 0) { perror(pCallerMsg); }; if ( (mserrno != 0) || ($$ferr == 0) ) { if ( (pCallerMsg != NULL) && (*pCallerMsg != EOF) ) { fprintf(stderr, "%s: ", pCallerMsg); } fprintf(stderr, "%s", mserrlist[mserrno]); if (pCallerMsg != NULL) { fprintf(stderr, "\n"); } } } USHORT msfeclust(clust, pd) /* Lookup FAT entry for this cluster */ USHORT clust; MSDEV *pd; { MSFE *pFE; pFE = (MSFE *)&pd->pFAT[clust/2*3]; return (clust & 1)? GetFE_1(pFE) : GetFE_0(pFE); } /* * msfflush() flush an MS-DOS file */ int /* Returns 0 on success, else EOF */ msfflush(pMSF) MSFILE *pMSF; /* Pointer to this file */ { /* * Flush the file */ if ( pMSF->fWrite ) { ChFul(((MSDE *)pMSF->pDE)->FileSize, pMSF->nbMax); return fflush(((MSDEV *)pMSF->pDEV)->pFILE); } return 0; } #if 0 /* Routines yet to be implemented */ /* Routines unlikely to ever be implemented */ remove fseek ftell feof ferror clearerr fwild fnext fgetname rename ungetc fgets fputs fprintf fscanf #endif /* * MSFILE routines -- These routines operate upon the MSFILE structure */ /* * AllocFile() Get a free slot in the file I/O vector, and fill it */ static MSFILE * AllocFile() { MSFILE *pf; /* Pointer to the new MSFILE */ int i; /* find an unused pointer in the array */ for (i = 0; msiov[i] != NULL; i++) { if (i >= MSFOPEN_MAX) { mserrno = MSE_TMFILES; return NULL; } } SONAR(("AllocFile: using msiov[%d] \n", i)); /* Allocate space for the MSFILE */ if ( (pf = malloc(sizeof(MSFILE))) == NULL) { #ifdef decus $$ferr = E$$NSP; #endif return NULL; } msiov[i]=pf; /* Put the new MSFILE in our array */ /* Initialize the MSFILE */ pf->nbPos = 0; /* Start at the beginning */ pf->nbMax = 0; /* Start at the beginning */ pf->pDEV = NULL; /* pointer to device info */ pf->pDE = NULL; /* pointer to device Directory Entry*/ pf->clust = 0; /* Cluster corresponding to nbPos */ pf->sName[0] = EOS; /* File name in ASCII */ return pf; } /* * FreeFile() Remove a file from a slot in the I/O vector */ FreeFile(pf) MSFILE *pf; /* Pointer to the MSFILE to free */ { MSDEV *pD; int i; /* find the given pointer in the array */ for (i = 0; msiov[i] != pf; i++) { if (i >= MSFOPEN_MAX) { mserrno = MSE_BADFREE; return 1; } } SONAR(("FreeFile: freeing msiov[%d] \n", i)); if (pf->pDEV != NULL) { pD = pf->pDEV; /* Unlink device from this file */ pf->pDEV = NULL; msdclose(pD); } free(pf); msiov[i] = NULL; return 0; } /* * Device Routines -- These routines operate upon the MSDEV structure */ /* * msdopen() Set up the MS-DOS device info */ MSDEV * msdopen(devname) char *devname; /* RT-11 "DEV:" or "DEV:FILNAM.TYP" */ { MSDEV *pDEV; /* Our MS-DOS device struct */ FILE *pFILE; /* The corresponding RT-11 file */ char sName[MSDEVNAME_MAX]; /* The RT-11 file name */ /* Extract the RT-11 file name */ dncpy(sName,devname); /* First check to see if we've already opened this device */ if ( (pDEV = DevFind(sName)) != NULL) { SONAR(("msdopen: Device already open: %s\n", devname)); return pDEV; } /* Allocate space for the MSDEV */ if ( (pDEV = malloc(sizeof(MSDEV))) == NULL) { #ifdef decus $$ferr = E$$NSP; #endif return NULL; } /* Attempt to open the device */ if ((pFILE = FOPEN(sName, "r+b")) == NULL) { free(pDEV); return NULL; } SONAR(("msdopen: Opened %s\n", sName)); /* * Fill in the DEV fields */ pDEV->pFILE = pFILE; strcpy(pDEV->sName, sName); if (DevGetInfo(pDEV) == NULL) { free(pDEV); return NULL; } if (pDEV->fValid || pDEV->fMaybe) { if ( (pDEV->pFAT = LoadFAT(pDEV)) == NULL) { free(pDEV); return NULL; } if ( (pDEV->pDir = LoadDir(pDEV)) == NULL) { free(pDEV); return NULL; } } else { pDEV->pFAT = NULL; pDEV->pDir = NULL; } return pDEV; } /* * msdclose() Close the MS-DOS device */ int msdclose(pd) MSDEV *pd; { int i; FlushFAT(pd); FlushDir(pd); /* But don't free it if some other file still has it open */ for (i=0; ipDEV == pd){ return 0; } } fclose(pd->pFILE); free(pd); return 0; } /* * DevFind() See if we've already opened this device */ static MSDEV * DevFind(devname) char *devname; /* RT-11 "DEV:" or "DEV:FILNAM.TYP" */ { int i; /* #### This really should do something better than compare the names given by the user. In RT-11 it should lookup the names to get physical names (in case a logical assignment was used) and if the device is an LD unit it should lookup the physical file name. */ for (i=0; ipDEV != NULL){ SONAR(("Devfind: \"%s\" ?= msiov[%d]->\"%s\"\n", \ devname, i, ((MSDEV *)msiov[i]->pDEV)->sName)); if (fstrncmp(((MSDEV *)msiov[i]->pDEV)->sName, devname,MSDEVNAME_MAX) == 0){ return msiov[i]->pDEV; } } } return NULL; } /* * DevGetInfo() Set up the MS-DOS device info */ static MSDEV * DevGetInfo(pDEV) MSDEV *pDEV; /* Our MS-DOS device struct */ { FILE *pf; /* The corresponding RT-11 file */ USHORT signature; /* MS-DOS boot sector signature = 0xAA55 */ MSBS bs; /* MS-DOS Bios Parameter Block */ int temp; #define IDLEN 16 char buf[IDLEN]; /* A small buffer */ pf = pDEV->pFILE; /* * Look in the bootstrap for MSDOS marker */ if ( (temp=fseek(pf,MSVH_SYSID,SEEK_SET)) != 0) { return NULL; } if ( fread(&signature,1,sizeof(USHORT),pf) != sizeof(USHORT) || ferror(pf) ) { return NULL; } SONAR(("DevGetInfo: Signature = 0x%4X\n", signature)); pDEV->fValid = signature == 0xAA55; pDEV->fMaybe = FALSE; pDEV->fRT11 = FALSE; /* * Read in the Boot Sector */ if ( (temp=fseek(pf,MSVH_BS,SEEK_SET)) != 0) { return NULL; } if ( fread(&bs,1,sizeof(MSBS),pf) != sizeof(MSBS) || ferror(pf) ) { return NULL; } pDEV->nsCluster = UchFromPch(&bs.SecPerClust); pDEV->nsBoot = UsFromPch(&bs.ResSectors); pDEV->nFATs = UchFromPch(&bs.FATs); pDEV->nDirEntries = UsFromPch(&bs.RootDirEnts); pDEV->nsTotal = (long)UsFromPch(&bs.Sectors); pDEV->nsFAT = UsFromPch(&bs.FATsecs); if ( (pDEV->nsTotal == 0) && (bs.BootSignature == 0x29) ) { pDEV->nsTotal = UlFromPch(&bs.HugeSectors); } /* * Make sure we don't trash RT-11 or unknown disks */ if (!pDEV->fValid ){ /* If this wasn't a valid MSDOS disk... */ if ( (temp=fseek(pf,RTVH_SYSID,SEEK_SET)) != 0) { return NULL; } if ( fread(&buf[0],1,IDLEN,pf) != IDLEN || ferror(pf) ) { return NULL; } SONAR(("DevGetInfo: RT-11 ID = \"%.12s\"\n", buf)); if (strncmp(buf, "DECRT11A ",12) == 0){ pDEV->fRT11 = TRUE; } /* * #### Here we should simply leave it "Unrecognized", but instead * we'll check to see if the parameters are reasonable. This is because * diskettes formatted by FastBack(tm) don't have the boot signature, * yet are otherwise readable as MS-DOS disks. Note, such diskettes may * be only partially formatted (FastBack only formats as much of the * disk as it needs). */ if ( (bs.BytesPerSec[0] == 0) && (bs.BytesPerSec[1] == 2) && (bs.ResSectors[0] == 1) && (bs.ResSectors[1] == 0) && (bs.FATs == 2) ) { pDEV->fMaybe = TRUE; } } SONAR(("DevGetInfo: fatsize = %2d, ", pDEV->nsFAT)); SONAR(("clustersize = %2d, ", pDEV->nsCluster)); SONAR(("dirsize = %2d\n", pDEV->nDirEntries)); return pDEV; } /* * Routines to deal with the File Allocation Table (FAT) */ static int IsValidClust(clust) /* Check for valid cluster number */ int clust; { if (clust > 0xFF8) { /* End of cluster chain */ mserrno = MSE_NOCLST; return FALSE; } if (clust == 0) { /* Unused cluster */ mserrno = MSE_EMPTYCLST; return FALSE; } if ( (clust > 0xFF0) || (clust == 1) ) { /* Illegal Values */ mserrno = MSE_BADCLST; return FALSE; } return TRUE; } static USHORT GetFE_0(pFE) /* Return least entry */ MSFE *pFE; { return( ((USHORT)pFE->b1<<8 ) + (UchFromPch(&pFE->b0)) & 0xFFF ); } static USHORT GetFE_1(pFE) /* Return higher entry */ MSFE *pFE; { return( ((USHORT)pFE->b2<<8) + (UchFromPch(&pFE->b1)) >> 4 & 0xFFF); } static USHORT PutFE_0(val, pFE) /* Store into least entry */ USHORT val; MSFE *pFE; { assert(val == (val & 0xFFF)); pFE->b0 = LSB(val); pFE->b1 = MSB(val) + (pFE->b1 & 0xF0); return val; } static USHORT PutFE_1(val, pFE) /* Store into upper entry */ USHORT val; MSFE *pFE; { assert(val == (val & 0xFFF)); pFE->b1 = LSB(val << 4) + (pFE->b1 & 0x0F); pFE->b2 = MSB(val << 4); return val; } static UCHAR * LoadFAT(pd) /* Copy the FAT into memory for later use */ MSDEV *pd; { long fatoffset; /* Argument to fseek */ UCHAR *pFAT; /* Pointer to the memory */ int fatbytes; /* Size of the memory */ int temp; fatoffset = (long)SECTOR * pd->nsBoot; fatbytes = SECTOR * pd->nsFAT; if ( (pFAT = malloc(fatbytes)) == NULL) { #ifdef decus $$ferr = E$$NSP; #endif return NULL; } SONAR(("LoadFAT: Reading FAT_1...\n")); if ( (temp=fseek(pd->pFILE,fatoffset,SEEK_SET)) != 0) { free(pFAT); return NULL; } if ( fread(pFAT,1,fatbytes,pd->pFILE) != fatbytes || ferror(pd->pFILE) ) { free(pFAT); return NULL; } return pFAT; } static int FlushFAT(pd) /* Copy the FAT into memory for later use */ MSDEV *pd; { long fatoffset; /* Argument to fseek */ int fatbytes; /* Size of the memory */ if (!pd->fFAT || (pd->pFAT == NULL) ) { return 0; /* Do only what we must */ } fatoffset = (long)SECTOR * pd->nsBoot; fatbytes = SECTOR * pd->nsFAT; SONAR(("FlushFAT: Writing FAT_1...\n")); if ( FSEEK(pd->pFILE,fatoffset,SEEK_SET) != 0) { return EOF; } if ( fwrite(pd->pFAT,1,fatbytes,pd->pFILE) != fatbytes || ferror(pd->pFILE) ) { return EOF; } SONAR(("FlushFAT: Writing FAT_2...\n")); if ( fwrite(pd->pFAT,1,fatbytes,pd->pFILE) != fatbytes || ferror(pd->pFILE) ) { return EOF; } fflush(pd->pFILE); pd->fFAT = FALSE; return 0; } static long NbRemClust(pf) /* Bytes remaining in current cluster */ MSFILE *pf; { int clustbyte; /* Bytes per cluster */ clustbyte = SECTOR * ((MSDEV *)(pf)->pDEV)->nsCluster; return (clustbyte - (pf->nbPos % clustbyte)) % clustbyte; } static int PutClust(val, clust, pf) /* Put value into FAT entry for this cluster */ int val; int clust; MSFILE *pf; { MSFE *pFE; pFE = (MSFE *)&((MSDEV *)pf->pDEV)->pFAT[clust/2*3]; return (clust & 1)? PutFE_1(val, pFE) : PutFE_0(val, pFE); } static int NextClust(pf) /* Returns next cluster number */ MSFILE *pf; { if (pf->nbPos == 0) { /* Start at the beginning */ return UsFromPch( ((MSDE *)pf->pDE)->StartCluster ); } return msfeclust(pf->clust, pf->pDEV); } static int GetAvailClust(pf) /* Chains this cluster to the next available */ MSFILE *pf; { int newclust; newclust = pf->clust + 1; /* Hope for the best... */ if (msfeclust(newclust, pf->pDEV) != 0) { /* * Too bad, the sequential cluster was not available. That was my one * concession to efficiency. Now I'll just start at the beginning and * take the first cluster available. */ for (newclust = 2; msfeclust(newclust, pf->pDEV) != 0; newclust++) { if (newclust >= ((MSDEV *)pf->pDEV)->nsTotal / ((MSDEV *)pf->pDEV)->nsCluster) { return 0xFFF; } } } if (pf->clust == 0) { /* Start at the beginning */ ChFus(((MSDE *)pf->pDE)->StartCluster, newclust); ((MSDEV *)pf->pDEV)->fDir = TRUE; } else { PutClust(newclust, pf->clust, pf); } PutClust(0xFFF, newclust, pf); ((MSDEV *)pf->pDEV)->fFAT = TRUE; return newclust; } static int UnAllocFile(pf) /* Free the file's FAT entries */ MSFILE *pf; { MSFE *pFE; int clust; clust = UsFromPch( ((MSDE *)pf->pDE)->StartCluster ); ((MSDEV *)pf->pDEV)->fFAT = TRUE; while (IsValidClust(clust) ) { pFE = (MSFE *)&((MSDEV *)pf->pDEV)->pFAT[clust/2*3]; if (clust & 1) { clust = GetFE_1(pFE); PutFE_1(0, pFE); } else { clust = GetFE_0(pFE); PutFE_0(0, pFE); } } if (mserrno == MSE_NOCLST) { mserrno = MSE_UNKNOWN; return TRUE; } else { return FALSE; } } static int SeekClust(pd, clust) /* Seeks to specified cluster */ MSDEV *pd; int clust; { long absbyte; /* Absolute byte offset */ /* * seek position == ((cluster-2) * clustersize + offset) * SECTOR ) * The "offset" is to allow room for the FATs and Directory. * The "-2" is stupidity on the part of the filesystem designer. */ absbyte = (pd->nsBoot + pd->nFATs * pd->nsFAT + (clust-2) * pd->nsCluster ) * (long)SECTOR + pd->nDirEntries * (long)sizeof(MSDE); return FSEEK(pd->pFILE, absbyte, SEEK_SET); } /* * Routines to deal with the directory */ static UCHAR * LoadDir(pd) /* Copy the directory into memory for later use */ MSDEV *pd; { long diroffset; /* Argument to fseek */ UCHAR *pDir; /* Pointer to the memory */ int dirbytes; /* Size of the memory */ int temp; diroffset = (long)SECTOR * (pd->nsBoot + 2*pd->nsFAT); dirbytes = pd->nDirEntries * sizeof(MSDE); if ( (pDir = malloc(dirbytes)) == NULL) { #ifdef decus $$ferr = E$$NSP; #endif return NULL; } SONAR(("LoadDir: Reading directory...\n")); if ( (temp=fseek(pd->pFILE,diroffset,SEEK_SET)) != 0) { free(pDir); return NULL; } if ( fread(pDir,1,dirbytes,pd->pFILE) != dirbytes || ferror(pd->pFILE) ) { free(pDir); return NULL; } pd->fDir = FALSE; return pDir; } static int FlushDir(pd) /* Copy the directory into memory for later use */ MSDEV *pd; { long diroffset; /* Argument to fseek */ int dirbytes; /* Size of the memory */ if (!pd->fDir || (pd->pDir == NULL) ) { return 0; /* Do only what we must */ } diroffset = (long)SECTOR * (pd->nsBoot + 2*pd->nsFAT); dirbytes = pd->nDirEntries * sizeof(MSDE); SONAR(("FlushDir: Writing directory...\n")); if ( FSEEK(pd->pFILE,diroffset,SEEK_SET) != 0) { return EOF; } if ( fwrite(pd->pDir,1,dirbytes,pd->pFILE) != dirbytes || ferror(pd->pFILE) ) { return EOF; } fflush(pd->pFILE); pd->fDir = FALSE; return 0; } static int isunused(pDE) /* Directory Entry is "unused" */ MSDE *pDE; { return pDE->Name[0] == 0xE5; } static int islast(pDE) /* This is a marker for the end */ MSDE *pDE; { return pDE->Name[0] == EOS; } /* * FindEntry() Get a copy of the file's directory entry */ static MSDE * FindEntry(pDEV,sName) MSDEV *pDEV; char *sName; /* "FILENAME.TYP" */ { /* #### If we didn't cache the whole directory this routine would have * to malloc() some memory to hold a copy of the directory entry! */ MSDE *pDE; /* pointer to directory entry */ char fname[MSFN_FMAX]; /* "FILENAME" */ char ftype[MSFN_TMAX]; /* "TYP" */ msdeFs(fname, ftype, sName); /* Parse the file name into fields */ /* * Search for the file name & type */ for ( pDE = (MSDE *)pDEV->pDir; pDE < (MSDE *)pDEV->pDir + pDEV->nDirEntries; pDE++) { if (islast(pDE)) break; /* Stop at nil entry */ if (isunused(pDE)) continue; /* Skip unused entries */ if (fstrncmp(pDE->Name, fname, MSFN_FMAX) == 0){ if (fstrncmp(pDE->Extension, ftype, MSFN_TMAX) == 0){ SONAR(("FindEntry: Found %8.8s.%3.3s\n",pDE->Name,pDE->Extension)); return pDE; } } } SONAR(("FindEntry: Not found %s\n", sName)); return NULL; } /* * CreateEntry() Create a new file directory entry */ static MSDE * CreateEntry(pDEV, sName) MSDEV *pDEV; char *sName; /* "FILENAME.TYP" */ { /* #### If we didn't cache the whole directory this routine would have * to malloc() some memory to hold a copy of the directory entry! */ MSDE *pDE; /* pointer to directory entry */ long lTime; /* Variables for current date & time */ struct tm *ptm; /* " */ /* * Search for an unused entry */ for ( pDE = (MSDE *)pDEV->pDir; pDE < (MSDE *)pDEV->pDir + pDEV->nDirEntries; pDE++) { if (islast(pDE)) { /* Extend directory */ pDE->Name[0] = 0xE5; /* " Mark this one unused */ if (pDE+1 < (MSDE *)pDEV->pDir + pDEV->nDirEntries ){ (pDE+1)->Name[0] = 0; /* " Mark the next as last */ } } if (isunused(pDE)) { /* Take the unused entry */ pDEV->fDir = TRUE; msdeFs(pDE->Name, pDE->Extension, sName); pDE->Attributes = MSS_DIRTY; /* * Insert the current date & time */ lTime = time(NULL); ptm = localtime(&lTime); ChFus(pDE->Time, (ptm->tm_hour<<11) + (ptm->tm_min<<5) + (ptm->tm_sec>>1) ); ChFus(pDE->Date, (ptm->tm_year-80<<9) + (ptm->tm_mon+1<<5) + ptm->tm_mday ); pDE->StartCluster[0] = 0; pDE->StartCluster[1] = 0; pDE->FileSize[0] = 0; pDE->FileSize[1] = 0; pDE->FileSize[2] = 0; pDE->FileSize[3] = 0; return pDE; } } SONAR(("CreateEntry: No room %s\n", sName)); mserrno = MSE_DIRFUL; return NULL; } /* * String Routines -- file names & other strings */ static int fstrncmp(s1, s2, n) /* Filename string compare */ char *s1, *s2; int n; { /* Like strncmp, but case independent, and ignore trailing whitespace */ while (n > 0 && tolower(*s1) == tolower(*s2) && *s1 != EOS) { s1++; s2++; n--; } if ( (n==0) || ((*s1 == ' ' || *s1 == EOS) && (*s2 == ' ' || *s2 == EOS)) ) { return 0; } return(*s1-*s2); } static void mssFde(buf, pchN, pchT) /* Build FILENAME.TYP in buf */ char *buf; char *pchN; /* Name field as in a directory entry */ char *pchT; /* Type field " */ { char *t; assert(buf != NULL); assert(pchN != NULL); assert(pchT != NULL); strncpy(buf, pchN, MSFN_FMAX); /* Copy the name field */ t = &buf[MSFN_FMAX]; /* & strip out any whitespace */ do { *t-- = EOS; } while (*t == ' '); strcat(buf, "."); strncat(buf, pchT, MSFN_TMAX); /* Copy the type field */ t = &buf[strlen(buf)]; /* & strip out its whitespace */ do { *t-- = EOS; } while (*t == ' '); /* * Fix up Microsoft's kludge for international characters */ if (buf[0] == '\005') buf[0] = 0xE5; } static void msdeFs(pchN, pchT, sName) /* Parse a string into file name and type */ char *pchN; /* Name field as in a directory entry */ char *pchT; /* Type field " */ char *sName; /* File name string */ { char *pT; /* Points to the '.' in sName's FILENAME.TYP */ char *pE; /* Points to the EOS of sName */ char *pch; /* pointer into sName */ int i; /* index into pchN or pchT */ assert(pchN != NULL); assert(pchT != NULL); assert(sName != NULL); pE = &sName[strlen(sName)]; /* Set up initial conditions */ if ((pT=strchr(sName,'.')) == NULL) pT = pE; pch = sName; for (i = 0; (pch= end) { /* "DEV:" */ sprintf(buf, "%.*s:", MIN(end-dev,RTFN_DMAX), dev); } else if (typ == NULL) { /* "DEV:DIRNAM" */ sprintf(buf, "%.*s:%.*s.MS", MIN(dir-dev-1,RTFN_DMAX), dev, MIN(end-dir,RTFN_FMAX), dir); } else { /* "DEV:DIRNAM.TYP" */ sprintf(buf, "%.*s:%.*s.%.*s", MIN(dir-dev-1,RTFN_DMAX), dev, MIN(typ-dir-1,RTFN_FMAX), dir, MIN(end-typ,RTFN_TMAX), typ); } } /* SONAR(("\"%s\" == dncpy(buf, \"%s\")\n", buf, filename)); */ return buf; } /* * MS-DOS runs on strict little-endian machines. To use its data structures * we must provide conversions to the host's formats... */ /* * The PDP-11 is a word-aligned machine, but the data on disk is only byte * aligned. These definitions build values from the individual bytes, stored * little-endian in the disk data structures. There is a routine for UCHAR * values because the DECUS C compiler doesn't support unsigned char values. */ static USHORT UchFromPch(pch) /* Get unsigned char from (char *) */ char *pch; { return LSB(*pch); } static USHORT UsFromPch(pch) /* Get unsigned short from (char *) */ char *pch; { return (UchFromPch(pch+1) << 8) + UchFromPch(pch); } static long UlFromPch(pch) /* Get unsigned long from (char *) */ char *pch; { return ((long)UsFromPch(pch+2) << 16) + UsFromPch(pch); } static void ChFus(pch, val) /* Store long value in character array */ char *pch; /* Pointer to destination */ USHORT val; /* Value to store */ { pch[0] = val; pch[1] = val >> 8; } static void ChFul(pch, val) /* Store long value in character array */ char *pch; /* Pointer to destination */ long val; /* Value to store */ { ChFus(pch, (USHORT)val); ChFus(pch+2, (USHORT)(val >> 16)); }