/***************************************************************************** * * FSWRITE.CPP * * Copyright (C) Microsoft Corporation 1990-1995. * All Rights reserved. * ****************************************************************************** * * Module Intent * * File System Manager functions for writing. * *****************************************************************************/ #include "stdafx.h" #include "fspriv.h" #ifdef _DEBUG #include // for tell() #endif #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /***************************************************************************** * * * Defines * * * *****************************************************************************/ #define CDROM_ALIGN 2048 // CDROM alignment block size /***************************************************************************** * * * Prototypes * * * *****************************************************************************/ static int FASTCALL LcbCdRomPadding(int lif, int lcbOffset); static BOOL STDCALL FCloseOrFlushDirtyQrwfo(QRWFO qrwfo, BOOL fClose, int lcbOffset); static int STDCALL LcbGetFree(QFSHR qfshr, QRWFO qrwfo, int lcbOffset); /***************************************************************************\ * * * Private Functions * * * \***************************************************************************/ /***************************************************************************\ * * Function: FFreeBlock( qfshr, lifThis ) * * Purpose: Free the block beginning at lifThis. * * Method: Insert into free list in sorted order. If this block is * adjacent to another free block, merge them. If this block * is at the end of the file, truncate the file. * * ASSUMES * * returns: fTruth or FALSEhood of success * If FALSE is returned, free list could be corrupted * * args IN: qfshr - pointer to file system header dealie * - qfshr->fid is valid (plunged) * lifThis - valid index of nonfree block * * PROMISES * * args OUT: qfshr - free list has a new entry, fModified flag set * * NOTES This function got hacked when I realized that I'd have to * deal with the case where the block being freed is * adjacent to EOF and to the last block on the free list. * Probably this could be done more clearly and cleanly. * \***************************************************************************/ BOOL STDCALL FFreeBlock(QFSHR qfshr, LONG lifThis) { FID fid; FH fh; FREE_HEADER free_header_PrevPrev, free_header_Prev; FREE_HEADER free_header_This, free_header_Next; int lifPrevPrev = lifNil, lifPrev, lifNext; BOOL fWritePrev, fAtEof; if (lifThis < sizeof(FSH) || lifThis + (int) sizeof(FH) > (int) qfshr->fsh.lifEof || LSeekFid(qfshr->fid, lifThis, SEEK_SET) != lifThis || LcbReadFid(qfshr->fid, &fh, sizeof(FH)) != sizeof(FH)) { if (SetFSErrorRc(RcGetIOError()) == RC_Success) SetFSErrorRc(RC_Invalid); return FALSE; } SetFSErrorRc(RC_Failure); fid = qfshr->fid; free_header_This.lcbBlock = fh.lcbBlock; fAtEof = (lifThis + free_header_This.lcbBlock == qfshr->fsh.lifEof); lifPrev = qfshr->fsh.lifFirstFree; if (lifPrev == lifNil || lifThis < lifPrev) { free_header_This.lifNext = lifNext = lifPrev; qfshr->fsh.lifFirstFree = lifThis; fWritePrev = FALSE; } else { if (LSeekFid(fid, lifPrev, SEEK_SET) != lifPrev || LcbReadFid(fid, &free_header_Prev, sizeof(FREE_HEADER)) != sizeof(FREE_HEADER)) { if (RcGetIOError() != RC_Success) SetFSErrorRc(RcGetIOError()); return FALSE; } lifNext = free_header_Prev.lifNext; for (;;) { ASSERT(lifPrev < lifThis); ASSERT(free_header_Prev.lifNext == lifNext); if (lifNext == lifNil || lifThis < lifNext) { free_header_This.lifNext = lifNext; free_header_Prev.lifNext = lifThis; fWritePrev = TRUE; break; } if (fAtEof) { lifPrevPrev = lifPrev; free_header_PrevPrev = free_header_Prev; } lifPrev = lifNext; if (LSeekFid(fid, lifPrev, SEEK_SET) != lifNext || LcbReadFid(fid, &free_header_Prev, sizeof(FREE_HEADER)) != sizeof(FREE_HEADER)) { if (RcGetIOError() != RC_Success) SetFSErrorRc(RcGetIOError()); return FALSE; } lifNext = free_header_Prev.lifNext; } ASSERT(lifNext == lifNil || lifNext > lifThis); ASSERT(lifPrev != lifNil); ASSERT(lifPrev < lifThis); ASSERT(fWritePrev); if (lifPrev + free_header_Prev.lcbBlock == lifThis) { free_header_This.lcbBlock += free_header_Prev.lcbBlock; lifThis = lifPrev; if (fAtEof) { free_header_Prev = free_header_PrevPrev; lifPrev = lifPrevPrev; fWritePrev = (lifPrev != lifNil); } else { fWritePrev = FALSE; } } } if (fAtEof) { if (SetFSErrorRc(RcChSizeFid(fid, lifThis)) != RC_Success) return FALSE; qfshr->fsh.lifEof = lifThis; ASSERT((lifPrev == lifNil) != fWritePrev); if (lifPrev == lifNil) qfshr->fsh.lifFirstFree = lifNil; else free_header_Prev.lifNext = lifNil; } else { if (lifThis + free_header_This.lcbBlock == lifNext) { if (LSeekFid(fid, lifNext, SEEK_SET) != lifNext || LcbReadFid(fid, &free_header_Next, sizeof(FREE_HEADER)) != sizeof(FREE_HEADER)) { if (RcGetIOError() != RC_Success) SetFSErrorRc(RcGetIOError()); return FALSE; } free_header_This.lcbBlock += free_header_Next.lcbBlock; free_header_This.lifNext = free_header_Next.lifNext; } if (LSeekFid(fid, lifThis, SEEK_SET) != lifThis || LcbWriteFid(fid, &free_header_This, sizeof(FREE_HEADER)) != sizeof(FREE_HEADER)) { if (RcGetIOError() != RC_Success) SetFSErrorRc(RcGetIOError()); return FALSE; } } if (fWritePrev) { if (LSeekFid(fid, lifPrev, SEEK_SET) != lifPrev || LcbWriteFid(fid, &free_header_Prev, sizeof(FREE_HEADER)) != sizeof(FREE_HEADER)) { if (RcGetIOError() != RC_Success) SetFSErrorRc(RcGetIOError()); return FALSE; } } qfshr->fsh.bFlags |= FS_DIRTY; SetFSErrorRc(RC_Success); return TRUE; } /***************************************************************************\ * * Function: LcbGetFree( qfshr, qrwfo, lcbOffset ) * * Purpose: Get an adequate block from the free list. * * ASSUMES * * args IN: qfshr - pointer to file system header * qrwfo->lcbFile - (+header) is size we need to allocate * * PROMISES * * returns: actual size of allocated block * * globals OUT: rcFSError * * args OUT: qfshr->lifFirstFree - a block is allocated from free list * ->fModified - set to TRUE * * qrwfo->lifBase - set to new block index * * ALSO: if FS_CDROM is set for the file, we align it on a * (MOD 2K) - 9 byte boundary so the |Topic file blocks are all * properly aligned. * +++ * * Method: First Fit: * Walk the free list. If a block is found that is * big enough, remove it from the free list, plug its * lif into qrwfo, and return the actual size. * If a block isn't found, grow the file and make * a new block starting at the old EOF. * * Bugs: The leftover part of the block isn't left on * the free list. This is the whole point of First Fit. * If aligning for CDROM, the padding part is not * added to the free list. This breaks the FS abstraction * and creates a permanent hole in the FS. This is evil. * \***************************************************************************/ static int STDCALL LcbGetFree(QFSHR qfshr, QRWFO qrwfo, int lcbOffset) { FID fid; FREE_HEADER free_header_this, free_header_prev; int lifPrev, lifThis; int lcb = qrwfo->lcbFile + sizeof(FH); int lcbPadding; // padding for file alignment fid = qfshr->fid; ASSERT(fid != HFILE_ERROR); lifPrev = lifNil; lifThis = qfshr->fsh.lifFirstFree; for (;;) { if (lifThis == lifNil) { // end of free list // cut the new block lifThis = qfshr->fsh.lifEof; if (qrwfo->bFlags & FS_CDROM) lcbPadding = LcbCdRomPadding(lifThis, lcbOffset); else lcbPadding = 0; if (lifThis != LSeekFid(fid, lifThis, SEEK_SET)) goto error_return; // Put the hole in the free list someday?-Tom lifThis += lcbPadding; qfshr->fsh.lifEof += lcb + lcbPadding; if (RcChSizeFid(fid, qfshr->fsh.lifEof) != RC_Success) { qfshr->fsh.lifEof -= lcb + lcbPadding; goto error_return; } break; } else { // get header of this free block if (LSeekFid(fid, lifThis, SEEK_SET) != lifThis) goto error_return; if (LcbReadFid(fid, &free_header_this, (int) sizeof(FREE_HEADER)) != (int) sizeof(FREE_HEADER)) { goto error_return; } // Check for alignment requirements: if (qrwfo->bFlags & FS_CDROM) lcbPadding = LcbCdRomPadding(lifThis, lcbOffset); else lcbPadding = 0; if (lcb + lcbPadding <= free_header_this.lcbBlock) { // this block is big enough: take it /* * Someday break the free block into two (or three): one to * return and the leftover piece(s) left in the free list. */ lcb = free_header_this.lcbBlock; if (lifThis == qfshr->fsh.lifFirstFree) { // lFirst = this->next; qfshr->fsh.lifFirstFree = free_header_this.lifNext; } else { // prev->next = this->next; if (LSeekFid(fid, lifPrev, SEEK_SET) != lifPrev) goto error_return; if (LcbReadFid(fid, &free_header_prev, (int) sizeof(FREE_HEADER)) != (int) sizeof(FREE_HEADER)) goto error_return; free_header_prev.lifNext = free_header_this.lifNext; if (LSeekFid(fid, lifPrev, SEEK_SET) != lifPrev) goto error_return; if (LcbWriteFid(fid, &free_header_prev, (int) sizeof(FREE_HEADER)) != (int) sizeof(FREE_HEADER)) goto error_return; } // add padding at beginning: lifThis += lcbPadding; break; } else { lifPrev = lifThis; lifThis = free_header_this.lifNext; } } } qfshr->fsh.bFlags |= FS_DIRTY; qrwfo->lifBase = lifThis; SetFSErrorRc(RC_Success); return lcb; error_return: if (RcGetIOError() == RC_Success) SetFSErrorRc(RC_Invalid); else SetFSErrorRc(RcGetIOError()); return (int) -1; } /***************************************************************************\ * - Function: LcbCdRomPadding( lif, lcbOffset ) - * Purpose: Returns the number of bytes that must be added to * lif to align the file on a CD block boundary. * This is also the amount of the free block that * should stay a free block. * This allows block structured data to be retrieved * more quickly from a CDROM drive. * * ASSUMES * args IN: lif - offset in bytes of the beginning of the * free block (relative to top of FS) * lcbOffset - align the file this many bytes from the * beginning of the file * * PROMISES * returns: the number of bytes that must be added to lif in * order to align the file * * Notes: Currently doesn't ensure that the padding is big enough * to hold a FREE_HEADER so it can be added to the free list. * That's what the "#if 0"'ed code does. * +++ * * Notes: Should CDROM_ALIGN be a parameter? * \***************************************************************************/ static int FASTCALL LcbCdRomPadding( int lif, int lcbOffset ) { return CDROM_ALIGN - (lif + sizeof(FH) + lcbOffset) % CDROM_ALIGN; #if 0 /* Guarantee the padding block can be added to the free list. */ /* #if'ed out because we don't add it to the free list today. */ int lT = lif + sizeof( FREE_HEADER ) + sizeof( FH ) + lcbOffset; return sizeof( FREE_HEADER ) + CDROM_ALIGN - lT % CDROM_ALIGN; #endif // 0 } /***************************************************************************\ * * Function: RcCopyToTempFile( qrwfo ) * * Purpose: Copy a FS file into a temp file. This is done when the * file needs to be modified. * * ASSUMES * * args IN: qrwfo - specs the open file * * PROMISES * * returns: RC_Success; RC_Failure * \***************************************************************************/ RC_TYPE STDCALL RcCopyToTempFile(QRWFO qrwfo) { QFSHR qfshr = (QFSHR) qrwfo->hfs; ConfirmOrDie(!(qfshr->fsh.bFlags & FS_OPEN_READ_ONLY)); if (!FPlungeQfshr(qfshr)) return rcFSError; qrwfo->bFlags |= FS_DIRTY; qrwfo->pTmpFile = new CTmpFile(); // copy from FS file into temp file if (LSeekFid(qfshr->fid, qrwfo->lifBase, SEEK_SET) != qrwfo->lifBase) return SetFSErrorRc(RcGetIOError()); ASSERT(qrwfo->pTmpFile); if (qrwfo->pTmpFile->copyfromfile(qfshr->fid, qrwfo->lcbFile + sizeof(FH)) != RC_Success) { delete qrwfo->pTmpFile; qrwfo->pTmpFile = NULL; } return rcFSError; } /*************************************************************************** FUNCTION: LcbWriteHf PURPOSE: Writes buffer to a temporary file PARAMETERS: hf qb lcb RETURNS: Returns ONLY if no errors occured, and what we wrote is the same amount as what was requested COMMENTS: MODIFICATION DATES: 01-Mar-1994 [ralphw] ***************************************************************************/ void STDCALL LcbWriteHf(HF hf, void* pvData, int lcb) { int lcbTotalWrote; ASSERT(hf != NULL); QRWFO qrwfo = (QRWFO) hf; ASSERT(!(qrwfo->bFlags & FS_OPEN_READ_ONLY)); if (!(qrwfo->bFlags & FS_DIRTY)) { ASSERT(!qrwfo->pTmpFile); // make sure we have a temp file version // FS permission is checked in RcCopyToTempFile() if (RcCopyToTempFile(qrwfo) != RC_Success) { FatalError: OutErrorRc(rcFSError); HardExit(); // doesn't return } } // position file pointer in temp file if (qrwfo->pTmpFile->seek(sizeof(FH) + qrwfo->lifCurrent, SEEK_SET) != (int) (sizeof(FH) + qrwfo->lifCurrent)) { ForceFSError(); goto FatalError; } lcbTotalWrote = qrwfo->pTmpFile->write(pvData, lcb); if (lcbTotalWrote != lcb) { errHpj.ep = epNoFile; if (!qrwfo->pTmpFile->pszFileName) OOM(); // This shouldn't return VReportError(HCERR_CANNOT_WRITE, &errHpj, qrwfo->pTmpFile->pszFileName); } // update file pointer and file size if (lcbTotalWrote > 0) { qrwfo->lifCurrent += lcbTotalWrote; if (qrwfo->lifCurrent > qrwfo->lcbFile) qrwfo->lcbFile = qrwfo->lifCurrent; } } /*************************************************************************** FUNCTION: LcbWriteIntAsShort PURPOSE: Write a 32-bit integer as a 16-bit value PARAMETERS: hf val RETURNS: COMMENTS: MODIFICATION DATES: 19-Aug-1994 [ralphw] ***************************************************************************/ void STDCALL LcbWriteIntAsShort(HF hf, int val) { INT16 iWriteVal = (INT16) val; LcbWriteHf(hf, &iWriteVal, sizeof(INT16)); } /***************************************************************************\ * * Function: FCloseOrFlushDirtyQrwfo( qrwfo, fClose, lcbOffset ) * * Purpose: flush a dirty open file in a file system * * Method: If the file is dirty, copy the scratch file back to the * FS file. If this is the first time the file has been closed, * we enter the name into the FS directory. If this file is * the FS directory, store the location in a special place * instead. Write the FS directory and header to disk. * Do other various hairy stuff. * * ASSUMES * * args IN: qrwfo - * fClose - TRUE to close file; FALSE to just flush * lcbOffset - offset for CDROM alignment * * PROMISES * * returns: TRUE on success; FALSE for error * * failure: If we fail on a flush, the handle is still valid * but hosed? yes. This is so further file ops will fail but * not assert. * \***************************************************************************/ static BOOL STDCALL FCloseOrFlushDirtyQrwfo(QRWFO qrwfo, BOOL fClose, int lcbOffset) { QFSHR qfshr; FILE_REC fr; FH fh; RC_TYPE rc = RC_Success; BOOL fChangeFH = FALSE; qfshr = (QFSHR) qrwfo->hfs; ASSERT(!(qfshr->fsh.bFlags & FS_OPEN_READ_ONLY)); if (qfshr->fid == HFILE_ERROR && !FPlungeQfshr(qfshr)) return FALSE; ASSERT(qrwfo->pTmpFile); // read the file header if (qrwfo->pTmpFile->seek(0, SEEK_SET) != 0 || qrwfo->pTmpFile->read(&fh, sizeof(FH)) != sizeof(FH)) { ForceFSError(); return FALSE; } if (qrwfo->bFlags & FS_NO_BLOCK) { if ((fh.lcbBlock = LcbGetFree(qfshr, qrwfo, lcbOffset)) == (int) -1) return FALSE; fChangeFH = TRUE; // store file offset for new file if (qrwfo->bFlags & FS_IS_DIRECTORY) qfshr->fsh.lifDirectory = qrwfo->lifBase; else { fr.lifBase = qrwfo->lifBase; rc = RcInsertHbt(qfshr->qbthr, (KEY) qrwfo->rgchKey, &fr); if (rc == RC_Exists) { // oops there is one (someone else created the same file) // lookup directory entry and free old block if (RcLookupByKey(qfshr->qbthr, (KEY) qrwfo->rgchKey, NULL, &fr) != RC_Success) { SetFSErrorRc(RcGetBtreeError()); goto error_freeblock; } if (!FFreeBlock(qfshr, fr.lifBase)) goto error_freeblock; // update directory record to show new block fr.lifBase = qrwfo->lifBase; if (RcUpdateHbt(qfshr->qbthr, (KEY) qrwfo->rgchKey, &fr) != RC_Success) { SetFSErrorRc(RcGetBtreeError()); goto error_freeblock; } } else if (rc != RC_Success) { // some other btree error: handle it SetFSErrorRc(rc); goto error_freeblock; } } } else { // see if file still fits in old block if (qrwfo->lcbFile + (LONG) sizeof(FH) > fh.lcbBlock) { // file doesn't fit in old block: get a new one, free old one int lif = qrwfo->lifBase; if ((fh.lcbBlock = LcbGetFree(qfshr, qrwfo, lcbOffset)) == (int) -1) return FALSE; if (!FFreeBlock(qfshr, lif)) goto error_freeblock; fChangeFH = TRUE; // update directory record to show new block if (qrwfo->bFlags & FS_IS_DIRECTORY) qfshr->fsh.lifDirectory = qrwfo->lifBase; else { fr.lifBase = qrwfo->lifBase; rc = RcUpdateHbt(qfshr->qbthr, (KEY) qrwfo->rgchKey, &fr); if (rc != RC_Success) { SetFSErrorRc(rc); return FALSE; } } } } // put new header in temp file if block or file size changed if (fh.lcbFile != qrwfo->lcbFile) { fChangeFH = TRUE; fh.lcbFile = qrwfo->lcbFile; } if (fChangeFH) { if (qrwfo->pTmpFile->seek(0, SEEK_SET) != 0 || qrwfo->pTmpFile->write(&fh, sizeof(FH)) != sizeof(FH)) { ForceFSError(); goto error_deletekey; } } qrwfo->pTmpFile->seek(0, SEEK_SET); // copy tmp file back to file system file if (LSeekFid(qfshr->fid, qrwfo->lifBase, SEEK_SET) != qrwfo->lifBase) { ForceFSError(); goto error_deletekey; } if (qrwfo->pTmpFile->copytofile(qfshr->fid, qrwfo->lcbFile + sizeof(FH)) != RC_Success) goto error_deletekey; delete qrwfo->pTmpFile; qrwfo->pTmpFile = NULL; /* * Don't flush the FS if this file is the FS directory, because if it * is, we're already closing or flushing it! */ if (!(qrwfo->bFlags & FS_IS_DIRECTORY)) RcCloseOrFlushHfs(qrwfo->hfs, FALSE); return TRUE; // errors here are already cleaned up error_deletekey: if (!(qrwfo->bFlags & FS_IS_DIRECTORY) && fClose) RcDeleteHbt(qfshr->qbthr, (KEY) qrwfo->rgchKey); error_freeblock: if (fClose) { rc = rcFSError; FFreeBlock(qfshr, qrwfo->lifBase); // we don't want to lose an error SetFSErrorRc(rc); } delete qrwfo->pTmpFile; qrwfo->pTmpFile = NULL; return FALSE; } /***************************************************************************\ * - Function: RcCloseOrFlushHfs( hfs, fClose ) - * Purpose: Close or sync the header and directory of an open file system. * * ASSUMES * args IN: hfs - handle to an open file system * fClose - fTrue to close the file system; * FALSE to write through * PROMISES * returns: standard return code * globals OUT:rcFSError * * Note: If closing the FS, all FS files must have been closed or * changes made will be lost. * \***************************************************************************/ RC_TYPE STDCALL RcCloseOrFlushHfs(HFS hfs, BOOL fClose) { ASSERT(hfs != NULL); QFSHR qfshr = (QFSHR) hfs; /* * We don't call FPlungeQfshr() here because if we need to open the * file, it will be opened in the btree call. In fixing a bug (help3.5 * 164) I added this FPlungeQfshr() call, but I now think the bug was due * to inherent FS limitations in dealing with a FS open multiple times. */ if (SetFSErrorRc(RcCloseOrFlushHbt(qfshr->qbthr, fClose)) != RC_Success) { ASSERT(qfshr->fid != HFILE_ERROR); // see comment above // out of disk space, internal error, or out of file handles. if (RC_NoFileHandles != RcGetBtreeError()) { // attempt to invalidate FS by clobbering magic number LSeekFid(qfshr->fid, 0L, SEEK_SET); qfshr->fsh.wMagic = 0; LcbWriteFid(qfshr->fid, &qfshr->fsh, sizeof(FSH)); } } else { if (qfshr->fsh.bFlags & FS_DIRTY) { ASSERT(qfshr->fid != HFILE_ERROR); // see comment above ASSERT(!(qfshr->fsh.bFlags & (FS_OPEN_READ_ONLY | FS_READ_ONLY))); // save the directory flag, clear before writing, and restore BOOL fIsDir = qfshr->fsh.bFlags & FS_IS_DIRECTORY; qfshr->fsh.bFlags &= ~(FS_DIRTY | FS_IS_DIRECTORY); // write out file system header if (LSeekFid(qfshr->fid, 0, SEEK_SET) != 0 || LcbWriteFid(qfshr->fid, &qfshr->fsh, sizeof(FSH)) != sizeof(FSH)) ForceFSError(); qfshr->fsh.bFlags |= fIsDir; /* * REVIEW: should we keep track of open files and make sure they * are all closed, or close them here? */ } } if (fClose) { if (qfshr->fid != HFILE_ERROR) { if (hfs == hfsOut) cbHlpFile = GetFileSize((HANDLE) qfshr->fid, NULL); RcCloseFid(qfshr->fid); if (rcFSError == RC_Success) rcFSError = RcGetIOError(); } DisposeFm(qfshr->fm); lcFree(hfs); } return rcFSError; } /***************************************************************************\ * - Function: HfOpenHfs( hfs, sz, bFlags ) - * Purpose: open a file in a file system * * ASSUMES * args IN: hfs - handle to file system * sz - name (key) of file to open * bFlags - FS_OPEN_READ_ONLY, FS_IS_DIRECTORY, or combination * * PROMISES * returns: handle to open file or NULL on failure * +++ * * Notes: strlen( NULL ) and strcpy( s, NULL ) don't work as I'd like. * \***************************************************************************/ QRWFO STDCALL HfOpenHfs(QFSHR qfshr, PCSTR pszKey, DWORD bFlags) { FILE_REC fr; FH fh; if ((qfshr->fsh.bFlags & FS_OPEN_READ_ONLY) && !(bFlags & FS_OPEN_READ_ONLY)) { SetFSErrorRc(RC_NoPermission); return NULL; } if (!FPlungeQfshr(qfshr)) return NULL; if (bFlags & FS_IS_DIRECTORY) { // check if directory is already open if (qfshr->fsh.bFlags & FS_IS_DIRECTORY) { SetFSErrorRc(RC_BadArg); return NULL; } qfshr->fsh.bFlags |= FS_IS_DIRECTORY; fr.lifBase = qfshr->fsh.lifDirectory; } else if (SetFSErrorRc(RcLookupByKey(qfshr->qbthr, (KEY) pszKey, NULL, &fr)) != RC_Success) return NULL; // sanity check if (fr.lifBase < sizeof(FSH) || fr.lifBase > qfshr->fsh.lifEof) { SetFSErrorRc(RC_Invalid); return NULL; } // read the file header if (LSeekFid(qfshr->fid, fr.lifBase, SEEK_SET) != fr.lifBase || LcbReadFid(qfshr->fid, &fh, sizeof(FH)) != sizeof(FH)) { ForceFSError(); return NULL; } // sanity check if (fh.lcbFile < 0 || fh.lcbFile + (int) sizeof(FH) > (int) fh.lcbBlock || fr.lifBase + fh.lcbBlock > qfshr->fsh.lifEof) { SetFSErrorRc(RC_Invalid); return NULL; } // check mode against fh.bPerms for legality if ((fh.bPerms & FS_READ_ONLY) && !(bFlags & FS_OPEN_READ_ONLY)) { SetFSErrorRc(RC_NoPermission); return NULL; } // build file struct QRWFO qrwfo = (QRWFO) lcCalloc(sizeof(RWFO) + (pszKey == NULL ? 0 : strlen(pszKey))); qrwfo->hfs = (HFS) qfshr; qrwfo->lifBase = fr.lifBase; qrwfo->lifCurrent = 0; qrwfo->lcbFile = fh.lcbFile; qrwfo->bFlags = bFlags & (~(FS_DIRTY | FS_NO_BLOCK)); if (pszKey != NULL) strcpy(qrwfo->rgchKey, pszKey); SetFSErrorRc(RC_Success); return qrwfo; } /***************************************************************************\ * - Function: RcCloseOrFlushHf( hf, fClose, lcbOffset ) - * Purpose: close or flush an open file in a file system * * ASSUMES * args IN: hf - file handle * fClose - fTrue to close; FALSE to just flush * lcbOffset - offset for CDROM alignment (align at this * offset into the file) (only used if * FS_CDROM flag is set for the file) * * PROMISES * returns: RC_Success on successful closing * failure: If we fail on a flush, the handle is still valid * but hosed? yes. This is so further file ops will fail but * not assert. * +++ * * Method: If the file is dirty, copy the scratch file back to the * FS file. If this is the first time the file has been closed, * we enter the name into the FS directory. If this file is * the FS directory, store the location in a special place * instead. Write the FS directory and header to disk. * Do other various hairy stuff. * \***************************************************************************/ RC_TYPE STDCALL RcCloseOrFlushHf(HF hf, BOOL fClose, int lcbOffset) { QRWFO qrwfo; BOOL fError = FALSE; ASSERT(hf); qrwfo = (QRWFO) hf; if (qrwfo->bFlags & FS_DIRTY) fError = !FCloseOrFlushDirtyQrwfo(qrwfo, fClose, lcbOffset); else SetFSErrorRc(RC_Success); if (fClose || fError) lcFree(hf); else qrwfo->bFlags &= ~(FS_NO_BLOCK | FS_DIRTY); return rcFSError; } /***************************************************************************\ * - Function: HfsOpenFm( fm, bFlags ) - * Purpose: Open a file system * * ASSUMES * args IN: fm - descriptor of file system to open * bFlags - FS_OPEN_READ_ONLY or FS_OPEN_READ_WRITE * * PROMISES * returns: handle to file system if opened OK, else NULL * * Bugs: don't have mode now (a file system is opened r/w) * \***************************************************************************/ HFS STDCALL HfsOpenFm(FM fm, BYTE bFlags) { QFSHR qfshr; int lcb; // make header qfshr = (QFSHR) lcCalloc(sizeof(FSHR)); qfshr->fm = FmCopyFm(fm); qfshr->fid = HFILE_ERROR; if (qfshr->fm == fmNil) { SetFSErrorRc(RcGetIOError()); goto error_return; } qfshr->fsh.bFlags = (BYTE) ((bFlags & FS_OPEN_READ_ONLY) ? FS_OPEN_READ_ONLY : FS_OPEN_READ_WRITE); if (!FPlungeQfshr(qfshr)) goto error_return; lcb = LcbReadFid(qfshr->fid, &qfshr->fsh, sizeof(FSH)); // restore the FS_OPEN_READ_ONLY bit if (bFlags & FS_OPEN_READ_ONLY) qfshr->fsh.bFlags |= FS_OPEN_READ_ONLY; if (lcb != sizeof(FSH) || qfshr->fsh.wMagic != wFileSysMagic || qfshr->fsh.lifDirectory < sizeof(FSH) || qfshr->fsh.lifDirectory > qfshr->fsh.lifEof || (qfshr->fsh.lifFirstFree < sizeof(FSH) && qfshr->fsh.lifFirstFree != lifNil) || qfshr->fsh.lifFirstFree > qfshr->fsh.lifEof) { if (RcGetIOError() == RC_Success) SetFSErrorRc(RC_Invalid); else SetFSErrorRc(RcGetIOError()); goto error_return; } if (qfshr->fsh.bVersion != FILESYSVERSION) { SetFSErrorRc(RC_BadVersion); goto error_return; } // open btree directory qfshr->qbthr = HbtOpenBtreeSz(NULL, (HFS) qfshr, (BYTE) (qfshr->fsh.bFlags | FS_IS_DIRECTORY)); if (!qfshr->qbthr) { SetFSErrorRc(RcGetBtreeError()); goto error_return; } SetFSErrorRc(RC_Success); return (HFS) qfshr; error_return: if (qfshr->fid != HFILE_ERROR) { RcCloseFid(qfshr->fid); qfshr->fid = HFILE_ERROR; } if (FValidFm(qfshr->fm)) DisposeFm(qfshr->fm); lcFree(qfshr); return NULL; }