/* Copyright (c) Microsoft Corporation. Licensed under the MIT License. */ /*************************************************************************** Author: ShonK Project: Kauai Reviewed: Copyright (c) Microsoft Corporation File name management. ***************************************************************************/ #include "util.h" #include ASSERTNAME // This is the FTG to use for temp files - clients may set this to whatever // they want. FTG vftgTemp = kftgTemp; typedef OFSTRUCT OFS; typedef OPENFILENAME OFN; // maximal number of short characters in an extension is 4 (so it fits in // a long). const long kcchsMaxExt = size(long); priv void _CleanFtg(FTG *pftg, PSTN pstnExt = pvNil); FNI _fniTemp; RTCLASS(FNI) RTCLASS(FNE) /*************************************************************************** Sets the fni to nil values. ***************************************************************************/ void FNI::SetNil(void) { _ftg = ftgNil; _stnFile.SetNil(); AssertThis(ffniEmpty); } /*************************************************************************** Constructor for fni class. ***************************************************************************/ FNI::FNI(void) { SetNil(); } /*************************************************************************** Get an fni (for opening) from the user. ***************************************************************************/ bool FNI::FGetOpen(achar *prgchFilter, HWND hwndOwner) { AssertThis(0); AssertNilOrVarMem(prgchFilter); OFN ofn; SZ sz; ClearPb(&ofn, size(OFN)); SetNil(); sz[0] = 0; ofn.lStructSize = size(OFN); ofn.hwndOwner = hwndOwner; ofn.hInstance = NULL; ofn.lpstrFilter = prgchFilter; ofn.nFilterIndex = 1L; ofn.lpstrFile = sz; ofn.nMaxFile = kcchMaxSz; ofn.lpstrFileTitle = NULL; ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOTESTFILECREATE | OFN_READONLY; if (!GetOpenFileName(&ofn)) { SetNil(); return fFalse; } _stnFile = ofn.lpstrFile; _SetFtgFromName(); AssertThis(ffniFile); return fTrue; } /*************************************************************************** Get an fni (for saving) from the user. ***************************************************************************/ bool FNI::FGetSave(achar *prgchFilter, HWND hwndOwner) { AssertThis(0); AssertNilOrVarMem(prgchFilter); OFN ofn; SZ sz; ClearPb(&ofn, size(OFN)); SetNil(); sz[0] = 0; ofn.lStructSize = size(OFN); ofn.hwndOwner = hwndOwner; ofn.hInstance = NULL; ofn.lpstrFilter = prgchFilter; ofn.nFilterIndex = 1L; ofn.lpstrFile = sz; ofn.nMaxFile = kcchMaxSz; ofn.lpstrFileTitle = NULL; ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; ofn.lpstrDefExt = PszLit(""); if (!GetSaveFileName(&ofn)) { SetNil(); return fFalse; } _stnFile = sz; _SetFtgFromName(); AssertThis(ffniFile); return fTrue; } /*************************************************************************** Builds the fni from the path. ***************************************************************************/ bool FNI::FBuildFromPath(PSTN pstn, FTG ftgDef) { AssertThis(0); AssertPo(pstn, 0); long cch; achar *pchT; SZ sz; if (kftgDir != ftgDef) { // if the path ends with a slash or only has periods after the last // slash, force the fni to be a directory. cch = pstn->Cch(); for (pchT = pstn->Prgch() + cch - 1; ; pchT--) { if (cch-- <= 0 || *pchT == ChLit('\\') || *pchT == ChLit('/')) { ftgDef = kftgDir; break; } if (*pchT != ChLit('.')) break; } } /* ask windows to parse the file name (resolves ".." and ".") and returns absolute filename "X:\FOO\BAR", relative to the current drive and directory if no drive or directory is given in pstn */ if ((cch = GetFullPathName(pstn->Psz(), kcchMaxSz, sz, &pchT)) == 0 || cch > kcchMaxSz) { goto LFail; } Assert(cch <= kcchMaxSz, 0); _stnFile = sz; if (ftgDef == kftgDir) { achar ch = _stnFile.Prgch()[_stnFile.Cch() - 1]; if (ch != ChLit('\\') && ch != ChLit('/')) { if (!_stnFile.FAppendCh(ChLit('\\'))) { goto LFail; } } _ftg = kftgDir; } else { _SetFtgFromName(); if (_ftg == 0 && ftgDef != ftgNil && pstn->Prgch()[pstn->Cch() - 1] != ChLit('.') && !FChangeFtg(ftgDef)) { LFail: SetNil(); PushErc(ercFniGeneral); return fFalse; } } AssertThis(ffniFile | ffniDir); return fTrue; } /****************************************************************************** Will attempt to build an FNI with the given filename. Uses the Windows SearchPath API, and thus the Windows path*search rules. Arguments: PSTN pstn ** the filename to look for Returns: fTrue if it could find the file ******************************************************************************/ bool FNI::FSearchInPath(PSTN pstn, PSTN pstnEnv) { AssertThis(0); AssertPo(pstn, 0); AssertNilOrPo(pstnEnv, 0); long cch; SZ sz; achar *pchT; PSZ psz = (pstnEnv == pvNil) ? pvNil : pstnEnv->Psz(); if ((cch = SearchPath(psz, pstn->Psz(), pvNil, kcchMaxSz, sz, &pchT)) == 0 || cch > kcchMaxSz) { SetNil(); PushErc(ercFniGeneral); return fFalse; } Assert(cch <= kcchMaxSz, 0); _stnFile = sz; _SetFtgFromName(); AssertThis(ffniFile | ffniDir); return fTrue; } /*************************************************************************** Get a unique filename in the directory currently indicated by the fni. ***************************************************************************/ bool FNI::FGetUnique(FTG ftg) { AssertThis(ffniFile | ffniDir); static short _dsw = 0; STN stn; STN stnOld; short sw; long cact; if (Ftg() == kftgDir) stnOld.SetNil(); else GetLeaf(&stnOld); sw = (short)TsCurrentSystem() + ++_dsw; for (cact = 20; cact != 0; cact--, sw += ++_dsw) { stn.FFormatSz(PszLit("Temp%04x"), (long)sw); if (stn.FEqual(&stnOld)) continue; if (FSetLeaf(&stn, ftg) && TExists() == tNo) return fTrue; } SetNil(); PushErc(ercFniGeneral); return fFalse; } /*************************************************************************** Get a temporary fni. ***************************************************************************/ bool FNI::FGetTemp(void) { AssertThis(0); if (_fniTemp._ftg != kftgDir) { // get the temp directory SZ sz; if (GetTempPath(kcchMaxSz, sz) == 0) { PushErc(ercFniGeneral); return fFalse; } _fniTemp._stnFile = sz; _fniTemp._ftg = kftgDir; AssertPo(&_fniTemp, ffniDir); } *this = _fniTemp; return FGetUnique(vftgTemp); } /*************************************************************************** Return the file type of the fni. ***************************************************************************/ FTG FNI::Ftg(void) { AssertThis(0); return _ftg; } /*************************************************************************** Return the volume kind for the given fni. ***************************************************************************/ ulong FNI::Grfvk(void) { AssertThis(ffniDir | ffniFile); STN stn; PSZ psz; ulong grfvk = fvkNil; psz = _stnFile.Psz(); if (_stnFile.Cch() < 3 || psz[1] != ':' || psz[2] != '\\' && psz[2] != '/') return fvkNetwork; stn.FFormatSz(PszLit("%c:\\"), psz[0]); switch (GetDriveType(stn.Psz())) { case DRIVE_FIXED: case DRIVE_RAMDISK: break; case DRIVE_REMOVABLE: grfvk |= fvkRemovable; switch (stn.Psz()[0]) { case ChLit('A'): case ChLit('B'): case ChLit('a'): case ChLit('b'): grfvk |= fvkFloppy; break; } break; case DRIVE_CDROM: grfvk |= fvkRemovable | fvkCD; break; case DRIVE_REMOTE: default: // treat anything else like a network drive grfvk |= fvkNetwork; break; } return grfvk; } /*************************************************************************** Set the leaf to the given string and type. ***************************************************************************/ bool FNI::FSetLeaf(PSTN pstn, FTG ftg) { AssertThis(ffniFile | ffniDir); AssertNilOrPo(pstn, 0); _CleanFtg(&ftg); Assert(FPure(ftg == kftgDir) == FPure(pstn == pvNil || pstn->Cch() == 0), "ftg doesn't match pstn"); if (!_FChangeLeaf(pstn)) goto LFail; if ((kftgDir != ftg) && (ftgNil != ftg) && !FChangeFtg(ftg)) goto LFail; AssertThis(ffniFile | ffniDir); return fTrue; LFail: SetNil(); PushErc(ercFniGeneral); return fFalse; } /****************************************************************************** Changes just the FTG of the FNI, leaving the file path and filename alone (but does change the extension). Returns: fTrue if it succeeds ******************************************************************************/ bool FNI::FChangeFtg(FTG ftg) { AssertThis(ffniFile); Assert(ftg != ftgNil && ftg != kftgDir, "Bad FTG"); STN stnFtg; long cchBase; _CleanFtg(&ftg, &stnFtg); if (_ftg == ftg) return fTrue; // set the extension cchBase = _stnFile.Cch() - _CchExt(); //use >= to leave room for the '.' if (cchBase + stnFtg.Cch() >= kcchMaxStn) return fFalse; _stnFile.Delete(cchBase); _ftg = ftg; if (stnFtg.Cch() > 0) { _stnFile.FAppendCh(ChLit('.')); _stnFile.FAppendStn(&stnFtg); } return fTrue; } /*************************************************************************** Get the leaf name. ***************************************************************************/ void FNI::GetLeaf(PSTN pstn) { AssertThis(0); AssertPo(pstn, 0); achar *pch; PSZ psz = _stnFile.Psz(); for (pch = psz + _stnFile.Cch(); pch-- > psz && *pch != '\\' && *pch != '/'; ) { } Assert(pch > psz, "bad fni"); pstn->SetSz(pch + 1); } /*************************************************************************** Get a string representing the path of the fni. ***************************************************************************/ void FNI::GetStnPath(PSTN pstn) { AssertThis(0); AssertPo(pstn, 0); *pstn = _stnFile; } /*************************************************************************** Determines if the file/directory exists. Returns tMaybe on error or if the fni type (file or dir) doesn't match the disk object of the same name. ***************************************************************************/ bool FNI::TExists(void) { AssertThis(ffniFile | ffniDir); STN stn; PSTN pstn; ulong lu; // strip off the trailing slash (if a directory). pstn = &_stnFile; if (_ftg == kftgDir) { long cch; stn = _stnFile; pstn = &stn; cch = stn.Cch(); Assert(cch > 0 && (stn.Psz()[cch - 1] == '\\' || stn.Psz()[cch - 1] == '/'), 0); stn.Delete(cch - 1); } if (0xFFFFFFFF == (lu = GetFileAttributes(pstn->Psz()))) { /* Any of these are equivalent to "there's no file with that name" */ if ((lu = GetLastError()) == ERROR_FILE_NOT_FOUND || lu == ERROR_INVALID_DRIVE) { return tNo; } PushErc(ercFniGeneral); return tMaybe; } if ((_ftg == kftgDir) != FPure(lu & FILE_ATTRIBUTE_DIRECTORY)) { PushErc(ercFniMismatch); return tMaybe; } if (lu & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) { PushErc(ercFniHidden); return tMaybe; } return tYes; } /*************************************************************************** Delete the physical file. Should not be open. ***************************************************************************/ bool FNI::FDelete(void) { AssertThis(ffniFile); Assert(FIL::PfilFromFni(this) == pvNil, "file is open"); if (DeleteFile(_stnFile.Psz())) return fTrue; PushErc(ercFniDelete); return fFalse; } /*************************************************************************** Renames the file indicated by this to *pfni. ***************************************************************************/ bool FNI::FRename(FNI *pfni) { AssertThis(ffniFile); AssertPo(pfni, ffniFile); if (!(FILE_ATTRIBUTE_READONLY & GetFileAttributes(_stnFile.Psz())) && MoveFile(_stnFile.Psz(), pfni->_stnFile.Psz())) { return fTrue; } PushErc(ercFniRename); return fFalse; } /*************************************************************************** Compare two fni's for equality. ***************************************************************************/ bool FNI::FEqual(FNI *pfni) { AssertThis(ffniFile | ffniDir); AssertPo(pfni, ffniFile | ffniDir); return pfni->_stnFile.FEqualUser(&_stnFile); } /*************************************************************************** Return whether the fni refers to a directory. ***************************************************************************/ bool FNI::FDir(void) { AssertThis(0); return _ftg == kftgDir; } /*************************************************************************** Return whether the directory portions of the fni's are the same. ***************************************************************************/ bool FNI::FSameDir(FNI *pfni) { AssertThis(ffniFile | ffniDir); AssertPo(pfni, ffniFile | ffniDir); FNI fni1, fni2; fni1 = *this; fni2 = *pfni; fni1._FChangeLeaf(pvNil); fni2._FChangeLeaf(pvNil); return fni1.FEqual(&fni2); } /*************************************************************************** Determine if the directory pstn in fni exists, optionally creating it and/or moving into it. Specify ffniCreateDir to create it if it doesn't exist. Specify ffniMoveTo to make the fni refer to it. ***************************************************************************/ bool FNI::FDownDir(PSTN pstn, ulong grffni) { AssertThis(ffniDir); AssertPo(pstn, 0); FNI fniT; fniT = *this; //the +1 is for the \ character if (fniT._stnFile.Cch() + pstn->Cch() + 1 > kcchMaxStn) { PushErc(ercFniGeneral); return fFalse; } AssertDo(fniT._stnFile.FAppendStn(pstn), 0); AssertDo(fniT._stnFile.FAppendCh(ChLit('\\')), 0); fniT._ftg = kftgDir; AssertPo(&fniT, ffniDir); if (fniT.TExists() != tYes) { if (!(grffni & ffniCreateDir)) return fFalse; // try to create it if (!CreateDirectory(fniT._stnFile.Psz(), NULL)) { PushErc(ercFniDirCreate); return fFalse; } } if (grffni & ffniMoveToDir) *this = fniT; return fTrue; } /*************************************************************************** Gets the lowest directory name (if pstn is not nil) and optionally moves the fni up a level (if ffniMoveToDir is specified). ***************************************************************************/ bool FNI::FUpDir(PSTN pstn, ulong grffni) { AssertThis(ffniDir); AssertNilOrPo(pstn, 0); long cch; achar *pchT; SZ sz; STN stn; stn = _stnFile; if (!stn.FAppendSz(PszLit(".."))) return fFalse; if ((cch = GetFullPathName(stn.Psz(), kcchMaxSz, sz, &pchT)) == 0 || cch >= _stnFile.Cch() - 1) { return fFalse; } Assert(cch <= kcchMaxSz, 0); Assert(cch < _stnFile.Cch() + 2, 0); stn = sz; switch (stn.Psz()[cch - 1]) { case ChLit('\\'): case ChLit('/'): break; default: AssertDo(stn.FAppendCh(ChLit('\\')), 0); cch++; break; } if (pvNil != pstn) { // copy the tail and delete the trailing slash pstn->SetSz(_stnFile.Psz() + cch); pstn->Delete(pstn->Cch() - 1); } if (grffni & ffniMoveToDir) { _stnFile = stn; AssertThis(ffniDir); } return fTrue; } #ifdef DEBUG /*************************************************************************** Assert validity of the FNI. ***************************************************************************/ void FNI::AssertValid(ulong grffni) { FNI_PAR::AssertValid(0); AssertPo(&_stnFile, 0); SZ szT; long cch; PSZ pszT; if (grffni == 0) grffni = ffniEmpty | ffniDir | ffniFile; if (_ftg == ftgNil) { Assert(grffni & ffniEmpty, "unexpected empty"); Assert(_stnFile.Cch() == 0, "named empty?"); return; } if ((cch = GetFullPathName(_stnFile.Psz(), kcchMaxSz, szT, &pszT)) == 0 || cch > kcchMaxSz || !_stnFile.FEqualUserRgch(szT, CchSz(szT))) { Bug("bad fni"); return; } if (_ftg == kftgDir) { Assert(grffni & ffniDir, "unexpected dir"); Assert(szT[cch - 1] == ChLit('\\') || szT[cch - 1] == ChLit('/'), "expected trailing slash"); Assert(pszT == NULL, "unexpected filename"); } else { Assert(grffni & ffniFile, "unexpected file"); Assert(pszT >= szT && pszT < szT + cch, "expected filename"); } } #endif //DEBUG /*************************************************************************** Find the length of the file extension on the fni (including the period). Allow up to kcchsMaxExt characters for the extension (plus one for the period). ***************************************************************************/ long FNI::_CchExt(void) { AssertBaseThis(0); long cch; PSZ psz = _stnFile.Psz(); achar *pch = psz + _stnFile.Cch() - 1; for (cch = 1; cch <= kcchsMaxExt + 1 && pch >= psz; cch++, pch--) { if ((achar)(schar)*pch != *pch) { // not an ANSI character - so doesn't qualify for our // definition of an extension return 0; } switch (*pch) { case ChLit('.'): return cch; case ChLit('\\'): case ChLit('/'): return 0; } } return 0; } /*************************************************************************** Set the ftg from the file name. ***************************************************************************/ void FNI::_SetFtgFromName(void) { AssertBaseThis(0); Assert(_stnFile.Cch() > 0, 0); long cch, ich; achar *pchLim = _stnFile.Psz() + _stnFile.Cch(); if (pchLim[-1] == ChLit('\\') || pchLim[-1] == ChLit('/')) _ftg = kftgDir; else { _ftg = 0; cch = _CchExt() - 1; AssertIn(cch, -1, kcchsMaxExt + 1); pchLim -= cch; for (ich = 0; ich < cch; ich++) _ftg = (_ftg << 8) | (long)(byte)ChsUpper((schar)pchLim[ich]); } AssertThis(ffniFile | ffniDir); } /*************************************************************************** Change the leaf of the fni. ***************************************************************************/ bool FNI::_FChangeLeaf(PSTN pstn) { AssertThis(ffniFile | ffniDir); AssertNilOrPo(pstn, 0); achar *pch; PSZ psz; long cchBase, cch; psz = _stnFile.Psz(); for (pch = psz + _stnFile.Cch(); pch-- > psz && *pch != ChLit('\\') && *pch != ChLit('/'); ) { } Assert(pch > psz, "bad fni"); cchBase = pch - psz + 1; _stnFile.Delete(cchBase); _ftg = kftgDir; if (pstn != pvNil && (cch = pstn->Cch()) > 0) { if (cchBase + cch > kcchMaxStn) return fFalse; AssertDo(_stnFile.FAppendStn(pstn), 0); _SetFtgFromName(); } AssertThis(ffniFile | ffniDir); return fTrue; } /*************************************************************************** Make sure the ftg is all uppercase and has no characters after a zero. ***************************************************************************/ priv void _CleanFtg(FTG *pftg, PSTN pstnExt) { AssertVarMem(pftg); AssertNilOrPo(pstnExt, 0); long ichs; schar chs; bool fZero; FTG ftgNew; if (pvNil != pstnExt) pstnExt->SetNil(); if (*pftg == kftgDir || *pftg == ftgNil) return; fZero = fFalse; ftgNew = 0; for (ichs = 0; ichs < kcchsMaxExt; ichs++) { chs = (schar)((ulong)*pftg >> (ichs * 8)); fZero |= (chs == 0); if (!fZero) { chs = ChsUpper(chs); ftgNew |= (long)(byte)chs << (8 * ichs); if (pvNil != pstnExt) pstnExt->FInsertCh(0, (achar)(byte)chs); } } *pftg = ftgNew; } /*************************************************************************** Constructor for a File Name Enumerator. ***************************************************************************/ FNE::FNE(void) { AssertBaseThis(0); _prgftg = _rgftg; _pglfes = pvNil; _fesCur.hn = hBadWin; _fInited = fFalse; AssertThis(0); } /*************************************************************************** Destructor for an FNE. ***************************************************************************/ FNE::~FNE(void) { AssertBaseThis(0); _Free(); } /*************************************************************************** Free all the memory associated with the FNE. ***************************************************************************/ void FNE::_Free(void) { if (_prgftg != _rgftg) { FreePpv((void **)&_prgftg); _prgftg = _rgftg; } do { if (hBadWin != _fesCur.hn) FindClose(_fesCur.hn); } while (pvNil != _pglfes && _pglfes->FPop(&_fesCur)); _fesCur.hn = hBadWin; _fInited = fFalse; ReleasePpo(&_pglfes); AssertThis(0); } /*************************************************************************** Initialize the fne to do an enumeration. ***************************************************************************/ bool FNE::FInit(FNI *pfniDir, FTG *prgftg, long cftg, ulong grffne) { AssertThis(0); AssertNilOrVarMem(pfniDir); AssertIn(cftg, 0, kcbMax); AssertPvCb(prgftg, LwMul(cftg, size(FTG))); FTG *pftg; //free the old stuff _Free(); if (0 >= cftg) _cftg = 0; else { long cb = LwMul(cftg, size(FTG)); if (cftg > kcftgFneBase && !FAllocPv((void **)&_prgftg, cb, fmemNil, mprNormal)) { _prgftg = _rgftg; PushErc(ercFneGeneral); AssertThis(0); return fFalse; } CopyPb(prgftg, _prgftg, cb); _cftg = cftg; for (pftg = _prgftg + _cftg; pftg-- > _prgftg; ) _CleanFtg(pftg); } if (pfniDir == pvNil) { _fesCur.chVol = 'A'; _fesCur.grfvol = GetLogicalDrives(); } else { STN stn; _fesCur.grfvol = 0; _fesCur.chVol = 0; _fesCur.fni = *pfniDir; stn = PszLit("*"); if (!_fesCur.fni._FChangeLeaf(&stn)) { PushErc(ercFneGeneral); _Free(); AssertThis(0); return fFalse; } } _fesCur.hn = hBadWin; _fRecurse = FPure(grffne & ffneRecurse); _fInited = fTrue; AssertThis(0); return fTrue; } /*************************************************************************** Get the next FNI in the enumeration. ***************************************************************************/ bool FNE::FNextFni(FNI *pfni, ulong *pgrffneOut, ulong grffneIn) { AssertThis(0); AssertVarMem(pfni); AssertNilOrVarMem(pgrffneOut); STN stn; bool fT; long fvol; long err; FTG *pftg; if (!_fInited) { Bug("must initialize the FNE before using it!"); return fFalse; } if (grffneIn & ffneSkipDir) { //skip the rest of the stuff in this dir if (!_FPop()) goto LDone; } if (_fesCur.chVol != 0) { //volume for (fvol = 1L << (_fesCur.chVol - 'A'); _fesCur.chVol <= 'Z' && (_fesCur.grfvol & fvol) == 0; _fesCur.chVol++, fvol <<= 1) { } if (_fesCur.chVol > 'Z') goto LDone; //we've got one stn.FFormatSz(PszLit("%c:\\"), (long)_fesCur.chVol++); AssertDo(pfni->FBuildFromPath(&stn), 0); goto LGotOne; } // directory or file for (;;) { if (hBadWin == _fesCur.hn) { _fesCur.hn = FindFirstFile(_fesCur.fni._stnFile.Psz(), &_fesCur.wfd); if (hBadWin == _fesCur.hn) { err = GetLastError(); goto LReportError; } } else if (!FindNextFile(_fesCur.hn, &_fesCur.wfd)) { err = GetLastError(); LReportError: if (err != ERROR_NO_MORE_FILES) PushErc(ercFneGeneral); goto LPop; } if (_fesCur.wfd.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) continue; stn.SetSz(_fesCur.wfd.cFileName); *pfni = _fesCur.fni; if (_fesCur.wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (stn.FEqualSz(PszLit(".")) || stn.FEqualSz(PszLit(".."))) continue; AssertDo(pfni->_FChangeLeaf(pvNil), 0); fT = pfni->FDownDir(&stn, ffniMoveToDir); } else fT = pfni->_FChangeLeaf(&stn); if (!fT) { PushErc(ercFneGeneral); continue; } if (_cftg == 0) goto LGotOne; for (pftg = _prgftg + _cftg; pftg-- > _prgftg; ) { if (*pftg == pfni->_ftg) goto LGotOne; } } Bug("How did we fall through to here?"); LPop: if (pvNil == _pglfes || _pglfes->IvMac() == 0) { LDone: _Free(); AssertThis(0); return fFalse; } //we're about to pop a directory, so send the current directory back //with ffnePost if (pvNil != pgrffneOut) *pgrffneOut = ffnePost; *pfni = _fesCur.fni; AssertDo(pfni->_FChangeLeaf(pvNil), 0); AssertDo(_FPop(), 0); AssertPo(pfni, ffniDir); AssertThis(0); return fTrue; LGotOne: AssertPo(pfni, ffniFile | ffniDir); if (pvNil != pgrffneOut) *pgrffneOut = ffnePre | ffnePost; if (_fRecurse && pfni->_ftg == kftgDir) { if ((pvNil != _pglfes || pvNil != (_pglfes = GL::PglNew(size(FES), 5))) && _pglfes->FPush(&_fesCur)) { //set up the new fes _fesCur.fni = *pfni; stn = PszLit("*"); if (!_fesCur.fni._FChangeLeaf(&stn)) { AssertDo(_pglfes->FPop(&_fesCur), 0); } else { _fesCur.hn = hBadWin; _fesCur.grfvol = 0; _fesCur.chVol = 0; if (pvNil != pgrffneOut) *pgrffneOut = ffnePre; } } else PushErc(ercFneGeneral); } AssertThis(0); return fTrue; } /*************************************************************************** Pop a state in the FNE. ***************************************************************************/ bool FNE::_FPop(void) { AssertBaseThis(0); if (hBadWin != _fesCur.hn) { FindClose(_fesCur.hn); _fesCur.hn = hBadWin; } return pvNil != _pglfes && _pglfes->FPop(&_fesCur); } #ifdef DEBUG /*************************************************************************** Assert the validity of a FNE. ***************************************************************************/ void FNE::AssertValid(ulong grf) { FNE_PAR::AssertValid(0); if (_fInited) { AssertNilOrPo(_pglfes, 0); AssertIn(_cftg, 0, kcbMax); AssertPvCb(_prgftg, LwMul(size(FTG), _cftg)); Assert((_cftg <= kcftgFneBase) == (_prgftg == _rgftg), "wrong _prgftg"); } else Assert(_pglfes == pvNil, 0); } /*************************************************************************** Mark memory for the FNE. ***************************************************************************/ void FNE::MarkMem(void) { AssertValid(0); FNE_PAR::MarkMem(); if (_prgftg != _rgftg) MarkPv(_prgftg); MarkMemObj(_pglfes); } #endif //DEBUG