/****************************************************************************/ /* */ /* WFCOPY.C - */ /* */ /* Windows File System File Copying Routines */ /* */ /****************************************************************************/ #include "winfile.h" #include "winnet.h" #include "wnetcaps.h" // WNetGetCaps() #include "lfn.h" #include "wfcopy.h" #if 1 #ifdef TRACECOPY #define dbg(x) DebugF x #else #define dbg(x) #endif #else #define dbg(x) DEBUGF(x) #endif BOOL *pbConfirmAll; CHAR szSpace[] = " "; INT ManySource; INT nCopyNumQueue; // # of items in the queue INT nCopyMaxQueue; // size of the queue PCOPYQUEUE pCopyQueue; // copy queue buffer BOOL bCopyReport; // do notifications? bogus LPSTR lpCopyBuffer; // global memory for FileCopy() buffer WORD wCopyBufferSize; // size of this buffer VOID APIENTRY wfYield(VOID); INT NEAR PASCAL CopyMoveRetry(PSTR, INT); VOID NEAR PASCAL CopyError(PSTR, PSTR, INT, WORD, INT); BOOL NEAR PASCAL IsRootDirectory(PSTR pPath); BOOL NEAR PASCAL IsDirectory(PSTR pPath); WORD NEAR PASCAL ConfirmDialog( HWND hDlg, WORD dlg, LPSTR pFileDest, PLFNDTA pDTADest, LPSTR pFileSource, PLFNDTA pDTASource, BOOL bConfirmByDefault, BOOL *pbAll); VOID NEAR PASCAL MergePathName(LPSTR pPath, LPSTR pName); BOOL NEAR PASCAL IsInvalidPath(register LPSTR pPath); WORD NEAR PASCAL GetNextPair(register PCOPYROOT pcr, LPSTR pFrom, LPSTR pToPath, LPSTR pToSpec, WORD wFunc); INT NEAR PASCAL CheckMultiple(LPSTR pInput); VOID NEAR PASCAL DialogEnterFileStuff(register HWND hwnd); WORD NEAR PASCAL SafeFileRemove(LPSTR szFileOEM); BOOL NEAR PASCAL IsWindowsFile(LPSTR szFileOEM); INT APIENTRY ReplaceDlgProc(register HWND hDlg, UINT wMsg, WPARAM wParam, LONG lParam); BOOL APIENTRY IsValidChar(BYTE ch, BOOL fPath) { switch (ch) { case ';': // terminator case ',': // terminator case '|': // pipe case '>': // redir case '<': // redir case '"': // quote return FALSE; case '?': // wc we only do wilds here because they're case '*': // wc legal for qualifypath case '\\': // path separator case ':': // drive colon case '/': // path sep return fPath; } // cannot be a control character or space return ch > ' '; } //-------------------------------------------------------------------------- // // StripColon() - // // removes trailing colon if not a drive letter. // this is to support DOS character devices (CON:, COM1: LPT1:). DOS // can't deal with these things having a colon on the end (so we strip it). // //-------------------------------------------------------------------------- PSTR NEAR PASCAL StripColon(register PSTR pPath) { register INT cb = lstrlen(pPath); dbg(("StripColon(%s)\r\n",(LPSTR)pPath)); #ifdef DBCS { LPSTR pTailp = AnsiPrev( pPath, &(pPath[cb]) ); if (cb > 2 && *pTailp == ':') *pTailp = 0; } #else if (cb > 2 && pPath[cb-1] == ':') pPath[cb-1] = 0; #endif return pPath; } /*--------------------------------------------------------------------------*/ /* */ /* FindFileName() - */ /* */ /*--------------------------------------------------------------------------*/ /* Returns a pointer to the last component of a path string. */ PSTR NEAR PASCAL FindFileName(register PSTR pPath) { register LPSTR pT; dbg(("FindFileName(%s);\r\n",(LPSTR)pPath)); #ifdef DBCS for (pT=pPath; *pPath; pPath=(LPSTR)AnsiNext(pPath)) { if ((pPath[0] == '\\' || pPath[0] == ':') && pPath[1]) pT = pPath+1; } #else for (pT=pPath; *pPath; pPath++) { if ((pPath[0] == '\\' || pPath[0] == ':') && pPath[1]) pT = pPath+1; } #endif return(pT); } /*--------------------------------------------------------------------------*/ /* */ /* AppendToPath() - */ /* */ /*--------------------------------------------------------------------------*/ /* Appends a filename to a path. Checks the \ problem first * (which is why one can't just use lstrcat()) * Also don't append a \ to : so we can have drive-relative paths... * this last bit is no longer appropriate since we qualify first! * * is this relative junk needed anymore? if not this can be * replaced with AddBackslash(); lstrcat() */ VOID APIENTRY AppendToPath(PSTR pPath, PSTR pMore) { dbg(("AppendToPath(%s,%s);\r\n",(LPSTR)pPath,(LPSTR)pMore)); /* Don't append a \ to empty paths. */ if (*pPath) { #ifdef DBCS { LPSTR pPathBase = pPath; BYTE ch; while (*pPath) pPath++; ch = *AnsiPrev(pPathBase, pPath ); if (ch != '\\') *pPath++='\\'; } #else while (*pPath) pPath++; if (pPath[-1]!='\\') *pPath++='\\'; #endif } /* Skip any initial terminators on input. */ #ifdef DBCS while (*pMore == '\\') pMore = (LPSTR)AnsiNext(pMore); #else while (*pMore == '\\') pMore++; #endif lstrcpy(pPath, pMore); } /*--------------------------------------------------------------------------*/ /* */ /* RemoveLast() - */ /* */ /*--------------------------------------------------------------------------*/ /* Deletes the last component of a filename in a string. */ VOID APIENTRY RemoveLast(PSTR pFile) { PSTR pT; dbg(("RemoveLast(%s);\r\n",(LPSTR)pFile)); #ifdef DBCS for (pT=pFile; *pFile; pFile=(LPSTR)AnsiNext(pFile)) { if (*pFile == '\\') pT = pFile; else if (*pFile == ':') { if (pFile[1] =='\\') pFile++; pT = pFile + 1; } } #else for (pT=pFile; *pFile; pFile++) { if (*pFile == '\\') pT = pFile; else if (*pFile == ':') { if (pFile[1] =='\\') pFile++; pT = pFile + 1; } } #endif *pT = TEXT('\0'); } // qualify a DOS (or LFN) file name based on the currently active window. // this code is not careful to not write more than MAXPATHLEN characters // into psz // // in: // psz path to be qualified (of at least MAXPATHLEN characters) // ANSI string // // out: // psz fully qualified version of input string based // on the current active window (current directory) // VOID APIENTRY QualifyPath(PSTR psz) { INT cb, nSpaceLeft; CHAR szTemp[MAXPATHLEN]; INT iDrive = 0; LPSTR pOrig; #ifdef LFN BOOL flfn = FALSE; #endif STKCHK(); dbg(("QualifyPath(%s);\r\n",(LPSTR)psz)); /* Save it away. */ strncpy(szTemp, psz, sizeof(szTemp)); CheckSlashies(szTemp); StripColon(szTemp); nSpaceLeft = MAXPATHLEN; pOrig = szTemp; if (pOrig[0] == '\\' && pOrig[1] == '\\') { // leave the \\ in the buffer so that the various parts // of the UNC path will be qualified and appended. Note // we must assume that UNCs are FAT's. psz[2] = 0; nSpaceLeft -= 3; goto GetComps; } #ifdef DBCS if (pOrig[0] && pOrig[1]==':' && !IsDBCSLeadByte(pOrig[0])) { iDrive = DRIVEID(pOrig); /* Skip over the drive letter. */ pOrig += 2; } else iDrive = GetSelectedDrive(); #else if (pOrig[0] && pOrig[1]==':') { iDrive = DRIVEID(pOrig); /* Skip over the drive letter. */ pOrig += 2; } else iDrive = GetSelectedDrive(); #endif #ifdef LFN flfn = IsLFNDrive((WORD)iDrive); #ifdef DEBUG if (flfn) dbg(("lfn qualify!\r\n")); else dbg(("normal qualify!\r\n")); #endif #endif // on FAT devices, replace any illegal chars with underscores #ifdef LFN if (!flfn) #endif { LPSTR pT; #ifdef DBCS for (pT = pOrig; *pT; pT = (LPSTR)AnsiNext(pT)); #else for (pT = pOrig; *pT; pT++) #endif { if (!IsValidChar(*pT,TRUE)) *pT = '_'; } } if (pOrig[0]=='\\') { psz[0] = (CHAR)iDrive + (CHAR)'A'; psz[1] = ':'; psz[2] = '\\'; psz[3] = 0; nSpaceLeft -= 4; pOrig++; } else { /* Get current dir of drive in path. Also returns drive. */ GetSelectedDirectory((WORD)(iDrive+1), psz); nSpaceLeft -= (lstrlen(psz) + 1); } GetComps: while (*pOrig && nSpaceLeft > 0) { /* If the component is parent dir, go up one dir. * If its the current dir, skip it, else add it normally */ if (pOrig[0] == '.') { if (pOrig[1] == '.') RemoveLast(psz); else if (pOrig[1] && pOrig[1] != '\\') goto addcomponent; #ifdef DBCS while (*pOrig && *pOrig != '\\') pOrig = (LPSTR)AnsiNext(pOrig); #else while (*pOrig && *pOrig != '\\') pOrig++; #endif if (*pOrig) pOrig++; } else { LPSTR pT, pTT = NULL; addcomponent: AddBackslash(psz); nSpaceLeft--; pT = psz + lstrlen(psz); #ifdef LFN if (flfn) { // copy the component while (*pOrig && *pOrig != '\\') { #ifdef DBCS nSpaceLeft--; if (IsDBCSLeadByte(*pT++ = *pOrig++)) { if (nSpaceLeft <= 0) { pT--; } else { *pT++ = *pOrig++; nSpaceLeft--; } } #else *pT++ = *pOrig++; nSpaceLeft--; #endif } } else #endif { // copy the filename (up to 8 chars) for (cb = 0; *pOrig && *pOrig != '\\' && *pOrig != '.' && nSpaceLeft > 0;) { if (cb < 8) { cb++; #ifdef DBCS nSpaceLeft--; if (IsDBCSLeadByte(*pT++ = *pOrig++)) { if (nSpaceLeft <= 0) { pT--; } else { cb++; *pT++ = *pOrig++; nSpaceLeft--; } } #else *pT++ = *pOrig++; nSpaceLeft--; #endif } else { #ifdef DBCS pOrig = AnsiNext(pOrig); #else pOrig++; #endif } } // if there's an extension, copy it, up to 3 chars if (*pOrig == '.' && nSpaceLeft > 0) { *pT++ = '.'; nSpaceLeft--; pOrig++; for (cb = 0; *pOrig && *pOrig != '\\' && nSpaceLeft > 0;) { if (*pOrig == '.') cb = 3; if (cb < 3) { cb++; #ifdef DBCS nSpaceLeft--; if (IsDBCSLeadByte(*pT++ = *pOrig++)) { if (nSpaceLeft <= 0) { pT--; } else { cb++; *pT++ = *pOrig++; nSpaceLeft--; } } #else *pT++ = *pOrig++; nSpaceLeft--; #endif } else { #ifdef DBCS pOrig = AnsiNext(pOrig); #else pOrig++; #endif } } } } // skip the backslash if (*pOrig) pOrig++; // null terminate for next pass... *pT = 0; } } StripBackslash(psz); // remove any trailing dots if (*(psz + lstrlen(psz) - 1) == '.') *(psz + lstrlen(psz) - 1) = 0; } /*--------------------------------------------------------------------------*/ /* */ /* IsRootDirectory() - */ /* */ /*--------------------------------------------------------------------------*/ BOOL NEAR PASCAL IsRootDirectory(register LPSTR pPath) { #ifdef DBCS if (!IsDBCSLeadByte( *pPath )) { if (!lstrcmpi(pPath+1, ":\\")) return(TRUE); if (!lstrcmpi(pPath+1, ":")) return TRUE; } if (!lstrcmpi(pPath, "\\")) return(TRUE); #else if (!lstrcmpi(pPath+1, ":\\")) return(TRUE); if (!lstrcmpi(pPath, "\\")) return(TRUE); if (!lstrcmpi(pPath+1, ":")) return(TRUE); #endif return(FALSE); } // returns: // TRUE if pPath is a directory, including the root and // relative paths "." and ".." // FALSE not a dir BOOL NEAR PASCAL IsDirectory(PSTR pPath) { PSTR pT; CHAR szTemp[MAXPATHLEN]; STKCHK(); if (IsRootDirectory(pPath)) return TRUE; // check for "." and ".." pT = FindFileName(pPath); if (pT[0] == '.') { if (!pT[1] || pT[1] == '.') return TRUE; } lstrcpy(szTemp, pPath); FixAnsiPathForDos(szTemp); return WFIsDir(szTemp); } // // note: this has the side effect of setting the // current drive to the new disk if it is successful // WORD APIENTRY IsTheDiskReallyThere( HWND hwnd, register LPSTR pPath, WORD wFunc) { INT i; register INT drive; CHAR szTemp[MAXPATHLEN]; INT err = 0; WORD wError; STKCHK(); #ifdef DEBUG { char szMsg[200]; wsprintf(szMsg, "IsTheDiskReallyThere(%s)\r\n",(LPSTR)pPath); OutputDebugString(szMsg); } #endif #ifdef DBCS if (pPath[1]==':' && !IsDBCSLeadByte( *pPath )) drive = DRIVEID(pPath); else return TRUE; #else if (pPath[1]==':') drive = DRIVEID(pPath); else return TRUE; #endif Retry: err = SheGetDir(drive + 1, szTemp); if (err) goto DiskNotThere; return TRUE; DiskNotThere: wError = (WORD)GetExtendedError(); if (wError == 0x15) { // drive not ready (no disk in the drive) LoadString(hAppInstance, IDS_COPYERROR + wFunc, szTitle, sizeof(szTitle)); LoadString(hAppInstance, IDS_DRIVENOTREADY, szTemp, sizeof(szTemp)); wsprintf(szMessage, szTemp, drive + 'A'); if (MessageBox(hwnd, szMessage, szTitle, MB_ICONEXCLAMATION | MB_RETRYCANCEL) == IDRETRY) goto Retry; else return FALSE; } else if (wError == 0x1F) { // general failue (disk not formatted) LoadString(hAppInstance, IDS_COPYERROR + wFunc, szTitle, sizeof(szTitle)); LoadString(hAppInstance, IDS_UNFORMATTED, szTemp, sizeof(szTemp)); wsprintf(szMessage, szTemp, drive + 'A'); if (MessageBox(hwnd, szMessage, szTitle, MB_ICONEXCLAMATION| MB_YESNO) == IDYES) { HWND hwndSave; // this is ugly: hdlgProgress is a global that is used // by the copy code and the format code. this should // be rewritten so it is not a global (hdlgProgress should // be passed to all QueryAbort() functions, etc) hwndSave = hdlgProgress; nLastDriveInd = 0; for (i = 0; i < cDrives; i++) { if (IsRemovableDrive(rgiDrive[i])) { if (rgiDrive[i] == drive) break; nLastDriveInd++; } } fFormatFlags |= FF_ONLYONE; // alow only one format if (FormatDiskette(hwnd) != TRUE) { hdlgProgress = hwndSave; return FALSE; } hdlgProgress = hwndSave; goto Retry; } else return FALSE; } #if 0 { CHAR szBuf[100]; wsprintf(szBuf, "DOS error code %X\r\n" "Extended error %X\r\n", err, wError); OutputDebugString(szBuf); } #endif LoadString(hAppInstance, IDS_COPYERROR + wFunc, szTitle, 32); LoadString(hAppInstance, IDS_NOSUCHDRIVE, szTemp, sizeof(szTemp)); wsprintf(szMessage, szTemp, drive + 'A'); MessageBox(hwnd, szMessage, szTitle, MB_ICONHAND); return FALSE; } VOID NEAR PASCAL BuildDateLine(LPSTR szTemp, PLFNDTA plfndta) { wsprintf(szTemp, szBytes, plfndta->fd.nFileSizeLow); lstrcat(szTemp, szSpace); PutDate(&plfndta->fd.ftLastWriteTime, szTemp + lstrlen(szTemp)); lstrcat(szTemp, szSpace); PutTime(&plfndta->fd.ftLastWriteTime, szTemp + lstrlen(szTemp)); } typedef struct { LPSTR pFileDest; LPSTR pFileSource; PLFNDTA plfndtaDest; PLFNDTA plfndtaSrc; INT bWriteProtect; } PARAM_REPLACEDLG, FAR *LPPARAM_REPLACEDLG; VOID NEAR PASCAL SetDlgItemPath(HWND hDlg, INT id, LPSTR pszPath) { RECT rc; HDC hdc; HFONT hFont; CHAR szPath[MAXPATHLEN+1]; // can have one extra char HWND hwnd; hwnd = GetDlgItem(hDlg, id); if (!hwnd) return; lstrcpy(szPath, pszPath); GetClientRect(hwnd, &rc); hdc = GetDC(hDlg); hFont = (HANDLE)SendMessage(hwnd, WM_GETFONT, 0, 0L); if (hFont = SelectObject(hdc, hFont)) { CompactPath(hdc, szPath, (WORD)rc.right); SelectObject(hdc, hFont); } ReleaseDC(hDlg, hdc); SetWindowText(hwnd, szPath); } INT APIENTRY ReplaceDlgProc(register HWND hDlg, UINT wMsg, WPARAM wParam, LONG lParam) { STKCHK(); switch (wMsg) { case WM_INITDIALOG: { #define lpdlgparams ((LPPARAM_REPLACEDLG)lParam) if (lpdlgparams->bWriteProtect) { LoadString(hAppInstance, IDS_WRITEPROTECTFILE, szMessage, sizeof(szMessage)); SetDlgItemText(hDlg, IDD_STATUS, szMessage); } EnableWindow(GetDlgItem(hDlg, IDD_YESALL), !lpdlgparams->bWriteProtect && ManySource); lstrcpy(szMessage, lpdlgparams->pFileSource); lstrcat(szMessage, "?"); SetDlgItemPath(hDlg, IDD_FROM, szMessage); if (lpdlgparams->pFileDest) { BuildDateLine(szMessage, lpdlgparams->plfndtaSrc); SetDlgItemText(hDlg, IDD_DATE2, szMessage); SetDlgItemPath(hDlg, IDD_TO, lpdlgparams->pFileDest); BuildDateLine(szMessage, lpdlgparams->plfndtaDest); SetDlgItemText(hDlg, IDD_DATE1, szMessage); } break; } case WM_COMMAND: { WORD id; id = GET_WM_COMMAND_ID(wParam, lParam); switch (id) { case IDD_HELP: goto DoHelp; case IDD_FLAGS: break; case IDD_YESALL: *pbConfirmAll = TRUE; id = IDYES; // fall through case IDYES: // fall through default: // this is IDNO and IDCANCEL EndDialog(hDlg, id); return FALSE; } } break; default: if (wMsg == wHelpMessage) { DoHelp: WFHelp(hDlg); return TRUE; } else return FALSE; } return TRUE; } WORD NEAR PASCAL ConfirmDialog( HWND hDlg, WORD dlg, LPSTR pFileDest, PLFNDTA plfndtaDest, LPSTR pFileSource, PLFNDTA plfndtaSrc, BOOL bConfirmByDefault, BOOL *pbAll) { INT w; PARAM_REPLACEDLG params; FARPROC lpfp; STKCHK(); params.pFileDest = pFileDest; params.pFileSource = pFileSource; params.plfndtaDest = plfndtaDest; params.plfndtaSrc = plfndtaSrc; params.bWriteProtect = FALSE; pbConfirmAll = pbAll; // set global for dialog box lpfp = MakeProcInstance((FARPROC)ReplaceDlgProc, hAppInstance); if (!lpfp) return DE_INSMEM; if (plfndtaDest->fd.dwFileAttributes & (ATTR_READONLY | ATTR_SYSTEM | ATTR_HIDDEN)) { DWORD dwSave = dwContext; dwContext = IDH_DLGFIRST + dlg; params.bWriteProtect = TRUE; w = DialogBoxParam(hAppInstance, MAKEINTRESOURCE(dlg), hDlg, (WNDPROC)lpfp, (LONG)(LPPARAM_REPLACEDLG)¶ms); dwContext = dwSave; if (w == IDYES) { lstrcpy(szMessage, pFileDest ? (LPSTR)pFileDest : (LPSTR)pFileSource); FixAnsiPathForDos(szMessage); WFSetAttr(szMessage, plfndtaDest->fd.dwFileAttributes & ~(ATTR_READONLY|ATTR_HIDDEN|ATTR_SYSTEM)); } } else if (!bConfirmByDefault || *pbConfirmAll) { w = IDYES; } else { DWORD dwSave = dwContext; dwContext = IDH_DLGFIRST + dlg; w = DialogBoxParam(hAppInstance, MAKEINTRESOURCE(dlg), hDlg, (WNDPROC)lpfp, (LONG)(LPPARAM_REPLACEDLG)¶ms); dwContext = dwSave; } FreeProcInstance(lpfp); if (w == -1) w = DE_INSMEM; return (WORD)w; } /*--------------------------------------------------------------------------*/ /* */ /* NetCheck() - */ /* */ /*--------------------------------------------------------------------------*/ /* check rmdirs and mkdirs with the net driver */ WORD APIENTRY NetCheck(LPSTR pPath, WORD wType) { #if ORGCODE WORD err; CHAR szT[128]; #endif UNREFERENCED_PARAMETER(pPath); UNREFERENCED_PARAMETER(wType); return WN_SUCCESS; #if ORGCODE if (!(WNetGetCaps(WNNC_ADMIN) & WNNC_ADM_DirectoryNotify)) return WN_SUCCESS; #endif // we will notify the winnet driver on all directory operations // so we can implement cool net stuff on a local drives #if ORGCODE err = WNetDirectoryNotify(hdlgProgress,pPath,wType); switch (err) { case WN_SUCCESS: case WN_CONTINUE: case WN_CANCEL: case WN_NOT_SUPPORTED: return err; } WNetErrorText(err,szT,sizeof(szT)); LoadString(hAppInstance, IDS_NETERR, szTitle, sizeof(szTitle)); MessageBox(hdlgProgress, szT, szTitle, MB_OK|MB_ICONEXCLAMATION); return WN_CANCEL; #endif } /*** FIX30: This "could use some cleaning up." ***/ /*--------------------------------------------------------------------------*/ /* */ /* MergePathName() - */ /* */ /*--------------------------------------------------------------------------*/ /* Used to generate destination filenames given a pattern and an original * source name. ? is replaced by the corresponding character in the source, * and * is replaced by the remainder of the source name. * * pPath path with wildcards to be expanded * pName mask used to expand pName * * DBCS by 07/21/90 - Yukinin * */ VOID NEAR PASCAL MergePathName(LPSTR pPath, LPSTR pName) { INT i; INT cch; LPSTR pWild, p2, pEnd; BOOL bNoDir = FALSE; CHAR szWildPart[13]; // if there are no wild cards the destination path does not need merging. if (!IsWild(pPath)) return; #ifdef LFN if (LFNMergePath(pPath,pName)) return; #endif // copy only 8.3... this part may not be fully qualified for rename pWild = FindFileName(pPath); #ifdef DBCS for (p2=szWildPart,i=0; *pWild && *pWild != '.' && i<8; i++, pWild++, p2++) { *p2 = *pWild; if (IsDBCSLeadByte(*pWild)) { if (i == 7) break; *(++p2) = *(++pWild); i++; } } while (*pWild && *pWild != '.') pWild = (LPSTR)AnsiNext(pWild); if (*pWild == '.') { *p2++ = '.'; pWild++; for (i=0; *pWild && i < 3; i++, pWild++, p2++) { *p2 = *pWild; if (IsDBCSLeadByte( *pWild )) { if (i == 2) break; *(++p2) = *(++pWild); i++; } } } *p2 = 0; #else // limit the mask to 8.3 form for (p2 = szWildPart, i = 0; *pWild && *pWild != '.' && i < 8; i++, pWild++, p2++) *p2 = *pWild; while (*pWild && *pWild != '.') // skip extra after 8 pWild++; if (*pWild == '.') { // extension? // do dot 3 part *p2++ = '.'; pWild++; for (i = 0; *pWild && i < 3; i++, pWild++, p2++) *p2 = *pWild; } *p2 = 0; #endif // szWildPart now has the 8.3 form of the wildcard mask RemoveLast(pPath); AddBackslash(pPath); for (pEnd = pPath; *pEnd; pEnd++); // point to end of string pWild = szWildPart; cch = 8; merge: #ifdef DBCS // 07/21/90 - Yukinin for (i=0; i < cch; i+=(IsDBCSLeadByte(*pWild)?2:1), pWild=AnsiNext(pWild)) { #else for (i=0; i < cch; i++, pWild++) { #endif switch (*pWild) { case '\0': case ' ': case '.': break; case '*': pWild--; /*** FALL THRU ***/ case '?': if (*pName && *pName!='.') *pEnd++ = *pName++; continue; default: #ifdef DBCS *pEnd++ = *pWild; if (IsDBCSLeadByte(*pWild)) { *pEnd++ = pWild[1]; if (*pName && *pName != '.') pName++; } #else *pEnd++ = *pWild; if (*pName && *pName != '.') pName++; #endif continue; } break; } while (*pName && *pName != '.') #ifdef DBCS pName = AnsiNext(pName); #else pName++; #endif if (*pName) pName++; while (*pWild && *pWild != '.') #ifdef DBCS pWild = AnsiNext(pWild); #else pWild++; #endif if (*pWild) pWild++; if (*pWild) { *pEnd++ = '.'; cch = 3; goto merge; // do it for the extension part now } else { if (pEnd[-1]=='.') pEnd[-1]=0; else pEnd[0] = TEXT('\0'); } QualifyPath(pPath); } /*--------------------------------------------------------------------------*/ /* */ /* IsInvalidPath() - */ /* */ /*--------------------------------------------------------------------------*/ /* Checks to see if a file spec is an evil character device or if it is * too long... */ BOOL NEAR PASCAL IsInvalidPath(register LPSTR pPath) { CHAR sz[9]; INT n = 0; if (lstrlen(pPath) >= MAXPATHLEN-1) return(TRUE); pPath = FindFileName(pPath); #ifdef DBCS while (*pPath && *pPath != '.' && *pPath != ':' && n < 8) { if (IsDBCSLeadByte( *pPath )) { if (n == 7) break; sz[n++] = *pPath; } sz[n++] = *pPath++; } #else while (*pPath && *pPath != '.' && *pPath != ':' && n < 8) sz[n++] = *pPath++; #endif sz[n] = TEXT('\0'); if (!lstrcmpi(sz,"CON")) return(TRUE); if (!lstrcmpi(sz,"MS$MOUSE")) return(TRUE); if (!lstrcmpi(sz,"EMMXXXX0")) return(TRUE); if (!lstrcmpi(sz,"CLOCK$")) return(TRUE); return(FALSE); } PLFNDTA NEAR PASCAL CurPDTA(PCOPYROOT pcr) { if (pcr->cDepth) { return (pcr->rgDTA + pcr->cDepth - 1); } else { return pcr->rgDTA; } } /*--------------------------------------------------------------------------*/ /* */ /* GetNextCleanup() - */ /* */ /*--------------------------------------------------------------------------*/ VOID NEAR PASCAL GetNextCleanup(PCOPYROOT pcr) { while (pcr->cDepth) { WFFindClose(CurPDTA(pcr)); pcr->cDepth--; } } #ifdef LFN /* GetNameDialog * * Runs the dialog box to prompt the user for a new filename when copying * or moving from HPFS to FAT. */ WORD NEAR PASCAL GetNameDialog(WORD, LPSTR, LPSTR); BOOL APIENTRY GetNameDlgProc(HWND,UINT,WPARAM,LONG); WORD wDialogOp; LPSTR pszDialogFrom; LPSTR pszDialogTo; BOOL APIENTRY GetNameDlgProc( HWND hwnd, UINT wMsg, WPARAM wParam, LONG lParam) { CHAR szT[14]; LPSTR p; INT i, j, cMax, fDot; UNREFERENCED_PARAMETER(lParam); switch (wMsg) { case WM_INITDIALOG: // inform the user of the old name SetDlgItemText(hwnd, IDD_FROM, pszDialogFrom); // generate a guess for the new name p = FindFileName(pszDialogFrom); for (i = j = fDot = 0, cMax = 8; *p; p++) { if (*p == '.') { // if there was a previous dot, step back to it // this way, we get the last extension if (fDot) i -= j+1; // set number of chars to 0, put the dot in j = 0; szT[i++] = '.'; // remember we saw a dot and set max 3 chars. fDot = TRUE; cMax = 3; } else if (j < cMax && IsValidChar(*p,FALSE)) { #ifdef DBCS if (IsDBCSLeadByte(*p)) { szT[i] = *p++; if (++j >= cMax) continue; ++i; } #endif j++; szT[i++] = *p; } } szT[i] = 0; SetDlgItemText(hwnd, IDD_TO, szT); SendDlgItemMessage(hwnd,IDD_TO,EM_LIMITTEXT,13,0L); // directory the file will go into RemoveLast(pszDialogTo); SetDlgItemText(hwnd, IDD_DIR, pszDialogTo); break; case WM_COMMAND: switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDOK: GetDlgItemText(hwnd,IDD_TO,szT,14); AppendToPath(pszDialogTo,szT); QualifyPath(pszDialogTo); EndDialog(hwnd,IDOK); break; case IDCANCEL: EndDialog(hwnd,IDCANCEL); break; case IDD_HELP: goto DoHelp; case IDD_TO: GetDlgItemText(hwnd,IDD_TO,szT,14); #ifdef DBCS for (p = szT; *p; p=AnsiNext(p)) #else for (p = szT; *p; p++) #endif { if (!IsValidChar(*p,FALSE)) break; } EnableWindow(GetDlgItem(hwnd,IDOK),((!*p) && (p != szT))); break; default: return FALSE; } break; default: if (wMsg == wHelpMessage) { DoHelp: WFHelp(hwnd); return TRUE; } return FALSE; } return TRUE; } WORD NEAR PASCAL GetNameDialog(WORD wOp, LPSTR pFrom, LPSTR pTo) { FARPROC lpfn; WORD wRet = -1; DWORD dwSave; dwSave = dwContext; dwContext = IDH_DLGFIRST + LFNTOFATDLG; wDialogOp = wOp; pszDialogFrom = pFrom; pszDialogTo = pTo; lpfn = (FARPROC)MakeProcInstance(GetNameDlgProc, hAppInstance); if (lpfn) { wRet = (WORD)DialogBox(hAppInstance, MAKEINTRESOURCE(LFNTOFATDLG), hdlgProgress, (WNDPROC)lpfn); FreeProcInstance(lpfn); } dwContext = dwSave; return wRet; } #else BOOL APIENTRY GetNameDlgProc(HWND hwnd, UINT wMsg, WPARAM wParam, LONG lParam) { return FALSE; } #endif /*============================================================================ ; ; GetNextPair ; ; The following function determines the next pair of files to copy, rename, ; move, or delete. ; ; Parameters: ; ; pcr - Pointer to structure for recursing directory tree ; pFrom - Source file or directory to copy ; pToPath - Path to destination file or directory ; pToSpec - Raw destination file or directory name ; wFunc - Operation being performed. Can be one of: ; ; FUNC_DELETE - Delete files in pFrom ; FUNC_RENAME - Rename files (same directory) ; FUNC_MOVE - Move files in pFrom to pTo (different disk) ; FUNC_COPY - Copy files in pFrom to pTo ; ; Return Value: Type of operation to perform. Can be one of: ; ; OPER_ERROR - Error processing filenames ; OPER_DOFILE - Go ahead and copy, rename, or delete file ; OPER_MKDIR - Make a directory specified in pTo ; OPER_RMDIR - Remove directory ; 0 - No more files left ; ; Revision History: ; ; Modified by C. Stevens, August, 1991. Added logic so that we would call ; IsTheDiskReallyThere only once per drive. Also changed some of the code ; to minimize the number of calls which access the disk. ; ============================================================================*/ WORD NEAR PASCAL GetNextPair(PCOPYROOT pcr, PSTR pFrom, PSTR pToPath, PSTR pToSpec, WORD wFunc) { PSTR pT; /* Temporary pointer */ WORD wOp; /* Return value (operation to perform */ PLFNDTA pDTA; /* Pointer to file DTA data */ CHAR szOEM[MAXPATHLEN+1]; /* OEM version of string */ STKCHK(); *pFrom = TEXT('\0'); dbg(("GetNextPair(-,-,%s,%s,%d);\r\n", (LPSTR)pToPath, (LPSTR)pToSpec, wFunc)); /* Keep recursing directory structure until we get to the bottom */ while (TRUE) { dbg ((" top of loop....\r\n")); if (pcr->cDepth) { /* The directory we returned last call needs to be recursed. */ pDTA = pcr->rgDTA + pcr->cDepth - 1; // use this DTA below dbg ((" pcr->cDepth=%d\r\n",pcr->cDepth)); if (pcr->fRecurse && pcr->cDepth == 1 && !pcr->rgDTA[0].fd.cFileName[0]) /* The last one was the recursion root. */ goto BeginDirSearch; if (pcr->cDepth >= (MAXDIRDEPTH - 1)) { // reached the limit? wOp = OPER_ERROR | DE_PATHTODEEP; goto ReturnPair; } if (pcr->fRecurse && (pDTA->fd.dwFileAttributes & ATTR_DIR) && !(pDTA->fd.dwFileAttributes & ATTR_RETURNED)) { /* Was returned on last call, begin search. */ pDTA->fd.dwFileAttributes |= ATTR_RETURNED; pcr->cDepth++; pDTA++; BeginDirSearch: /* Search for all subfiles in directory. */ dbg ((" BeginDirSearch\r\n")); AppendToPath (pcr->sz,szStarDotStar); goto BeginSearch; } SkipThisFile: /* Search for the next matching file. */ dbg ((" SkipThisFile:\r\n")); if (!WFFindNext (pDTA)) { dbg ((" FindNext() fails\r\n")); WFFindClose (pDTA); LeaveDirectory: /* This spec has been exhausted... */ pcr->cDepth--; /* Remove the child file spec. */ RemoveLast (pcr->sz); #ifdef LFN RemoveLast (pcr->szDest); #endif if (pcr->fRecurse) { /* Tell the move/copy driver it can now delete the source directory if necessary. */ wOp = OPER_RMDIR; goto ReturnPair; } /* Not recursing, get more stuff. */ continue; } ProcessSearchResult: /* Got a file or dir in the DTA which matches the wild card originally passed in... */ dbg ((" ProcessSearchResult:\r\n")); dbg ((" found %s\r\n",(LPSTR)pDTA->fd.cFileName)); if (pDTA->fd.dwFileAttributes & ATTR_DIR) { /* Ignore directories if we're not recursing. */ if (!pcr->fRecurse) goto SkipThisFile; /* Skip the current and parent directories. */ if (pDTA->fd.cFileName[0]=='.') { if (!pDTA->fd.cFileName[1] || pDTA->fd.cFileName[1] == '.') goto SkipThisFile; } /* We need to create this directory, and then begin searching for subfiles. */ wOp = OPER_MKDIR; RemoveLast (pcr->sz); OemToAnsi (pDTA->fd.cFileName,pDTA->fd.cFileName); AppendToPath (pcr->sz,pDTA->fd.cFileName); #ifdef LFN AppendToPath (pcr->szDest,pDTA->fd.cFileName); #endif goto ReturnPair; } if (pcr->fRecurse || !(pDTA->fd.dwFileAttributes & ATTR_DIR)) { /* Remove the original spec. */ RemoveLast (pcr->sz); /* Replace it. */ AppendToPath (pcr->sz,pDTA->fd.cFileName); /* Convert to ANSI. */ pT = FindFileName (pcr->sz); OemToAnsi (pT,pT); /* If its a dir, tell the driver to create it otherwise, tell the driver to "operate" on the file. */ wOp = (WORD)((pDTA->fd.dwFileAttributes & ATTR_DIR) ? OPER_RMDIR : OPER_DOFILE); goto ReturnPair; } continue; } else { /* Read the next source spec out of the raw source string. */ pcr->fRecurse = 0; pcr->pSource = GetNextFile (pcr->pSource,pcr->sz,sizeof(pcr->sz)); #ifdef LFN pcr->szDest[0] = 0; #endif if (!pcr->pSource) return (0); /* Fully qualify the path */ QualifyPath(pcr->sz); /* Ensure the source disk really exists before doing anything. Only call IsTheDiskReallyThere once for each drive letter. Set pcr->cIsDiskThereCheck[DRIVEID] after disk has been checked. Modified by C. Stevens, August 1991 */ if (pcr->sz[1]==':' && !pcr->cIsDiskThereCheck[DRIVEID (pcr->sz)]) { if (!IsTheDiskReallyThere(hdlgProgress, pcr->sz, wFunc)) return(0); pcr->cIsDiskThereCheck[DRIVEID (pcr->sz)] = 1; } /* Classify the input string. */ if (IsWild (pcr->sz)) { /* Wild card... operate on all matches but not recursively. */ pcr->cDepth = 1; pDTA = pcr->rgDTA; pcr->pRoot = NULL; BeginSearch: dbg ((" BeginSearch: (on %s)\r\n",(LPSTR)pcr->sz)); /* Quit if pcr->sz gets too big. */ if (lstrlen (pcr->sz) - lstrlen (FindFileName (pcr->sz)) >= MAXPATHLEN) goto SearchStartFail; lstrcpy (szOEM,pcr->sz); FixAnsiPathForDos (szOEM); /* Search for the wildcard spec in pcr->sz. */ if (!WFFindFirst(pDTA, szOEM, ATTR_ALL)) { SearchStartFail: dbg((" StartSearchFail:\r\n")); if (pcr->fRecurse) { /* We are inside a recursive directory delete, so instead of erroring out, go back a level */ goto LeaveDirectory; } lstrcpy (pFrom,pcr->sz); /* Back up as if we completed a search. */ RemoveLast (pcr->sz); pcr->cDepth--; /* Find First returned an error. Return FileNotFound. */ wOp = OPER_ERROR | DE_FILENOTFOUND; goto ReturnPair; } goto ProcessSearchResult; } else { /* This could be a file or a directory. Fill in the DTA structure for attrib check */ if (!IsRootDirectory(pcr->sz)) { lstrcpy(szOEM,pcr->sz); FixAnsiPathForDos(szOEM); if (!WFFindFirst(pcr->rgDTA, szOEM, ATTR_ALL)) { wOp = OPER_ERROR | DE_FILENOTFOUND; goto ReturnPair; } WFFindClose(pcr->rgDTA); } /* Now determine if its a file or a directory */ pDTA = pcr->rgDTA; if (IsRootDirectory(pcr->sz) || (pDTA->fd.dwFileAttributes & ATTR_DIR)) { /* Process directory */ if (wFunc == FUNC_RENAME) { if (IsRootDirectory (pcr->sz)) wOp = OPER_ERROR | DE_ROOTDIR; else wOp = OPER_DOFILE; goto ReturnPair; } /* Directory: operation is recursive. */ pcr->fRecurse = TRUE; pcr->cDepth = 1; pDTA->fd.cFileName[0] = 0; pcr->pRoot = FindFileName (pcr->sz); #ifdef LFN lstrcpy (pcr->szDest,pcr->pRoot); #endif wOp = OPER_MKDIR; goto ReturnPair; } else { /* Process file */ pcr->pRoot = NULL; wOp = OPER_DOFILE; goto ReturnPair; } } } } ReturnPair: /* The source filespec has been derived into pcr->sz that is copied to pFrom. pcr->sz and pToSpec are merged into pTo. */ dbg((" ReturnPair:\r\n")); if (!*pFrom) lstrcpy(pFrom,pcr->sz); QualifyPath(pFrom); if (wFunc != FUNC_DELETE) { if (wFunc == FUNC_RENAME && !*pToPath) { lstrcpy(pToPath, pFrom); RemoveLast(pToPath); AppendToPath(pToPath, pToSpec); } else { #ifdef LFN AppendToPath(pToPath,pcr->szDest); if (wOp == OPER_MKDIR) RemoveLast(pToPath); #else /* Append to the subdirectory portion of pcr->sz */ if (pcr->pRoot) { AppendToPath(pToPath,pcr->pRoot); RemoveLast(pToPath); } #endif AppendToPath(pToPath,pToSpec); } #ifdef LFN if ((wOp == OPER_MKDIR || wOp == OPER_DOFILE) && (!IsLFNDrive((WORD)DRIVEID(pToPath)) && IsLFNDrive((WORD)DRIVEID(pFrom))) && IsLFN (FindFileName (pFrom)) && (IsWild(pToSpec) || IsLFN(pToSpec))) { if (GetNameDialog(wOp, pFrom, pToPath) != IDOK) return 0; /* User cancelled the operation, return failure */ /* Update the "to" path with the FAT name chosen by the user. */ if (wOp == OPER_MKDIR) { RemoveLast(pcr->szDest); AppendToPath(pcr->szDest, FindFileName(pToPath)); } } else #endif MergePathName(pToPath, FindFileName(pFrom)); } if (wOp == OPER_MKDIR) { /* Make sure the new directory is not a subdir of the original... */ while (*pFrom && *pFrom == *pToPath) { pFrom++; pToPath++; } if (!*pFrom && (!*pToPath || *pToPath == '\\')) { /* The two fully qualified strings are equal up to the end of the source directory ==> the destination is a subdir.Must return an error. */ wOp = OPER_ERROR | DE_DESTSUBTREE; } } return wOp; } VOID NEAR PASCAL CdDotDot (PSTR szOrig) { CHAR szTemp[MAXPATHLEN]; STKCHK(); lstrcpy(szTemp, szOrig); StripFilespec(szTemp); SheChangeDir(szTemp); } /* p is a fully qualified ANSI string. */ BOOL NEAR PASCAL IsCurrentDirectory (PSTR p) { CHAR szTemp[MAXPATHLEN]; STKCHK(); SheGetDir(DRIVEID(p) + 1, szTemp); OemToAnsi(szTemp, szTemp); return (lstrcmpi(szTemp, p) == 0); } // // test input for "multiple" filespec // // examples: // 0 foo.bar (single non directory file) // 1 *.exe (wild card) // 1 foo.bar bletch.txt (multiple files) // 2 c:\ (directory) // // note: this may hit the disk in the directory check // INT NEAR PASCAL CheckMultiple(register PSTR pInput) { PSTR pT; CHAR szTemp[MAXPATHLEN]; /* Wildcards imply multiple files. */ if (IsWild(pInput)) return 1; // wild card /* More than one thing implies multiple files. */ pT = GetNextFile(pInput, szTemp, sizeof(szTemp)); if (!pT) return 0; // blank string StripBackslash(szTemp); if (IsDirectory(szTemp)) return 2; // directory pT = GetNextFile(pT, szTemp, sizeof(szTemp)); return pT ? 1 : 0; // several files, or just one } /*--------------------------------------------------------------------------*/ /* */ /* DialogEnterFileStuff() - */ /* */ /*--------------------------------------------------------------------------*/ /* Prevents the user from diddling anything other than the cancel button. */ VOID NEAR PASCAL DialogEnterFileStuff(register HWND hwnd) { register HWND hwndT; /* set the focus to the cancel button so the user can hit space or esc */ if (hwndT = GetDlgItem(hwnd, IDCANCEL)) { SetFocus(hwndT); SendMessage(hwnd,DM_SETDEFID,IDCANCEL,0L); } /* disable the ok button and the edit controls */ if (hwndT = GetDlgItem(hwnd, IDOK)) EnableWindow(hwndT, FALSE); if (hwndT = GetDlgItem(hwnd, IDD_TO)) EnableWindow(hwndT, FALSE); if (hwndT = GetDlgItem(hwnd, IDD_FROM)) EnableWindow(hwndT, FALSE); } /*--------------------------------------------------------------------------*/ /* */ /* Notify() - */ /* */ /*--------------------------------------------------------------------------*/ /* Sets the status dialog item in the modeless status dialog box. */ // used for both the drag drop status dialogs and the manual user // entry dialogs so be careful what you change VOID FAR PASCAL Notify(HWND hDlg, WORD idMessage, PSTR szFrom, PSTR szTo) { CHAR szTemp[40]; if (!bCopyReport) return; if (idMessage) { LoadString(hAppInstance, idMessage, szTemp, sizeof(szTemp)); SetDlgItemText(hDlg, IDD_STATUS, szTemp); SetDlgItemPath(hDlg, IDD_NAME, szFrom); } else { SetDlgItemText(hDlg, IDD_STATUS, szNULL); SetDlgItemText(hDlg, IDD_NAME, szNULL); } // is this the drag/drop status dialog or the move/copy dialog SetDlgItemPath(hDlg, IDD_TONAME, szTo); } // // BOOL NEAR PASCAL IsWindowsFile(LPSTR szFileOEM) // // this is a bit strange. kernel strips off the path info so he // will match only on the base name of the file. so if the base // name matches a currently open windows file we get the full // path string and compare against that. that will tell // us that we have a file that kernel has open. // // LFN: detect long names and ignore them? BOOL NEAR PASCAL IsWindowsFile(LPSTR szFileOEM) { HANDLE hMod; CHAR szModule[MAXPATHLEN]; STKCHK(); #ifdef LFN /* kernel can't load an lfn... */ if (GetNameType(szFileOEM) == FILE_LONG) return FALSE; #endif // kernel won't accept long paths lstrcpy(szModule, szFileOEM); StripPath(szModule); hMod = GetModuleHandle(szModule); // check for one cause that's what's returned if its MSDOS // but it isn't really loaded because of xl 2.1c kernel hack if (!hMod || hMod == (HANDLE)1) return FALSE; GetModuleFileName(hMod, szModule, sizeof(szModule)); if (!lstrcmpi(szFileOEM, szModule)) // they are both OEM & we return TRUE; // just care about equality else return FALSE; } WORD NEAR PASCAL SafeFileRemove(LPSTR szFileOEM) { if (IsWindowsFile(szFileOEM)) return DE_WINDOWSFILE; else return WFRemove(szFileOEM); } INT APIENTRY WF_CreateDirectory(HWND hwndParent, LPSTR szDestOEM) { INT ret = 0; CHAR szTemp[MAXPATHLEN + 1]; // +1 for AddBackslash() LPSTR p; BOOL bCheckPath = IsRemoteDrive(DRIVEID(szDestOEM)); STKCHK(); #ifdef DEBUG if (szDestOEM[1] != ':') OutputDebugString("CreateDirectory() with non qualified path\r\n"); #endif // now create the full dir tree on the destination strncpy(szTemp, szDestOEM, sizeof(szTemp)-1); AddBackslash(szTemp); // for the loop below p = szTemp + 3; // assume we have 'X:\' to start // create each part of the dir in order while (*p) { while (*p && *p != '\\') #ifdef DBCS p = AnsiNext(p); #else p++; #endif if (*p) { *p = 0; if (!(ret = MKDir(szTemp))) { if (bCheckPath) { static CHAR szTempTemp[] = "temptemp.tmp"; BOOL bFoundFile = FALSE; PSTR pEnd; CHAR szTempFile[MAXPATHLEN+sizeof(szTempTemp)]; LFNDTA DTA; HDC hDC; INT fh; /* Note that this assumes the dir has just been created, * so it is empty (except possibly for "." and "..") */ lstrcpy(szTempFile, szTemp); pEnd = szTempFile + lstrlen(szTempFile); *pEnd++ = '\\'; lstrcpy(pEnd, szTempTemp); if (fh=_lcreat(szTempFile, 0)) { _lclose(fh); lstrcpy(pEnd, szStarDotStar); if (WFFindFirst(&DTA, szTempFile, ATTR_ALL&(~ATTR_DIR))) { do { if (!lstrcmpi(DTA.fd.cFileName, szTempTemp)) { bFoundFile = TRUE; break; } } while (WFFindNext(&DTA)) ; WFFindClose(&DTA); } lstrcpy(pEnd, szTempTemp); WFRemove(szTempFile); } if (!bFoundFile) { *(pEnd-1) = '\0'; hDC = GetDC(NULL); CompactPath(hDC, szTempFile, (WORD)(GetSystemMetrics(SM_CXSCREEN)/2)); ReleaseDC(NULL, hDC); LoadString(hAppInstance, IDS_CREATELONGDIR, szTitle, sizeof(szTitle)); wsprintf(szMessage, szTitle, (LPSTR)szTempFile); LoadString(hAppInstance, IDS_CREATELONGDIRTITLE, szTitle, sizeof(szTitle)); if (MessageBox(hwndParent, szMessage, szTitle, MB_ICONHAND|MB_YESNO) != IDYES) { RMDir(szTemp); return(DE_OPCANCELLED); } } } /* Allow the WM_FILESYSCHANGE messages to be processed */ wfYield(); } *p++ = '\\'; } } return ret; // return the last error code } /*============================================================================ ; ; WFMoveCopyDriver ; ; The following function is the mainline function for COPYing, RENAMEing, ; DELETEing, and MOVEing single or multiple files. ; ; Parameters: ; ; pFrom - String containing list of source specs ; pTo - String containing destination specs ; wFunc - Operation to be performed. Possible values are: ; FUNC_DELETE - Delete files in pFrom ; FUNC_RENAME - Rename files (same directory) ; FUNC_MOVE - Move files in pFrom to pTo (different disk) ; FUNC_COPY - Copy files in pFrom to pTo ; ; Return Value: A 0 indicates success. ; ; Modification History: ; ; August 1991 - Modified by C. Stevens. Added code to allow us to queue ; calls to GetNextPair. The purpose of this is to examine as ; many source files at once as possible. This keeps the source ; disk spinning, so we don't suffer from having to wait for the ; source disk to speed up every time we call GetNextPair. Also ; see the comments for WFCopy and FileCopy. I have changed the ; code here so we can queue the copy operations. This allows ; us to open several source and destination files in one go, ; minimizing seek time to the directory track. ; ============================================================================*/ WORD APIENTRY WFMoveCopyDriver(PSTR pFrom, PSTR pTo, WORD wFunc) { INT i; // Counter WORD ret = 0; // Return value from WFMoveCopyDriver PSTR pSpec; // Pointer to file spec WORD wAttr; // File attributes WORD oper = 0; // Disk operation being performed CHAR szDestSpec[MAXFILENAMELEN+1]; // Dest file spec CHAR szDest[MAXPATHLEN]; // Dest file (ANSI string) CHAR szDestOEM[MAXPATHLEN]; // OEM version of above CHAR szSource[MAXPATHLEN]; // Source file (ANSI string) CHAR szSourceOEM[MAXPATHLEN]; // OEM version of above LFNDTA DTADest; // DTA block for reporting dest errors PLFNDTA pDTA; // DTA pointer for source errors PCOPYROOT pcr; // Structure for searching source tree BOOL bReplaceAll = FALSE; // Replace all flag BOOL bSubtreeDelAll = FALSE; // Delete entire subtree flag BOOL bDeleteAll = FALSE; // Delete all files flag BOOL bFalse = FALSE; // For cases that aren't disableable INT nNumQueue; // Number of calls to GetNextPair PGETNEXTQUEUE pGetNextQueue = NULL;// Pointer to GetNextPair queue buffer INT CurIDS = 0; // Current string displayed in status /* Initialization stuff. Disable all file system change processing until we're all done */ STKCHK(); bCopyReport = TRUE; szDest[0] = szSource[0] = 0; DisableFSC(); /* Change all '/' characters to '\' characters in dest spec */ CheckSlashies(pFrom); bUserAbort = FALSE; /* Check for multiple source files */ ManySource = CheckMultiple(pFrom); /* Allocate buffer for searching the source tree */ pcr = (PCOPYROOT)LocalAlloc(LPTR, sizeof(COPYROOT)); if (!pcr) { ret = DE_INSMEM; goto ShowMessageBox; } /* Allocate a buffer so we can queue calls to GetNextPair. */ pGetNextQueue = (PGETNEXTQUEUE)LocalAlloc(LPTR, COPYMAXFILES * sizeof (GETNEXTQUEUE)); if (!pGetNextQueue) { ret = DE_INSMEM; goto ShowMessageBox; } /* Skip destination specific processing if we are deleting files */ if (wFunc != FUNC_DELETE) { // it is an error condition if there are multiple files // specified as the dest (but not a single directory) pSpec = GetNextFile(pTo, szMessage, MAXPATHLEN); if (GetNextFile(pSpec, szMessage, MAXPATHLEN) != NULL) { // move, copy specified with multiple destinations // not allowed, error case ret = DE_MANYDEST; goto ShowMessageBox; } lstrcpy(pTo, szMessage); QualifyPath(pTo); if (wFunc == FUNC_RENAME) { // don't let them rename multiple files to one single file if ((ManySource == 1) && !IsWild(pTo)) { ret = DE_MANYSRC1DEST; goto ShowMessageBox; } } else { /* We are either executing FUNC_COPY or FUNC_MOVE at this point. Check that the destination disk is there. NOTE: There's a disk access here slowing us down. */ if (!IsTheDiskReallyThere(hdlgProgress,pTo,wFunc)) goto CancelWholeOperation; // deal with case where directory is implicit in source // move/copy: *.* -> c:\windows, c:\windows -> c:\temp // or foo.bar -> c:\temp if (!IsWild(pTo) && (ManySource || IsDirectory(pTo))) { AddBackslash(pTo); lstrcat(pTo, szStarDotStar); } } /* FUNC_RENAME or FUNC_MOVE FUNC_COPY with a file name dest (possibly including wildcards). Save the filespec and the path part of the destination */ pSpec = FindFileName(pTo); lstrcpy(szDestSpec,pSpec); lstrcpy(szDest,pTo); RemoveLast(szDest); pSpec = szDest + lstrlen(szDest); } pcr->pSource = pFrom; /* Disable all but the cancel button on the notify dialog */ DialogEnterFileStuff(hdlgProgress); /* Set up arguments for queued copy commands */ lpCopyBuffer = NULL; pCopyQueue = NULL; while (pcr) { /* Allow the user to abort the operation */ if (WFQueryAbort()) goto CancelWholeOperation; /* Now queue up a bunch of GetNextPair calls. */ for (nNumQueue = 0; nNumQueue < COPYMAXFILES; nNumQueue++) { /* Clean off the last filespec for multiple file copies */ if (wFunc != FUNC_DELETE) { *pSpec = TEXT('\0'); } oper = GetNextPair(pcr,szSource,szDest,szDestSpec,wFunc); /* Check for no operation or error */ if (!oper) { LocalFree((HANDLE)pcr); pcr = NULL; break; } if ((oper & OPER_MASK) == OPER_ERROR) { ret = LOBYTE (oper); oper = OPER_DOFILE; goto ShowMessageBox; } pGetNextQueue[nNumQueue].nOper = oper; lstrcpy(pGetNextQueue[nNumQueue].szSource, szSource); lstrcpy(pGetNextQueue[nNumQueue].szDest, szDest); pGetNextQueue[nNumQueue].SourceDTA = *CurPDTA(pcr); } /* Execute the queued GetNextPair calls */ for (i = 0; i < nNumQueue; i++) { /* Allow the user to abort the operation */ if (WFQueryAbort()) goto CancelWholeOperation; oper = (WORD)pGetNextQueue[i].nOper; lstrcpy(szSource, pGetNextQueue[i].szSource); lstrcpy(szDest, pGetNextQueue[i].szDest); pDTA = &pGetNextQueue[i].SourceDTA; dbg(("Gonna do OPER:%x FUNC:%x '%s' and '%s'.\r\n",oper,wFunc, (LPSTR)szSource, (LPSTR)szDest)); /* Fix up source spec */ lstrcpy (szSourceOEM,szSource); FixAnsiPathForDos (szSourceOEM); if (IsInvalidPath (szSource)) { ret = DE_ACCESSDENIED; goto ShowMessageBox; } if (wFunc != FUNC_DELETE) { /* Fix up dest spec */ lstrcpy(szDestOEM, szDest); FixAnsiPathForDos(szDestOEM); if (!lstrcmpi(szSource, szDest)) { ret = DE_SAMEFILE; goto ShowMessageBox; } else if (IsInvalidPath (szDest)) { ret = DE_ACCESSDENIED | ERRORONDEST; goto ShowMessageBox; } /* Check to see if we are overwriting an existing file. If so, better confirm */ if (oper == OPER_DOFILE) { // we can avoid this expensive call on dos 4.0 and up // by using the extended open don't replace option if (WFFindFirst(&DTADest, szDestOEM, ATTR_ALL)) { WFFindClose(&DTADest); if (wFunc == FUNC_RENAME) { ret = DE_RENAMREPLACE; goto ShowMessageBox; } // we need to check if we are trying to copy a file // over a directory and give a reasonable error message switch (wAttr = ConfirmDialog (hdlgProgress,CONFIRMREPLACE, szDest,&DTADest,szSource, pDTA,bConfirmReplace, &bReplaceAll)) { case IDYES: /* Perform the delete */ if ((wFunc == FUNC_MOVE) && (DRIVEID(szSource) == DRIVEID(szDest))) { /* For FUNC_MOVE we need to delete the * destination first. Do that now. */ if (DTADest.fd.dwFileAttributes & ATTR_DIR) { if (IsCurrentDirectory(szDestOEM)) CdDotDot(szDestOEM); switch (NetCheck(szDest, WNDN_RMDIR)) { case WN_SUCCESS: /* Remove directory */ ret = RMDir(szDestOEM); break; case WN_CONTINUE: break; case WN_CANCEL: goto CancelWholeOperation; } } else { ret = SafeFileRemove (szDestOEM); } if (ret) { ret |= ERRORONDEST; goto ShowMessageBox; } } break; case IDNO: /* Don't perform operation on current file */ continue; case IDCANCEL: goto CancelWholeOperation; default: ret = (WORD) wAttr; goto ShowMessageBox; } } } } /* Now determine which operation to perform */ switch (oper | wFunc) { case OPER_MKDIR | FUNC_COPY: // Create destination directory case OPER_MKDIR | FUNC_MOVE: // Create dest, verify source delete CurIDS = IDS_CREATINGMSG; Notify(hdlgProgress, IDS_CREATINGMSG, szDest, szNULL); switch (NetCheck(szDest, WNDN_MKDIR)) { case WN_SUCCESS: break; case WN_CONTINUE: goto SkipMKDir; case WN_CANCEL: goto CancelWholeOperation; } ret = (WORD)WF_CreateDirectory(hdlgProgress, szDestOEM); if (!ret) /* set attributes of dest to source (not including the subdir and vollabel bits) */ WFSetAttr(szDestOEM, pDTA->fd.dwFileAttributes & ~(ATTR_DIR|ATTR_VOLUME)); // if it already exits ingore the error return if (ret == DE_ACCESSDENIED) ret = 0; if (ret) ret |= ERRORONDEST; /* set attributes of new directory to those of the source */ SkipMKDir: break; case OPER_MKDIR | FUNC_DELETE: /* Confirm removal of directory on this pass. The directories are actually removed on the OPER_RMDIR pass */ /* We can't delete the root directory, so don't bother confirming it */ if (IsRootDirectory(szSource)) break; switch (wAttr = ConfirmDialog (hdlgProgress,CONFIRMRMDIR, NULL,pDTA,szSource, NULL, bConfirmSubDel, &bSubtreeDelAll)) { case IDYES: break; case IDNO: case IDCANCEL: goto CancelWholeOperation; default: ret = (WORD) wAttr; goto ShowMessageBox; } break; case OPER_RMDIR | FUNC_MOVE: case OPER_RMDIR | FUNC_DELETE: CurIDS = IDS_REMOVINGDIRMSG; Notify(hdlgProgress, IDS_REMOVINGDIRMSG, szSource, szNULL); if (IsRootDirectory (szSource)) break; if (IsCurrentDirectory (szSource)) CdDotDot (szSource); /* We already confirmed the delete at MKDIR time, so attempt to delete the directory */ switch (NetCheck (szSource,WNDN_RMDIR)) { case WN_SUCCESS: ret = RMDir (szSourceOEM); break; case WN_CONTINUE: break; case WN_CANCEL: goto CancelWholeOperation; } break; case OPER_RMDIR | FUNC_COPY: break; case OPER_DOFILE | FUNC_COPY: if (IsWindowsFile(szDestOEM)) { ret = DE_WINDOWSFILE | ERRORONDEST; break; } TRY_COPY_AGAIN: /* Now try to copy the file. Do extra error processing only in 2 cases: 1) If a floppy is full let the user stick in a new disk 2) If the path doesn't exist (the user typed in and explicit path that doesn't exits) ask if we should create it for him. NOTE: This processing is normally done by WFCopy. But in the case where LFN copy support is invoked, we have to support this error condition here. Modified by C. Stevens, August 1991 */ ret = WFCopy(szSourceOEM, szDestOEM); if (bUserAbort) goto CancelWholeOperation; if ((((ret & ~ERRORONDEST) == DE_NODISKSPACE) && IsRemovableDrive(DRIVEID(szDestOEM))) || ((ret & ~ERRORONDEST) == DE_PATHNOTFOUND)) { ret = (WORD)CopyMoveRetry(szDestOEM, (INT)ret); if (!ret) goto TRY_COPY_AGAIN; else goto CancelWholeOperation; } break; case OPER_DOFILE | FUNC_RENAME: { CHAR save1,save2; PSTR p; if (CurIDS != IDS_RENAMINGMSG) { CurIDS = IDS_RENAMINGMSG; Notify(hdlgProgress, IDS_RENAMINGMSG, szNULL, szNULL); } /* Get raw source and dest paths. Check to make sure the paths are the same */ p = FindFileName(szSource); save1 = *p; *p = TEXT('\0'); p = FindFileName(szDest); save2 = *p; *p = TEXT('\0'); ret = (WORD)lstrcmpi(szSource, szDest); szSource[lstrlen(szSource)] = save1; szDest[lstrlen(szDest)] = save2; if (ret) { ret = DE_DIFFDIR; break; } goto DoMoveRename; } case OPER_DOFILE | FUNC_MOVE: if (CurIDS != IDS_MOVINGMSG) { CurIDS = IDS_MOVINGMSG; Notify(hdlgProgress, IDS_MOVINGMSG, szNULL, szNULL); } DoMoveRename: /* Don't allow the user to rename from or to the root directory */ if (IsRootDirectory(szSource)) { ret = DE_ROOTDIR; break; } if (IsRootDirectory(szDest)) { ret = DE_ROOTDIR | ERRORONDEST; break; } if (IsCurrentDirectory(szSource)) CdDotDot(szSource); /* Confirm the rename */ switch (wAttr = ConfirmDialog (hdlgProgress, (WORD)(wFunc == FUNC_MOVE ? CONFIRMMOVE : CONFIRMRENAME), NULL,pDTA,szSource,NULL,FALSE, (BOOL *)&bFalse)) { case IDYES: break; case IDNO: continue; case IDCANCEL: goto CancelWholeOperation; default: ret = (WORD) wAttr; goto ShowMessageBox; } if (IsWindowsFile(szSourceOEM)) { ret = DE_WINDOWSFILE; } else { if (DRIVEID(szSource) == DRIVEID(szDest)) { ret = WFMove(szSourceOEM, szDestOEM); if (!ret) /* set attributes of dest to those of the source */ WFSetAttr(szDestOEM, pDTA->fd.dwFileAttributes); } else { // we must force all copies to go through // straight so we can remove the source // and have the ret = WFCopy(szSourceOEM, szDestOEM); if (!ret) { ret = EndCopy(); if (!ret) WFRemove(szSourceOEM); } if (bUserAbort) goto CancelWholeOperation; } } break; case OPER_DOFILE | FUNC_DELETE: if (CurIDS != IDS_DELETINGMSG) { CurIDS = IDS_DELETINGMSG; Notify(hdlgProgress,IDS_DELETINGMSG,szNULL, szNULL); } /* Confirm the delete first */ switch (wAttr = ConfirmDialog (hdlgProgress,CONFIRMDELETE, NULL,pDTA,szSource,NULL, bConfirmDelete,&bDeleteAll)) { case IDYES: break; case IDNO: continue; case IDCANCEL: goto CancelWholeOperation; default: ret = (WORD)wAttr; goto ShowMessageBox; } /* make sure we don't delete any open windows apps or dlls (lets hope this isn't too slow) */ ret = SafeFileRemove(szSourceOEM); break; default: ret = DE_HOWDIDTHISHAPPEN; // internal error break; } /* Report any errors which have occurred */ if (ret) { ShowMessageBox: CopyError(szSource, szDest, ret, wFunc, oper); /* Continue the operation where one file is a windows file in use */ if ((ret & ~ERRORONDEST) != DE_WINDOWSFILE) { CancelWholeOperation: /* Force a CopyAbort in case there are any files in the copy queue */ bUserAbort = TRUE; goto ExitLoop; } } } } ExitLoop: /* Copy any outstanding files in the copy queue */ if (!bUserAbort) { if (EndCopy()) CopyAbort(); } else CopyAbort(); // this happens in error cases where we broke out of the pcr loop // without hitting the end if (pcr) { GetNextCleanup(pcr); LocalFree((HANDLE)pcr); } if (pGetNextQueue) LocalFree((HANDLE)pGetNextQueue); /* goofy way to make sure we've gotten all the WM_FILESYSCHANGE messages */ WFQueryAbort(); EnableFSC(); return ret; } /*--------------------------------------------------------------------------*/ /* */ /* DMMoveCopyHelper() - */ /* */ /*--------------------------------------------------------------------------*/ /* Used by Danger Mouse to do moves and copies. */ WORD APIENTRY DMMoveCopyHelper(register LPSTR pFrom, register LPSTR pTo, BOOL bCopy) { WORD iStatus; FARPROC lpfn; dbg(("DMMoveCopyHelper(%s,%s);\r\n",(LPSTR)pFrom,(LPSTR)pTo)); /* Confirm mouse operations. */ if (bConfirmMouse) { LoadString(hAppInstance, IDS_MOUSECONFIRM, szTitle, sizeof(szTitle)); LoadString(hAppInstance, bCopy ? IDS_COPYMOUSECONFIRM : IDS_MOVEMOUSECONFIRM, szMessage, sizeof(szMessage)); if (MessageBox(hwndFrame, szMessage, szTitle, MB_YESNO | MB_ICONEXCLAMATION) != IDYES) return DE_OPCANCELLED; } /* Create the status dialog box. */ lpfn = MakeProcInstance((FARPROC)ProgressDlgProc, hAppInstance); if (!lpfn) return DE_INSMEM; hdlgProgress = CreateDialog(hAppInstance, MAKEINTRESOURCE(DMSTATUSDLG), hwndFrame, (WNDPROC)lpfn); if (!hdlgProgress) { FreeProcInstance(lpfn); return DE_INSMEM; } /* Set the destination directory in the dialog. * use IDD_TONAME 'cause IDD_TO gets disabled.... */ // SetDlgItemText(hdlgProgress, IDD_TONAME, pTo); /* The dialog title defaults to "Moving..." */ if (bCopy) { LoadString(hAppInstance, IDS_COPYINGTITLE, szMessage, sizeof(szMessage)); SetWindowText(hdlgProgress, szMessage); } /* Display and paint the status dialog. */ EnableWindow(hwndFrame,FALSE); ShowWindow(hdlgProgress, SW_SHOW); UpdateWindow(hdlgProgress); /* Move/Copy things. */ iStatus = WFMoveCopyDriver(pFrom, pTo, (WORD)(bCopy ? FUNC_COPY : FUNC_MOVE)); /* Destroy the status dialog. */ EnableWindow(hwndFrame,TRUE); DestroyWindow(hdlgProgress); FreeProcInstance(lpfn); return(iStatus); } WORD APIENTRY FileRemove(PSTR pSpec) { if (DeleteFile(pSpec)) return (WORD)0; else return (WORD)GetLastError(); } WORD APIENTRY FileMove(PSTR pFrom, PSTR pTo) { WORD result; TryAgain: if (MoveFile(pFrom, pTo)) result = 0; else result = (WORD)GetLastError(); // try to create the destination if it is not there if (result == DE_PATHNOTFOUND) { result = (WORD)CopyMoveRetry(pTo, (INT)result); if (!result) goto TryAgain; else return result; } } /*============================================================================ ; ; FileCopy ; ; The following function replaces the old FileCopy function which performed ; single file copies. This function queues copies. The function StartCopy ; is called to initialize the copy queue if required. If the queue is full, ; the function EndCopy is called to purge the copy queue before queueing ; up new copy commands. Note that the function EndCopy must be called to ; purge the copy queue. ; ; Parameters: ; ; pszSource - Fully qualified source path ; pszDest - Fully qualifies destination path ; ; returns: ; 0 success ; dos error code for failure ; ============================================================================*/ WORD APIENTRY FileCopy(PSTR pszSource, PSTR pszDest) { WORD ret; if (ret = StartCopy()) return ret; // failure // if the queue is full we must empty it first if (nCopyNumQueue >= nCopyMaxQueue) { // queue is full, now we empty it by really doing copies if (ret = EndCopy()) return ret; // failure if (ret = StartCopy()) return ret; // failure } // add this to the queue lstrcpy(pCopyQueue[nCopyNumQueue].szSource, pszSource); lstrcpy(pCopyQueue[nCopyNumQueue].szDest, pszDest); pCopyQueue[nCopyNumQueue].hSource = -1; pCopyQueue[nCopyNumQueue].hDest = -1; pCopyQueue[nCopyNumQueue].ftLastWriteTime.dwLowDateTime = 0; pCopyQueue[nCopyNumQueue].ftLastWriteTime.dwHighDateTime = 0; nCopyNumQueue++; return 0; // success } /*============================================================================ ; ; StartCopy ; ; The following function is called automatically by WFCopy to initialize the ; copy queue. The function is called each time by WFCopy, but will only ; initialize the first time. The function allocates a buffer for reading and ; writing, and a buffer for storing the source and destination filenames, ; handles, and time stamps. The function EndCopy must be called to flush the ; copy queue, and perform the actual disk transfer. ; ; Parameters: None ; ; return: ; 0 success ; != 0 dos error code (DE_ value) ; ; Written by C. Stevens, August 1991 ; ============================================================================*/ WORD APIENTRY StartCopy(VOID) { WORD wSize; /* Buffer size */ register INT i; /* Counter */ // have we already been called? if (lpCopyBuffer && pCopyQueue) return 0; // success, buffers already allocated /* Allocate and lock buffer for reading and writing */ wSize = COPYMAXBUFFERSIZE; while (!lpCopyBuffer) { lpCopyBuffer = GlobalAllocPtr(GHND, (DWORD)wSize); if (!lpCopyBuffer) { wSize /= 2; if (wSize < COPYMINBUFFERSIZE) return DE_INSMEM; // memory failure } } wCopyBufferSize = wSize; /* Allocate and lock buffer for copy queue. Note that magic +5 below is because we always have stdin, stdout, stderr, and AUX files open all the time, and we can't count them as available file handles */ // someone opens files on our psp, leave them 2 handles // so we don't run on in the midst of copying nCopyMaxQueue = min(SetHandleCount(11 * 2) / 2 - 1, 10); #ifdef DEBUG { char buf[80]; wsprintf(buf, "SetHandleCount() -> %d\r\n", nCopyMaxQueue); OutputDebugString(buf); } #endif wSize = (WORD)(nCopyMaxQueue * sizeof(COPYQUEUEENTRY)); while (!pCopyQueue) { pCopyQueue = (PCOPYQUEUE)LocalAlloc(LPTR,wSize); if (!pCopyQueue) { wSize /= 2; if (wSize < (COPYMINFILES * sizeof(COPYQUEUEENTRY))) { GlobalFreePtr(lpCopyBuffer); lpCopyBuffer = NULL; return DE_INSMEM; // memory failure } } } /* Initialize other Copy Queue variables and return success */ nCopyMaxQueue = (int) wSize / sizeof (COPYQUEUEENTRY); nCopyNumQueue = 0; for (i = 0; i < nCopyMaxQueue; i++) { pCopyQueue[i].szSource[0] = 0; pCopyQueue[i].szDest[0] = 0; pCopyQueue[i].hSource = -1; pCopyQueue[i].hDest = -1; } return 0; // success } // in: // pszFile file to open/create // wAttrib attributes to use on create // // returns: // flags register (carry set on error) // *pfh file handle or dos error code WORD NEAR PASCAL OpenDestFile(PSTR pszFile, WORD wAttrib, INT NEAR *pfh) { INT fh; WORD wStatus = 0; OFSTRUCT ofs; // use new extended open on dos > 4 if (wDOSversion >= 0x0400) { if (wAttrib & ATTR_ATTRIBS) wAttrib &= ATTR_USED; else wAttrib = ATTR_ARCHIVE; { fh = OpenFile(pszFile, &ofs, OF_READWRITE | OF_SHARE_DENY_WRITE | OF_CREATE); if (fh == (INT)-1) { fh = GetLastError(); wStatus |= CARRY_FLAG; } else { wStatus = 0; SetFileAttributes(pszFile, wAttrib); } // fh now contains a file handle or error code } } else { { fh = OpenFile(pszFile, &ofs, OF_READWRITE | OF_SHARE_DENY_WRITE | OF_CREATE); if (fh == (INT)-1) { fh = GetLastError(); wStatus |= CARRY_FLAG; } else wStatus = 0; } } *pfh = fh; if (!(wStatus & CARRY_FLAG)) { #if 0 // not possible on Win32/NT _asm { ; Call DOS IOCTL to set device up for RAW mode mov bx,fh mov dx,20h mov ax,4401h int 21h } #endif } return wStatus; } /*============================================================================ ; ; EndCopy ; ; The following function flushes the copy queue, attempting to copy all files ; in the queue. The function ALWAYS frees global memory and flushes the ; queue and reports it's own errors. ; ; strategy: ; we will do as many operations on one drive as we can, thus ; avoiding disk spin up time (really bad on floppies). ; ; Parameters: None ; ; returns: ; 0 successful operation ; != 0 dos error code (DE_OPCANCELLED) failure ; ; use like: ; ; loop { ; ret = WFCopy(); ; if (ret) { ; ReportError(ret); ; goto Error; ; } ; } ; ; ret = EndCopy(); ; if (ret) ; goto Error; ; ; return success; ; ;Error: ; CopyAbort(); ; ReportError(ret); ; ============================================================================*/ WORD APIENTRY EndCopy(VOID) { INT i, j; /* Counter */ PSTR pTemp; /* Pointer to source or dest filename */ INT fh; /* File handle for DOS calls */ WORD wStatus; /* Status flags returned from DOS calls */ DWORD wRead; /* Number of bytes read from source file */ DWORD wWrite; /* Number of bytes written to destination file */ FILETIME ftLastWriteTime; /* Source file date and time */ DWORD wAttrib; /* File attributes */ #ifdef DEBUG { char buf[80]; wsprintf(buf, "EndCopy() nCopyNumQueue == %d\r\n", nCopyNumQueue); OutputDebugString(buf); } #endif /* Open as many source files as possible. Note we are assuming here that nCopyNumQueue < nCopyMaxQueue. This should always be true because WFCopy calls EndCopy to purge the queue if it becomes full. We should never get an out of handles error opening source files or destination files. If we do get an out of handles error opening source files, cause a fatal error and abort the copy. */ // open all source files Notify(hdlgProgress, IDS_OPENINGMSG, szNULL, szNULL); for (i = 0; i < nCopyNumQueue; i++) { if (WFQueryAbort()) return DE_OPCANCELLED; pTemp = pCopyQueue[i].szSource; { OFSTRUCT ofs; fh = OpenFile(pTemp, &ofs, OF_READ); if (fh == (INT)-1) fh = OpenFile(pTemp, &ofs, OF_SHARE_DENY_WRITE); } if (fh == (INT)-1) { CopyError(pCopyQueue[i].szSource, pCopyQueue[i].szDest, fh, FUNC_COPY, OPER_DOFILE); return DE_OPCANCELLED; // error already reported } else pCopyQueue[i].hSource = fh; #if 0 // not possible on Win32/NT. _asm { ; Call DOS IOCTL to set device up for RAW mode. mov bx,fh mov dx,20h mov ax,4401h int 21h } #endif /* Get the source file date, time, and attributes if necessary */ fh = pCopyQueue[i].hSource; if (!IsSerialDevice(fh)) { { FILETIME ft; // Call DOS Get Date/Time of File. if (GetFileTime((HANDLE)fh, NULL, NULL, (LPFILETIME)&ft)) pCopyQueue[i].ftLastWriteTime = ft; } pTemp = pCopyQueue[i].szSource; { // Call DOS Get File Attributes wAttrib = GetFileAttributes(pTemp); if (wAttrib != (DWORD)-1) pCopyQueue[i].wAttrib |= (wAttrib | ATTR_ATTRIBS); } } } /* Now open as many destination files as possible. If we get an out of handles error, cause a fatal abort because we already called Windows SetHandleCount to ensure we had enough. Note: We are assuming the files do not exist when we try to open them, although for DOS 4.0 and above files WILL be replaced if they do happen to exist. */ // open all destination files for (i = 0; i < nCopyNumQueue; i++) { if (WFQueryAbort()) return DE_OPCANCELLED; TryOpen: wStatus = OpenDestFile(pCopyQueue[i].szDest, (WORD)pCopyQueue[i].wAttrib, (INT NEAR *)&fh); if (wStatus & CARRY_FLAG) { // error operning/creating destinaton file if (fh == DE_PATHNOTFOUND) { TryOpenDestAgain: // ask the user to stick in another disk fh = CopyMoveRetry(pCopyQueue[i].szDest, fh); if (!fh) { goto TryOpen; } else { // didn't happen, abort this copy CopyError(pCopyQueue[i].szSource, pCopyQueue[i].szDest, (WORD)fh | ERRORONDEST, FUNC_COPY, OPER_DOFILE); return DE_OPCANCELLED; // error already reported } } else { // some other error condition CopyError(pCopyQueue[i].szSource, pCopyQueue[i].szDest, (WORD)fh | ERRORONDEST, FUNC_COPY, OPER_DOFILE); return DE_OPCANCELLED; // error already reported } } else { pCopyQueue[i].hDest = fh; // dest file open success } } /* Now copy between the open files */ for (i = 0; i < nCopyNumQueue; i++) { Notify(hdlgProgress, IDS_COPYINGMSG, pCopyQueue[i].szSource, pCopyQueue[i].szDest); wRead = wCopyBufferSize; do { if (WFQueryAbort()) return DE_OPCANCELLED; fh = pCopyQueue[i].hSource; { wRead = _lread(fh, lpCopyBuffer, wCopyBufferSize); if (wRead == (DWORD)-1) { wStatus |= CARRY_FLAG; wRead = GetLastError(); } else wStatus = 0; // wRead is either # bytes read or error code } if (wStatus & CARRY_FLAG) { // Error during file read CopyError(pCopyQueue[i].szSource, pCopyQueue[i].szDest, wRead, FUNC_COPY, OPER_DOFILE); return DE_OPCANCELLED; // error already reported } fh = pCopyQueue[i].hDest; { // size can be zero to terminate file wWrite = _lwrite(fh, lpCopyBuffer, wRead); if (wWrite == (DWORD)-1) { wStatus |= CARRY_FLAG; wWrite = GetLastError(); } else wStatus = 0; // wWrite is either # bytes read or error code } if (wStatus & CARRY_FLAG) { CopyError(pCopyQueue[i].szSource, pCopyQueue[i].szDest, wWrite | ERRORONDEST, FUNC_COPY, OPER_DOFILE); return DE_OPCANCELLED; // error already reported } // write did not complete and removable drive? if (wRead != wWrite) { if (IsRemovableDrive(DRIVEID(pCopyQueue[i].szDest)) && (DRIVEID(pCopyQueue[i].szDest) != DRIVEID(pCopyQueue[i].szSource))) { // destination disk must be full. delete the destination // files, give the user the option to insert a new disk. for (j = i; j < nCopyNumQueue; j++) { _lclose(pCopyQueue[j].hDest); pCopyQueue[j].hDest = -1; pTemp = pCopyQueue[j].szDest; DeleteFile(pTemp); } fh = DE_NODISKSPACE; goto TryOpenDestAgain; // and try to create the destiations } else { // not removable, error condition CopyError(pCopyQueue[i].szSource, pCopyQueue[i].szDest, DE_NODISKSPACE | ERRORONDEST, FUNC_COPY, OPER_DOFILE); return DE_OPCANCELLED; // error already reported } } // we have moved all the data, so don't delete this on // clean up. if (!wRead) pCopyQueue[i].wAttrib |= ATTR_COPIED; } while (wRead); } // Close all destination files, set date time attribs Notify(hdlgProgress, IDS_CLOSINGMSG, szNULL, szNULL); for (i = 0; i < nCopyNumQueue; i++) { fh = pCopyQueue[i].hDest; if (!IsSerialDevice(fh)) { ftLastWriteTime = pCopyQueue[i].ftLastWriteTime; if (ftLastWriteTime.dwLowDateTime && ftLastWriteTime.dwHighDateTime) { SetFileTime((HANDLE)fh, NULL, NULL, (LPFILETIME)&ftLastWriteTime); } } _lclose(pCopyQueue[i].hDest); pCopyQueue[i].hDest = -1; /* Now set the file attributes if necessary */ if (wDOSversion < 0x0400) { pTemp = pCopyQueue[i].szDest; wAttrib = pCopyQueue[i].wAttrib; // only set attribs if necessary (this is slow) if (wAttrib & ATTR_ATTRIBS) { wAttrib &= ATTR_USED; SetFileAttributes(pTemp, wAttrib); } } } // Close all source files (and delete them if necessary) if (pCopyQueue && (pCopyQueue[0].wAttrib & ATTR_DELSRC)) Notify(hdlgProgress, IDS_REMOVINGMSG, szNULL, szNULL); for (i = 0; i < nCopyNumQueue; i++) { _lclose(pCopyQueue[i].hSource); pCopyQueue[i].hSource = -1; if (pCopyQueue[i].wAttrib & ATTR_DELSRC) { WFRemove(pCopyQueue[i].szSource); } } if (lpCopyBuffer) { GlobalFreePtr(lpCopyBuffer); lpCopyBuffer = NULL; } if (pCopyQueue) { LocalFree((HANDLE)pCopyQueue); pCopyQueue = NULL; } nCopyMaxQueue = 0; nCopyNumQueue = 0; return 0; // success } /*============================================================================ ; ; CopyError ; ; The following function reports an error during a file copy operation ; ; Parameters ; ; lpszSource - Source file name ; lpszDest - Destination file name ; nError - dos (or our exteneded) error code ; 0xFFFF for special case NET error ; wFunc - Operation being performed during error. Can be one of: ; FUNC_DELETE - Delete files in pFrom ; FUNC_RENAME - Rename files (same directory) ; FUNC_MOVE - Move files in pFrom to pTo (different disk) ; FUNC_COPY - Copy files in pFrom to pTo ; nOper - Operation being performed. Can be one of: ; OPER_ERROR - Error processing filenames ; OPER_DOFILE - Go ahead and copy, rename, or delete file ; OPER_MKDIR - Make a directory specified in pTo ; OPER_RMDIR - Remove directory ; 0 - No more files left ; ; Return Value: None ; ; Written by C. Stevens, August 1991 ; ============================================================================*/ VOID NEAR PASCAL CopyError(PSTR pszSource, PSTR pszDest, INT nError, WORD wFunc, INT nOper) { CHAR szVerb[70]; /* Verb describing error */ CHAR szReason[200]; /* Reason for error */ BOOL bDest; bDest = nError & ERRORONDEST; // was dest file cause of error nError &= ~ERRORONDEST; // clear the dest bit if (nError == DE_OPCANCELLED) // user abort return; if (!bCopyReport) // silent, don't report errors return; LoadString(hAppInstance, IDS_COPYERROR + wFunc, szTitle, sizeof(szTitle)); // get the verb string if (nOper == OPER_DOFILE || !nOper) { if (nError != 0xFFFF && bDest) // this is bogus, this could be IDS_CREATING as well... LoadString(hAppInstance, IDS_REPLACING, szVerb, sizeof(szVerb)); else LoadString(hAppInstance, IDS_VERBS + wFunc, szVerb, sizeof(szVerb)); } else { LoadString(hAppInstance, IDS_ACTIONS + (nOper >> 8), szVerb, sizeof(szVerb)); } // get the reason string if (nError == 0xFFFF) { // special case LFN net error WNetErrorText(WN_NET_ERROR, szReason, sizeof(szReason)); } else { // transform some error cases if (bDest) { if (nError != DE_ACCESSDENIED && GetFreeDiskSpace((WORD)DRIVEID(pszDest)) == 0L) nError = DE_NODISKSPACE; } else { if (nError == DE_ACCESSDENIED) nError = DE_ACCESSDENIEDSRC; // soruce file access denied } LoadString(hAppInstance, IDS_REASONS + nError, szReason, sizeof(szReason)); } // use the files names or "Selected files" if file list too long if (!nOper && (lstrlen(pszSource) > 64)) LoadString(hAppInstance, IDS_SELECTEDFILES, pszSource, 32); wsprintf(szMessage, szVerb, (LPSTR)(bDest ? pszDest : pszSource), (LPSTR)szReason); MessageBox(hdlgProgress, szMessage, szTitle, MB_OK | MB_ICONSTOP); } /*============================================================================ ; ; CopyAbort ; ; The following function aborts a queued copy operation. The function closes ; all source and destination files, deleteing all destination files ; including and following the specified index. ; ; Parameters: ; ; nIndex - Index of first destination file to delete ; ; Return Value: None ; ; Written by C. Stevens, August 1991 ; ============================================================================*/ VOID APIENTRY CopyAbort(VOID) { INT i; PSTR pTemp; // close all source files for (i = 0; i < nCopyMaxQueue; i++) { if (pCopyQueue[i].hSource != -1) _lclose(pCopyQueue[i].hSource); } // close and delete (if necessary) destination files for (i = 0; i < nCopyMaxQueue; i++) { if (pCopyQueue[i].hDest != -1) { _lclose(pCopyQueue[i].hDest); if (!(pCopyQueue[i].wAttrib & ATTR_COPIED)) { pTemp = pCopyQueue[i].szDest; DeleteFile(pTemp); } } } if (lpCopyBuffer) { GlobalFreePtr(lpCopyBuffer); lpCopyBuffer = NULL; } if (pCopyQueue) { LocalFree((HANDLE)pCopyQueue); pCopyQueue = NULL; } nCopyMaxQueue = 0; /* Clear other Copy Queue variables */ nCopyNumQueue = 0; } /*============================================================================ ; ; CopyMoveRetry ; ; The following function is used to retry failed move/copy operations ; due to out of disk situations or path not found errors ; on the destination. ; ; NOTE: the destination drive must be removable or this function ; does not make a whole lot of sense ; ; Parameters: ; ; pszDest - Fully qualified path to destination file ; nError - Type of error which occured: DE_NODISKSPACE or DE_PATHNOTFOUND ; ; returns: ; 0 success (destination path has been created) ; != 0 dos error code including DE_OPCANCELLED ; ============================================================================*/ INT NEAR PASCAL CopyMoveRetry(PSTR pszDest, INT nError) { CHAR szReason[128]; /* Error message string */ PSTR pTemp; /* Pointer into filename */ WORD wFlags; /* Message box flags */ INT result; /* Return from MessageBox call */ do { // until the destination path has been created GetWindowText(hdlgProgress, szTitle, sizeof(szTitle)); if (nError == DE_PATHNOTFOUND) { LoadString(hAppInstance, IDS_PATHNOTTHERE, szReason, sizeof(szReason)); /* Note the -1 below here is valid in both SBCS and DBCS because pszDest is fully qualified and the character preceding the file name must be a backslash */ pTemp = FindFileName(pszDest) - 1; *pTemp = 0; wsprintf(szMessage, szReason, (LPSTR)pszDest); *pTemp = '\\'; wFlags = MB_ICONEXCLAMATION | MB_YESNO; } else { wFlags = MB_ICONEXCLAMATION | MB_RETRYCANCEL; LoadString(hAppInstance, IDS_DESTFULL, szMessage, sizeof(szMessage)); } result = MessageBox(hdlgProgress,szMessage,szTitle,wFlags); if (result == IDRETRY || result == IDYES) { // Allow the disk to be formatted if (!IsTheDiskReallyThere(hdlgProgress, pszDest, FUNC_COPY)) return DE_OPCANCELLED; pTemp = FindFileName(pszDest) - 1; *pTemp = 0; result = WF_CreateDirectory(hdlgProgress, pszDest); *pTemp = '\\'; // only as once if creating the destionation failed if (result == DE_OPCANCELLED) return DE_OPCANCELLED; if (result && (nError == DE_PATHNOTFOUND)) return result | ERRORONDEST; } else return DE_OPCANCELLED; } while (result); return 0; // success } BOOL NEAR PASCAL IsSerialDevice(INT hFile) { UNREFERENCED_PARAMETER(hFile); return FALSE; // BUG BUG. How to findout if its a serialdevice }