/*- * $Id: hanfile.c,v 1.6 90/02/10 21:38:26 Rhialto Exp $ * $Log: hanfile.c,v $ * Revision 1.6 90/02/10 21:38:26 Rhialto * Optimized 12-bit fat unpacking. * * Revision 1.5 90/01/27 20:26:51 Rhialto * Fixed ATTR_ARCHIVED bit in MSWrite() * * Revision 1.4 90/01/23 02:32:23 Rhialto * Add 16-bit FAT support. * * Revision 1.3 90/01/23 00:39:04 Rhialto * Always return -1 on MSWrite error. * * Revision 1.2 89/12/17 23:04:39 Rhialto * Add ATTR_READONLY support * * Revision 1.1 89/12/17 20:03:11 Rhialto * Initial revision * * HANFILE.C * * The code for the messydos file system handler. * * This parts handles files and the File Allocation Table. * * This code is (C) Copyright 1989,1990 by Olaf Seibert. All rights reserved. * May not be used or copied without a licence. -*/ #include "han.h" #include "dos.h" #ifdef DEBUG # define debug(x) dbprintf x #else # define debug(x) #endif extern char DotDot[1 + 8 + 3]; /* * Read the FAT from the disk, and count the free clusters. */ int GetFat() { int i; byte *secptr; if (!Fat && !(Fat = AllocMem((long) Disk.bps * Disk.spf, BufMemType))) { debug(("No memory for FAT\n")); return 0; } FatDirty = FALSE; for (i = 0; i < Disk.spf; i++) { if (secptr = GetSec(Disk.res + i)) { CopyMem(secptr, Fat + i * Disk.bps, (long) Disk.bps); FreeSec(secptr); } else { /* q&d way to set the entire FAT to FAT_EOF */ setmem(Fat + i * Disk.bps, (int) Disk.bps, FAT_EOF); /* 0xFF */ } } debug(("counting free clusters\n")); Disk.nsectsfree = 0; for (i = MS_FIRSTCLUST; i <= Disk.maxclst; i++) { if (GetFatEntry((word) i) == FAT_UNUSED) Disk.nsectsfree += Disk.spc; } return 1; } void FreeFat() { if (Fat) { FreeMem(Fat, (long) Disk.bps * Disk.spf); Fat = NULL; FatDirty = FALSE; } } /*- * The FAT consists of 12-bits entries for each cluster, * indicating the next cluster in the chain, or FFF for EOF. * * Every two entries are packed in three bytes, like this: * * Two entries abc 123 (for one cluster and the next) * are packed as bc 3a 12 -*/ word GetFatEntry(cluster) word cluster; { if (!Fat && !GetFat()) return FAT_EOF; if (Disk.fat16bits) { return OtherEndianWord(((word *)Fat)[cluster]); } else { register int offset = 3 * (cluster / 2); register word twoentries; if (cluster & 1) { twoentries = Fat[offset + 1] >> 4; twoentries |= Fat[offset + 2] << 4; } else { twoentries = Fat[offset]; twoentries |= (Fat[offset + 1] & 0x0F) << 8; } /* * Convert the special values 0xFF0 .. 0xFFF to 16 bits so they * can be checked consistently. */ if (twoentries >= 0xFF0) twoentries |= 0xF000; return twoentries; } } #ifndef READONLY void SetFatEntry(cluster, value) word cluster; word value; { if (!Fat && !GetFat()) return; if (Disk.fat16bits) { ((word *)Fat)[cluster] = OtherEndianWord(value); } else { register int offset = 3 * (cluster / 2); if (cluster & 1) { /* 123 kind of entry */ Fat[offset + 2] = value >> 4; Fat[offset + 1] &= 0x0F; Fat[offset + 1] |= (value & 0x0F) << 4; } else { /* abc kind of entry */ Fat[offset + 0] = value; Fat[offset + 1] &= 0xF0; Fat[offset + 1] |= (value >> 8) & 0x0F; } } FatDirty = TRUE; } /* * Find a free cluster to install as the one following this one. Start * looking for it right after the given one, so we allocate the cluster * chain as contiguous as possible. If we run off the end of the disk, we * start again at the beginning. The termination test should not be * necessary (and won't work if we are given MSFIRSTCLUST - 1) but won't * harm either. */ word FindFreeCluster(prev) word prev; { register word i; if (prev == 0 || prev == FAT_EOF) prev = MS_FIRSTCLUST - 1; if (Disk.nsectsfree >= Disk.spc) { for (i = prev + 1; i != prev; i++) { if (i > Disk.maxclst) /* Wrap around */ i = MS_FIRSTCLUST; if (GetFatEntry(i) == FAT_UNUSED) { SetFatEntry(i, FAT_EOF); if (prev >= MS_FIRSTCLUST) SetFatEntry(prev, i); Disk.nsectsfree -= Disk.spc; return i; } } } return FAT_EOF; } /* * Add a cluster to a cluster chain. For input, we get some cluster we * know that is on the chain, even if it is the first one. */ word ExtendClusterChain(cluster) register word cluster; { register word nextcluster; /* * Find the end of the cluster chain to tack the new cluster on to. * Then FindFreeCluster will (or won't) extend the chain for us. */ if (cluster != 0) while ((nextcluster = NextCluster(cluster)) != FAT_EOF) { cluster = nextcluster; } return FindFreeCluster(cluster); } /* * Free a chain of clusters by setting their FAT entries to FAT_UNUSED. */ void FreeClusterChain(cluster) register word cluster; { register word nextcluster; while (cluster != FAT_EOF) { nextcluster = NextCluster(cluster); SetFatEntry(cluster, FAT_UNUSED); Disk.nsectsfree += Disk.spc; cluster = nextcluster; } } #endif /* READONLY */ /* * This routine opens a file. */ struct MSFileHandle * MSOpen(parentdir, name, mode) struct MSFileLock *parentdir; char *name; long mode; { struct MSFileLock *fl; struct MSFileHandle *fh = NULL; long lockmode; switch (mode) { case MODE_NEWFILE: case MODE_READWRITE: lockmode = EXCLUSIVE_LOCK ^ MODE_CREATEFILE; break; default: mode = MODE_OLDFILE; case MODE_OLDFILE: lockmode = SHARED_LOCK; } if (fl = MSLock(parentdir, name, lockmode)) { makefh: if (fl->msfl_Msd.msd_Attributes & ATTR_DIR) { error = ERROR_OBJECT_WRONG_TYPE; MSUnLock(fl); } else if (fh = AllocMem((long) sizeof (*fh), MEMF_PUBLIC)) { #ifndef READONLY /* Do we need to truncate the file? */ if (mode == MODE_NEWFILE && fl->msfl_Msd.msd_Cluster) { FreeClusterChain(fl->msfl_Msd.msd_Cluster); fl->msfl_Msd.msd_Cluster = 0; fl->msfl_Msd.msd_Filesize = 0; UpdateFileLock(fl); } #endif fh->msfh_Cluster = fl->msfl_Msd.msd_Cluster; fh->msfh_SeekPos = 0; fh->msfh_FileLock = fl; } else { error = ERROR_NO_FREE_STORE; MSUnLock(fl); } return fh; } #ifndef READONLY /* * If the file was not found, see if we can make a new one. Therefore * we need to have an empty spot in the desired directory, and create * an MSFileLock for it. */ if (!(lockmode & MODE_CREATEFILE) && (fl = EmptyFileLock)) { debug(("Creating new file\n")); EmptyFileLock = NULL; fl->msfl_Msd.msd_Attributes = ATTR_ARCHIVED; UpdateFileLock(fl); goto makefh; } if (EmptyFileLock) { MSUnLock(EmptyFileLock); EmptyFileLock = NULL; } #endif return NULL; } void MSClose(fh) register struct MSFileHandle *fh; { if (fh) { MSUnLock(fh->msfh_FileLock); FreeMem(fh, (long) sizeof (*fh)); } } long MSSeek(fh, position, mode) struct MSFileHandle *fh; long position; long mode; { long oldpos = fh->msfh_SeekPos; long newpos = oldpos; long filesize = fh->msfh_FileLock->msfl_Msd.msd_Filesize; word cluster = fh->msfh_Cluster; word oldcluster; word newcluster; switch (mode) { case OFFSET_BEGINNING: newpos = position; break; case OFFSET_CURRENT: newpos += position; break; case OFFSET_END: newpos = filesize - position; break; } if (newpos < 0 || newpos > filesize) { error = ERROR_SEEK_ERROR; return -1; } newcluster = newpos / Disk.bpc; oldcluster = oldpos / Disk.bpc; if (oldcluster > newcluster) { /* Seek backwards */ cluster = fh->msfh_FileLock->msfl_Msd.msd_Cluster; oldcluster = 0; } if (oldcluster < newcluster) { if (CheckLock(fh->msfh_FileLock)) return -1L; while (oldcluster < newcluster) { cluster = NextCluster(cluster); oldcluster++; } } fh->msfh_Cluster = cluster; fh->msfh_SeekPos = newpos; return oldpos; } long MSRead(fh, userbuffer, size) register struct MSFileHandle *fh; register byte *userbuffer; register long size; { long oldsize; if (CheckLock(fh->msfh_FileLock)) return -1L; if (fh->msfh_SeekPos + size > fh->msfh_FileLock->msfl_Msd.msd_Filesize) size = fh->msfh_FileLock->msfl_Msd.msd_Filesize - fh->msfh_SeekPos; oldsize = size; while (size > 0) { word offset; word sector; byte *diskbuffer; long insector; long tocopy; offset = fh->msfh_SeekPos % Disk.bpc; sector = ClusterOffsetToSector(fh->msfh_Cluster, (word) offset); if (diskbuffer = GetSec(sector)) { offset %= Disk.bps; insector = Disk.bps - offset; tocopy = lmin(size, insector); CopyMem(diskbuffer + offset, userbuffer, tocopy); userbuffer += tocopy; size -= tocopy; FreeSec(diskbuffer); /* MSSeek(fh, tocopy, (long) OFFSET_CURRENT); */ if ((fh->msfh_SeekPos += tocopy) % Disk.bpc == 0) fh->msfh_Cluster = NextCluster(fh->msfh_Cluster); } else { /* Read error. Return amount successfully * read, if any. Else return -1 for error. */ if (size == oldsize) { return -1L; } return oldsize - size; } } return oldsize; } long MSWrite(fh, userbuffer, size) register struct MSFileHandle *fh; register byte *userbuffer; register long size; { #ifdef READONLY return -1; #else long oldsize; struct MSFileLock *fl = fh->msfh_FileLock; word prevclust = fl->msfl_Msd.msd_Cluster; word update = 0; if (CheckLock(fl)) return -1; if (fl->msfl_Msd.msd_Attributes & ATTR_READONLY) { error = ERROR_WRITE_PROTECTED; return -1; } oldsize = size; while (size > 0) { /* * Do we need to extend the file? */ if (fh->msfh_Cluster == 0 || fh->msfh_Cluster == FAT_EOF) { word newclust; newclust = ExtendClusterChain(prevclust); debug(("Extend with %d\n", newclust)); if (newclust != FAT_EOF) { if (prevclust == 0) { /* Record first cluster in dir */ fl->msfl_Msd.msd_Cluster = newclust; } fh->msfh_Cluster = newclust; prevclust = newclust; } else { error = ERROR_DISK_FULL; goto error; } } { word offset; word sector; byte *diskbuffer; long insector; long tocopy; offset = fh->msfh_SeekPos % Disk.bpc; sector = ClusterOffsetToSector(fh->msfh_Cluster, (word) offset); offset %= Disk.bps; insector = Disk.bps - offset; tocopy = lmin(size, insector); if (tocopy == Disk.bps) diskbuffer = EmptySec(sector); else diskbuffer = GetSec(sector); if (diskbuffer != NULL) { CopyMem(userbuffer, diskbuffer + offset, tocopy); userbuffer += tocopy; size -= tocopy; MarkSecDirty(diskbuffer); FreeSec(diskbuffer); /* MSSeek(fh, tocopy, (long) OFFSET_CURRENT); */ if ((fh->msfh_SeekPos += tocopy) % Disk.bpc == 0) fh->msfh_Cluster = NextCluster(fh->msfh_Cluster); if (fh->msfh_SeekPos > fl->msfl_Msd.msd_Filesize) fl->msfl_Msd.msd_Filesize = fh->msfh_SeekPos; fl->msfl_Msd.msd_Attributes |= ATTR_ARCHIVED; update = 1; } else { /* Write error. */ error: if (update) UpdateFileLock(fl); #if 1 return -1; /* We loose the information about how much * data we wrote, but the standard file system * seems to do it this way. */ #else if (size == oldsize) { return -1; } return oldsize - size; /* Amount successfully written */ #endif } } } if (update) UpdateFileLock(fl); return oldsize; #endif } long MSDeleteFile(parentdir, name) struct MSFileLock *parentdir; byte *name; { #ifdef READONLY return DOSFALSE; #else register struct MSFileLock *fl; fl = MSLock(parentdir, name, EXCLUSIVE_LOCK); if (fl) { if (fl->msfl_Msd.msd_Attributes & ATTR_READONLY) { error = ERROR_DELETE_PROTECTED; goto error; } if (fl->msfl_Msd.msd_Attributes & ATTR_DIRECTORY) { struct FileInfoBlock fib; /* * We normally can't get REAL exclusive locks on directories, * so we check here just to be sure. We don't want to delete * anyone's current directory, do we? */ if (fl->msfl_Refcount != 1 || fl == RootLock) { error = ERROR_OBJECT_IN_USE; goto error; } if (MSExamine(fl, &fib) && /* directory itself */ MSExNext(fl, &fib)) { /* should fail */ if (error == 0) { not_empty: error = ERROR_DIRECTORY_NOT_EMPTY; error: MSUnLock(fl); return DOSFALSE; } } if (error != ERROR_NO_MORE_ENTRIES) goto error; error = 0; } if (fl->msfl_Msd.msd_Cluster) FreeClusterChain(fl->msfl_Msd.msd_Cluster); fl->msfl_Msd.msd_Name[0] = DIR_DELETED; WriteFileLock(fl); MSUnLock(fl); return DOSTRUE; } return DOSFALSE; #endif } long MSSetDate(parentdir, name, datestamp) struct MSFileLock *parentdir; byte *name; struct DateStamp *datestamp; { #ifdef READONLY return DOSFALSE; #else register struct MSFileLock *fl; fl = MSLock(parentdir, name, EXCLUSIVE_LOCK); if (fl) { ToMSDate(&fl->msfl_Msd.msd_Date, &fl->msfl_Msd.msd_Time, datestamp); WriteFileLock(fl); MSUnLock(fl); return DOSTRUE; } return DOSFALSE; #endif } /* * Create a new directory, with its own initial "." and ".." entries. */ struct MSFileLock * MSCreateDir(parentdir, name) struct MSFileLock *parentdir; byte *name; { #ifdef READONLY return DOSFALSE; #else register struct MSFileLock *fl; /* * Go create a new file. If we fail later, we have an empty file that * we delete again. */ fl = MSLock(parentdir, name, EXCLUSIVE_LOCK ^ MODE_CREATEFILE); if (fl || error == ERROR_OBJECT_IN_USE) { error = ERROR_OBJECT_EXISTS; goto error; } if (error != 0) { goto error; } if (fl = EmptyFileLock) { debug(("Creating new dir\n")); EmptyFileLock = NULL; if ((fl->msfl_Msd.msd_Cluster = FindFreeCluster(FAT_EOF)) != FAT_EOF) { struct MsDirEntry direntry; byte *sec; word sector; sector = ClusterToSector(fl->msfl_Msd.msd_Cluster); sec = EmptySec(sector); if (sec == NULL) goto error_no_free_store; setmem(sec, (int) Disk.bps, 0); /* * Turn the file into a directory. */ fl->msfl_Msd.msd_Attributes = ATTR_DIRECTORY; UpdateFileLock(fl); /* * Create the "." entry. */ direntry = fl->msfl_Msd; strncpy(direntry.msd_Name, DotDot + 1, 8 + 3); OtherEndianMsd(&direntry); ((struct MsDirEntry *) sec)[0] = direntry; /* * Get the real parent directory because we will duplicate the * directory entry in the subdirectory. */ parentdir = MSParentDir(fl); if (parentdir == NULL) /* Cannot happen */ parentdir = MSDupLock(RootLock); /* * Create the ".." entry. */ direntry = parentdir->msfl_Msd; strncpy(direntry.msd_Name, DotDot, 8 + 3); direntry.msd_Attributes = ATTR_DIRECTORY; OtherEndianMsd(&direntry); ((struct MsDirEntry *) sec)[1] = direntry; MSUnLock(parentdir); MarkSecDirty(sec); FreeSec(sec); /* * Clear out the rest of the newly created directory. */ while ((sector = NextClusteredSector(sector)) != SEC_EOF) { sec = EmptySec(sector); if (sec == NULL) goto error_no_free_store; setmem(sec, (int) Disk.bps, 0); MarkSecDirty(sec); FreeSec(sec); } } else { MSUnLock(fl); fl = NULL; MSDeleteFile(parentdir, name); error = ERROR_DISK_FULL; } } if (EmptyFileLock) { MSUnLock(EmptyFileLock); EmptyFileLock = NULL; } return fl; error_no_free_store: error = ERROR_NO_FREE_STORE; error: if (fl) MSUnLock(fl); return DOSFALSE; #endif } /* * Rename a file or directory, possibly moving it to a different * directory. * * "Tuned" to also work in full directories by first deleting the source * name, then look for a slot to put the destination name. If that fails, * we undo the deletion. By playing with the cache, we even avoid a write * of the sector with the undeleted entry. */ long MSRename(slock, sname, dlock, dname) struct MSFileLock *slock; byte *sname; struct MSFileLock *dlock; byte *dname; { #ifdef READONLY return DOSFALSE; #else struct MSFileLock *sfl; struct MSFileLock *dfl; long success; struct CacheSec *scache; ulong oldstatus; success = DOSFALSE; scache = NULL; dfl = NULL; sfl = MSLock(slock, sname, SHARED_LOCK); if (sfl == NULL || sfl == RootLock) goto error; /* * Now we are going to pull a dirty trick with the cache. We are going * to temporarily delete the source file, in the chache only, and * undelete it again if we cannot create the new name. And above all * we want to avoid unnecessary writes if we decide not to do the * deletion after all. */ { byte *sec; byte old; if ((sec = GetSec(sfl->msfl_DirSector)) == NULL) goto error; scache = FindSecByBuffer(sec); oldstatus = scache->sec_Refcount; old = sfl->msfl_Msd.msd_Name[0]; sfl->msfl_Msd.msd_Name[0] = DIR_DELETED; WriteFileLock(sfl); sfl->msfl_Msd.msd_Name[0] = old; /* * Don't FreeSec it yet; we don't want it written out to disk. */ } /* * Now we have freed the directory entry of the source name, we might * be able to use it for the destination name. But only if we also * temporarily hide the MSFileLock on that spot. Gross hack ahead! */ sfl->msfl_DirOffset = ~sfl->msfl_DirOffset; dfl = MSLock(dlock, dname, EXCLUSIVE_LOCK ^ MODE_CREATEFILE); sfl->msfl_DirOffset = ~sfl->msfl_DirOffset; if (dfl != NULL || error == ERROR_OBJECT_IN_USE) { error = ERROR_OBJECT_EXISTS; goto undelete; } dfl = EmptyFileLock; EmptyFileLock = NULL; if (dfl == NULL) { /* * Sigh, we could not create the new name. But because of that, we * are sure that we need to write nothing to the disk at all. So * we can safely reset the sector-dirty flag to what it was * before, if we also restore the cached sector. */ undelete: WriteFileLock(sfl); scache->sec_Refcount = oldstatus; goto error; } /* * Now, if the moved entry was a directory, and it was moved to a * different directory, we need to adapt its "..", which is the second * entry. */ if (sfl->msfl_Msd.msd_Attributes & ATTR_DIRECTORY && sfl->msfl_Parent != dfl->msfl_Parent) { struct MSFileLock *parentdir; struct MsDirEntry *dir; if (dir = (struct MsDirEntry *) GetSec(DirClusterToSector(sfl->msfl_Msd.msd_Cluster))) { parentdir = MSParentDir(dfl); /* * Copy everything except the name which must remain "..". But * first a quick consistency check... */ debug(("Creating new \"..\" ")); if (dir[1].msd_Name[1] == '.') { CopyMem(&parentdir->msfl_Msd.msd_Attributes, &dir[1].msd_Attributes, (long) sizeof (struct MsDirEntry) - OFFSETOF(MsDirEntry, msd_Attributes)); dir[1].msd_Attributes = ATTR_DIRECTORY; OtherEndianMsd(&dir[1]); MarkSecDirty(dir); } #ifdef DEBUG else debug(("!!! No \"..\" found ??\n")); #endif MSUnLock(parentdir); FreeSec(dir); } } /* * Move the name from the new entry to the old filelock. We do this * for the case that somebody else has a lock on the (possibly moved) * file/directory. Also move the other administration. */ strncpy(sfl->msfl_Msd.msd_Name, dfl->msfl_Msd.msd_Name, 8 + 3); sfl->msfl_DirSector = dfl->msfl_DirSector; sfl->msfl_DirOffset = dfl->msfl_DirOffset; /* * Free the old, and get the new parent directory. They might be the * same, of course... */ MSUnLock(sfl->msfl_Parent); sfl->msfl_Parent = dfl->msfl_Parent; dfl->msfl_Parent = NULL; sfl->msfl_Msd.msd_Attributes &= ~ATTR_ARCHIVED; WriteFileLock(sfl); /* Write the new name; the old name * already has been deleted. */ success = DOSTRUE; error: if (sfl) MSUnLock(sfl); if (dfl) MSUnLock(dfl); if (scache) FreeSec(scache->sec_Data); return success; #endif }