//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1997 - 1999 // // File: update.cpp // // Authors; // Jeff Saathoff (jeffreys) // // Notes; // SyncMgr integration //-------------------------------------------------------------------------- #include "pch.h" #include "msgbox.h" // CscWin32Message #include "folder.h" #include // OpenOfflineFile #include "cscst.h" // PostToSystray #include "uihooks.h" // Self-host notifications #include "fopendlg.h" // OpenFilesWarningDialog #include "nopin.h" #include "statdlg.h" // ReconnectServers #include "security.h" #include "strings.h" #define RAS_CONNECT_DELAY (10 * 1000) // Maximum length of username #define MAX_USERNAME_CHARS 64 // SYNCTHREADDATA.dwSyncStatus flags #define SDS_SYNC_OUT 0x00000001 // CSCMergeShare #define SDS_SYNC_IN_QUICK 0x00000002 // CSCFillSparseFiles(FALSE) #define SDS_SYNC_IN_FULL 0x00000004 // CSCFillSparseFiles(TRUE) #define SDS_SYNC_FORCE_INWARD 0x00000008 #define SDS_SYNC_RAS_CONNECTED 0x00000010 #define SDS_SYNC_RESTART_MERGE 0x00000020 #define SDS_SYNC_DELETE_DELETE 0x00000040 #define SDS_SYNC_DELETE_RESTORE 0x00000080 #define SDS_SYNC_AUTOCACHE 0x00000100 #define SDS_SYNC_CONFLICT_KEEPLOCAL 0x00000200 #define SDS_SYNC_CONFLICT_KEEPNET 0x00000400 #define SDS_SYNC_CONFLICT_KEEPBOTH 0x00000800 #define SDS_SYNC_STARTED 0x00010000 #define SDS_SYNC_ERROR 0x00020000 #define SDS_SYNC_CANCELLED 0x00040000 #define SDS_SYNC_FILE_SKIPPED 0x00080000 #define SDS_SYNC_DELETE_CONFLICT_MASK (SDS_SYNC_DELETE_DELETE | SDS_SYNC_DELETE_RESTORE) #define SDS_SYNC_FILE_CONFLICT_MASK (SDS_SYNC_CONFLICT_KEEPLOCAL | SDS_SYNC_CONFLICT_KEEPNET | SDS_SYNC_CONFLICT_KEEPBOTH) // Sync Flags used internally by CCscUpdate #define CSC_SYNC_OUT 0x00000001L #define CSC_SYNC_IN_QUICK 0x00000002L #define CSC_SYNC_IN_FULL 0x00000004L #define CSC_SYNC_SETTINGS 0x00000008L #define CSC_SYNC_MAYBOTHERUSER 0x00000010L #define CSC_SYNC_NOTIFY_SYSTRAY 0x00000020L #define CSC_SYNC_LOGOFF 0x00000040L #define CSC_SYNC_LOGON 0x00000080L #define CSC_SYNC_IDLE 0x00000100L #define CSC_SYNC_NONET 0x00000200L #define CSC_SYNC_PINFILES 0x00000400L #define CSC_SYNC_PIN_RECURSE 0x00000800L #define CSC_SYNC_OFWARNINGDONE 0x00001000L #define CSC_SYNC_CANCELLED 0x00002000L #define CSC_SYNC_SHOWUI_ALWAYS 0x00004000L #define CSC_SYNC_IGNORE_ACCESS 0x00008000L #define CSC_SYNC_EFS_PIN_NONE 0x00010000L #define CSC_SYNC_EFS_PIN_ALL 0x00020000L #define CSC_SYNC_RECONNECT 0x00040000L #define CSC_LOCALLY_MODIFIED (FLAG_CSC_COPY_STATUS_DATA_LOCALLY_MODIFIED \ | FLAG_CSC_COPY_STATUS_LOCALLY_DELETED \ | FLAG_CSC_COPY_STATUS_LOCALLY_CREATED) HICON g_hCscIcon = NULL; // Used for marshalling data into the SyncMgr process typedef struct _CSC_UPDATE_DATA { DWORD dwUpdateFlags; DWORD dwFileBufferOffset; } CSC_UPDATE_DATA, *PCSC_UPDATE_DATA; LPTSTR GetErrorText(DWORD dwErr) { UINT idString = (UINT)-1; LPTSTR pszError = NULL; switch (dwErr) { case ERROR_INVALID_NAME: // "Files of this type cannot be made available offline." idString = IDS_CACHING_DISALLOWED; break; } if ((UINT)-1 != idString) { LoadStringAlloc(&pszError, g_hInstance, idString); } else if (NOERROR != dwErr) { FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwErr, 0, (LPTSTR)&pszError, 1, NULL); } return pszError; } //************************************************************* // // CscUpdateCache // // Purpose: Invoke SyncMgr to update the CSC cache // // Parameters: pNamelist - list of files passed to the CSC SyncMgr handler // // // Return: HRESULT // //************************************************************* HRESULT CscUpdateCache(DWORD dwUpdateFlags, CscFilenameList *pfnl) { HRESULT hr; HRESULT hrComInit = E_FAIL; ISyncMgrSynchronizeInvoke *pSyncInvoke = NULL; DWORD dwSyncMgrFlags = 0; ULONG cbDataLength = sizeof(CSC_UPDATE_DATA); PCSC_UPDATE_DATA pUpdateData = NULL; PCSC_NAMELIST_HDR pNamelist = NULL; TraceEnter(TRACE_UPDATE, "CscUpdateCache"); hrComInit = CoInitialize(NULL); hr = CoCreateInstance(CLSID_SyncMgr, NULL, CLSCTX_SERVER, IID_ISyncMgrSynchronizeInvoke, (LPVOID*)&pSyncInvoke); FailGracefully(hr, "Unable to create SyncMgr object"); if (dwUpdateFlags & CSC_UPDATE_SELECTION) { if (NULL == pfnl || (0 == (CSC_UPDATE_SHOWUI_ALWAYS & dwUpdateFlags) && 0 == pfnl->GetShareCount())) ExitGracefully(hr, E_INVALIDARG, "CSC_UPDATE_SELECTION with no selection"); pNamelist = pfnl->CreateListBuffer(); if (!pNamelist) ExitGracefully(hr, E_OUTOFMEMORY, "Unable to create namelist buffer"); cbDataLength += pNamelist->cbSize; } // // Alloc a buffer for the cookie data // pUpdateData = (PCSC_UPDATE_DATA)LocalAlloc(LPTR, cbDataLength); if (!pUpdateData) ExitGracefully(hr, E_OUTOFMEMORY, "LocalAlloc failed"); pUpdateData->dwUpdateFlags = dwUpdateFlags; if (pNamelist) { pUpdateData->dwFileBufferOffset = sizeof(CSC_UPDATE_DATA); CopyMemory(ByteOffset(pUpdateData, pUpdateData->dwFileBufferOffset), pNamelist, pNamelist->cbSize); } if (dwUpdateFlags & CSC_UPDATE_STARTNOW) dwSyncMgrFlags |= SYNCMGRINVOKE_STARTSYNC; // // Start SyncMgr // hr = pSyncInvoke->UpdateItems(dwSyncMgrFlags, CLSID_CscUpdateHandler, cbDataLength, (LPBYTE)pUpdateData); exit_gracefully: if (pNamelist) CscFilenameList::FreeListBuffer(pNamelist); if (pUpdateData) LocalFree(pUpdateData); DoRelease(pSyncInvoke); if (SUCCEEDED(hrComInit)) CoUninitialize(); TraceLeaveResult(hr); } //************************************************************* // // GetNewVersionName // // Purpose: Create unique names for copies of a file // // Parameters: LPTSTR pszUNCPath - fully qualified UNC name of file // LPTSTR pszShare - \\server\share that file lives on // LPTSTR pszDrive - drive mapping to use for net operations // LPTSTR *ppszNewName - filename for new version returned here (must free) // // Return: Win32 error code // //************************************************************* DWORD GetNewVersionName(LPCTSTR pszUNCPath, LPCTSTR pszShare, LPCTSTR pszDrive, LPTSTR *ppszNewName) { DWORD dwErr = NOERROR; LPTSTR pszDriveLetterPath = NULL; LPTSTR pszPath = NULL; LPTSTR pszFile = NULL; LPTSTR pszExt = NULL; LPTSTR pszWildCardName = NULL; TCHAR szUserName[MAX_USERNAME_CHARS]; ULONG nLength; ULONG nMaxVersion = 0; ULONG cOlderVersions = 0; HANDLE hFind = INVALID_HANDLE_VALUE; WIN32_FIND_DATA fd; LPTSTR pszT; TraceEnter(TRACE_UPDATE, "GetNewVersionName"); TraceAssert(pszUNCPath != NULL); TraceAssert(ppszNewName != NULL); *ppszNewName = NULL; // 1. Split the path into components. // 2. Build wildcard name "X:\dir\foo (johndoe v*).txt" // 3. Do a findfirst/findnext loop to get the min & max version # // and count the number of old versions. // 4. Increment the max version # and build the new filename as: // "foo (johndoe v).txt" // Assume that the UNC name contains more than the share TraceAssert(!StrCmpNI(pszUNCPath, pszShare, lstrlen(pszShare))); TraceAssert(lstrlen(pszUNCPath) > lstrlen(pszShare)); // Copy the path (without \\server\share) if (!LocalAllocString(&pszPath, pszUNCPath + lstrlen(pszShare))) ExitGracefully(dwErr, ERROR_OUTOFMEMORY, "LocalAllocString failed"); // Find the file part of the name pszT = PathFindFileName(pszPath); if (!pszT) ExitGracefully(dwErr, ERROR_INVALID_PARAMETER, "Incomplete path"); // Copy the filename if (!LocalAllocString(&pszFile, pszT)) ExitGracefully(dwErr, ERROR_OUTOFMEMORY, "LocalAllocString failed"); // Look for the file extension pszT = PathFindExtension(pszFile); if (pszT) { // Copy the extension and truncate the file root at this point LocalAllocString(&pszExt, pszT); *pszT = TEXT('\0'); } // Truncate the path PathRemoveFileSpec(pszPath); // Get the user name nLength = ARRAYSIZE(szUserName); if (!GetUserName(szUserName, &nLength)) LoadString(g_hInstance, IDS_UNKNOWN_USER, szUserName, ARRAYSIZE(szUserName)); // Build the wildcard path "X:\dir\foo (johndoe v*).txt" nLength = FormatStringID(&pszWildCardName, g_hInstance, IDS_VERSION_FORMAT, pszFile, szUserName, c_szStar, pszExt); if (!nLength) ExitGracefully(dwErr, GetLastError(), "Unable to format string"); nLength += lstrlen(pszUNCPath) + lstrlen(szUserName); pszDriveLetterPath = (LPTSTR)LocalAlloc(LPTR, MAX(nLength, ULONG(MAX_PATH)) * sizeof(TCHAR)); if (!pszDriveLetterPath) ExitGracefully(dwErr, ERROR_OUTOFMEMORY, "LocalAlloc failed"); PathCombine(pszDriveLetterPath, pszDrive, pszPath); PathAppend(pszDriveLetterPath, pszWildCardName); nLength = (ULONG)(StrStr(pszWildCardName, c_szStar) - pszWildCardName); // remember where the '*' is // Search for existing versions of the file with this username hFind = FindFirstFile(pszDriveLetterPath, &fd); if (hFind != INVALID_HANDLE_VALUE) { ULONG nVersion; do { nVersion = StrToLong(&fd.cFileName[nLength]); if (nVersion > nMaxVersion) { nMaxVersion = nVersion; } cOlderVersions++; } while (FindNextFile(hFind, &fd)); FindClose(hFind); } // Build the new file name to return to the caller. // This one is version nMaxVersion+1. ULongToString(nMaxVersion+1, pszDriveLetterPath, lstrlen(pszDriveLetterPath)); nLength = FormatStringID(ppszNewName, g_hInstance, IDS_VERSION_FORMAT, pszFile, szUserName, pszDriveLetterPath, pszExt); if (!nLength) ExitGracefully(dwErr, GetLastError(), "Unable to format string"); exit_gracefully: LocalFreeString(&pszDriveLetterPath); LocalFreeString(&pszPath); LocalFreeString(&pszFile); LocalFreeString(&pszExt); LocalFreeString(&pszWildCardName); if (NOERROR != dwErr) { LocalFreeString(ppszNewName); } TraceLeaveValue(dwErr); } //************************************************************* // // ConflictDlgCallback // // Purpose: Display local or remote file from conflict dialog // // Parameters: hWnd - conflict dialog handle (used as parent for UI) // uMsg - one of RFCCM_* // wParam - depends on uMsg (unused) // lParam - pointer to context data (RFCDLGPARAM) // // // Return: TRUE on success, FALSE otherwise // //************************************************************* typedef struct _CONFLICT_DATA { LPCTSTR pszShare; LPCTSTR pszDrive; } CONFLICT_DATA; BOOL ConflictDlgCallback(HWND hWnd, UINT uMsg, WPARAM /*wParam*/, LPARAM lParam) { RFCDLGPARAM *pdlgParam = (RFCDLGPARAM*)lParam; CONFLICT_DATA cd = {0}; LPTSTR pszTmpName = NULL; ULONG cchShare = 0; LPTSTR szFile; DWORD dwErr = NOERROR; TraceEnter(TRACE_UPDATE, "ConflictDlgCallback"); if (NULL == pdlgParam) { TraceAssert(FALSE); TraceLeaveValue(FALSE); } szFile = (LPTSTR)LocalAlloc(LMEM_FIXED, MAX(StringByteSize(pdlgParam->pszLocation) + StringByteSize(pdlgParam->pszFilename), MAX_PATH_BYTES)); if (!szFile) TraceLeaveValue(FALSE); if (pdlgParam->lCallerData) cd = *(CONFLICT_DATA*)pdlgParam->lCallerData; if (cd.pszShare) cchShare = lstrlen(cd.pszShare); switch (uMsg) { case RFCCM_VIEWLOCAL: // Build UNC path and view what's in the cache PathCombine(szFile, pdlgParam->pszLocation, pdlgParam->pszFilename); dwErr = OpenOfflineFile(szFile); break; case RFCCM_VIEWNETWORK: // Build drive letter (non-UNC) path and ShellExecute it PathCombine(szFile, cd.pszDrive, pdlgParam->pszLocation + cchShare); PathAppend(szFile, pdlgParam->pszFilename); { SHELLEXECUTEINFO si = {0}; si.cbSize = sizeof(si); si.fMask = SEE_MASK_FLAG_NO_UI; si.hwnd = hWnd; si.lpFile = szFile; si.nShow = SW_NORMAL; Trace((TEXT("ShellExecuting \"%s\""), szFile)); if (!ShellExecuteEx(&si)) dwErr = GetLastError(); } break; } if (NOERROR != dwErr) CscWin32Message(hWnd, dwErr, CSCUI::SEV_ERROR); LocalFree(szFile); TraceLeaveValue(TRUE); } //************************************************************* // // ShowConflictDialog // // Purpose: Invoke the conflict resolution dialog // // Parameters: hWndParent - dialog parent window // pszUNCPath - full UNC of file that conflicts // pszNewName - filespec to use for new copy of file (e.g. "foo (johndoe v1).txt" // pszShare - "\\server\share" // pszDrive - "X:" drive mapping of remote connection // pfdLocal - Information about local file // pfdRemote - Information about remote file // // // Return: HRESULT // //************************************************************* typedef int (WINAPI *PFNSYNCMGRRESOLVECONFLICT)(HWND hWndParent, RFCDLGPARAM *pdlgParam); TCHAR const c_szSyncMgrDll[] = TEXT("mobsync.dll"); #ifdef UNICODE CHAR const c_szResolveConflict[] = "SyncMgrResolveConflictW"; #else CHAR const c_szResolveConflict[] = "SyncMgrResolveConflictA"; #endif BOOL FileHasAssociation(LPCTSTR pszFile) { HRESULT hr = HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION); if (pszFile) { pszFile = PathFindExtension(pszFile); if (pszFile && *pszFile) { IQueryAssociations *pAssoc = NULL; hr = AssocCreate(CLSID_QueryAssociations, IID_IQueryAssociations, (LPVOID*)&pAssoc); if (SUCCEEDED(hr)) { hr = pAssoc->Init(ASSOCF_IGNOREBASECLASS, pszFile, NULL, NULL); pAssoc->Release(); } } } return SUCCEEDED(hr); } int ShowConflictDialog(HWND hWndParent, LPCTSTR pszUNCPath, LPCTSTR pszNewName, LPCTSTR pszShare, LPCTSTR pszDrive, LPWIN32_FIND_DATA pfdLocal, LPWIN32_FIND_DATA pfdRemote) { int nResult = 0; TCHAR szUser[MAX_USERNAME_CHARS]; LPTSTR pszPath = NULL; LPTSTR pszFile = NULL; TCHAR szRemoteDate[MAX_PATH]; TCHAR szLocalDate[MAX_PATH]; ULONG nLength; SYSTEMTIME st; RFCDLGPARAM dp = {0}; CONFLICT_DATA cd; BOOL bLocalIsDir = FALSE; BOOL bRemoteIsDir = FALSE; static PFNSYNCMGRRESOLVECONFLICT pfnResolveConflict = NULL; TraceEnter(TRACE_UPDATE, "ShowConflictDialog"); TraceAssert(pszUNCPath); if (NULL == pfnResolveConflict) { // The CSC Update handler is loaded by SyncMgr, so assume the SyncMgr // dll is already loaded. We don't want to link to the LIB to keep // SyncMgr from loading every time our context menu or icon overlay // handler is loaded (for example). HMODULE hSyncMgrDll = GetModuleHandle(c_szSyncMgrDll); if (NULL != hSyncMgrDll) pfnResolveConflict = (PFNSYNCMGRRESOLVECONFLICT)GetProcAddress(hSyncMgrDll, c_szResolveConflict); if (NULL == pfnResolveConflict) return 0; } TraceAssert(NULL != pfnResolveConflict); szUser[0] = TEXT('\0'); nLength = ARRAYSIZE(szUser); GetUserName(szUser, &nLength); szRemoteDate[0] = TEXT('\0'); if (NULL != pfdRemote) { DWORD dwFlags = FDTF_DEFAULT; SHFormatDateTime(&pfdRemote->ftLastWriteTime, &dwFlags, szRemoteDate, ARRAYSIZE(szRemoteDate)); if (pfdRemote->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) bRemoteIsDir = TRUE; } szLocalDate[0] = TEXT('\0'); if (NULL != pfdLocal) { DWORD dwFlags = FDTF_DEFAULT; SHFormatDateTime(&pfdLocal->ftLastWriteTime, &dwFlags, szLocalDate, ARRAYSIZE(szLocalDate)); if (pfdLocal->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) bLocalIsDir = TRUE; } if (!LocalAllocString(&pszPath, pszUNCPath)) ExitGracefully(nResult, 0, "LocalAllocString failed"); pszFile = PathFindFileName(pszUNCPath); PathRemoveFileSpec(pszPath); dp.dwFlags = RFCF_APPLY_ALL; dp.pszFilename = pszFile; dp.pszLocation = pszPath; dp.pszNewName = pszNewName; dp.pszNetworkModifiedBy = NULL; dp.pszLocalModifiedBy = szUser; dp.pszNetworkModifiedOn = szRemoteDate; dp.pszLocalModifiedOn = szLocalDate; dp.pfnCallBack = NULL; dp.lCallerData = 0; // Only turn on the View buttons (set a callback) if we're // dealing with files that have associations. if (!(bLocalIsDir || bRemoteIsDir) && FileHasAssociation(pszFile)) { // Save both the share name and drive letter for building paths to view files cd.pszShare = pszShare; cd.pszDrive = pszDrive; dp.pfnCallBack = ConflictDlgCallback; dp.lCallerData = (LPARAM)&cd; } nResult = (*pfnResolveConflict)(hWndParent, &dp); exit_gracefully: LocalFreeString(&pszPath); // No need to free pszFile TraceLeaveValue(nResult); } /////////////////////////////////////////////////////////////////////////////// // // // SyncMgr integration implementation // // // /////////////////////////////////////////////////////////////////////////////// CCscUpdate::CCscUpdate() : m_cRef(1), m_ShareLog(HKEY_CURRENT_USER, c_szCSCShareKey), m_pSyncMgrCB(NULL), m_hSyncThreads(NULL), m_pFileList(NULL), m_hSyncItems(NULL), m_hwndDlgParent(NULL), m_hSyncInProgMutex(NULL), m_pConflictPinList(NULL), m_pSilentFolderList(NULL), m_pSpecialFolderList(NULL), m_bCacheIsEncrypted(IsCacheEncrypted(NULL)) { DllAddRef(); InitializeCriticalSection(&m_csThreadList); if (!g_hCscIcon) g_hCscIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_CSCUI_ICON)); m_hSyncMutex = CreateMutex(NULL, FALSE, c_szSyncMutex); } CCscUpdate::~CCscUpdate() { TraceEnter(TRACE_UPDATE, "CCscUpdate::~CCscUpdate"); SyncCompleted(); TraceAssert(NULL == m_hSyncInProgMutex); // We should never get here while a sync thread is still running TraceAssert(NULL == m_hSyncThreads || 0 == DPA_GetPtrCount(m_hSyncThreads)); if (NULL != m_hSyncThreads) DPA_Destroy(m_hSyncThreads); DeleteCriticalSection(&m_csThreadList); if (NULL != m_hSyncItems) DSA_Destroy(m_hSyncItems); DoRelease(m_pSyncMgrCB); delete m_pFileList; delete m_pConflictPinList; delete m_pSilentFolderList; delete m_pSpecialFolderList; if (NULL != m_hSyncMutex) CloseHandle(m_hSyncMutex); DllRelease(); TraceLeaveVoid(); } HRESULT WINAPI CCscUpdate::CreateInstance(REFIID riid, LPVOID *ppv) { HRESULT hr; CCscUpdate *pThis; TraceEnter(TRACE_UPDATE, "CCscUpdate::CreateInstance"); TraceAssert(IsCSCEnabled()); pThis = new CCscUpdate; if (pThis) { hr = pThis->QueryInterface(riid, ppv); pThis->Release(); } else hr = E_OUTOFMEMORY; TraceLeaveResult(hr); } /////////////////////////////////////////////////////////////////////////////// // // // SyncMgr integration implementation (IUnknown) // // // /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP CCscUpdate::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CCscUpdate, ISyncMgrSynchronize), { 0 }, }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_(ULONG) CCscUpdate::AddRef() { return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) CCscUpdate::Release() { if (InterlockedDecrement(&m_cRef)) return m_cRef; delete this; return 0; } /////////////////////////////////////////////////////////////////////////////// // // // Sync Manager integration implementation (ISyncMgrSynchronize) // // // /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP CCscUpdate::Initialize(DWORD /*dwReserved*/, DWORD dwSyncFlags, DWORD cbCookie, const BYTE *pCookie) { HRESULT hr = S_OK; HKEY hkCSC; BOOL bNoNet = TRUE; TraceEnter(TRACE_UPDATE, "CCscUpdate::Initialize"); TraceAssert(IsCSCEnabled()); if (!(SYNCMGRFLAG_SETTINGS & dwSyncFlags) && ::IsSyncInProgress()) { // // We need to guard against running multiple syncs at the // same time. User notification in the UI is handled where // the UI code calls CscUpdate(). This is so that the UI // message contains the proper context with respect to what // the user is doing. // TraceLeaveResult(E_FAIL); } m_dwSyncFlags = 0; delete m_pFileList; m_pFileList = NULL; delete m_pConflictPinList; m_pConflictPinList = NULL; // We used to get the tray status to check for NoNet, but // there's a timing problem at logon (the tray window may not // be created yet). So ask RDR instead. If this call fails, // then RDR must be dead, so bNoNet defaults to TRUE. CSCIsServerOffline(NULL, &bNoNet); switch (dwSyncFlags & SYNCMGRFLAG_EVENTMASK) { case SYNCMGRFLAG_CONNECT: // Logon if (bNoNet) ExitGracefully(hr, E_FAIL, "No Logon sync when no net"); m_dwSyncFlags = CSC_SYNC_OUT | CSC_SYNC_LOGON | CSC_SYNC_NOTIFY_SYSTRAY; // | CSC_SYNC_RECONNECT; if (CConfig::eSyncFull == CConfig::GetSingleton().SyncAtLogon()) { m_dwSyncFlags |= CSC_SYNC_IN_FULL; } break; case SYNCMGRFLAG_PENDINGDISCONNECT: // Logoff if (bNoNet) ExitGracefully(hr, E_FAIL, "No Logoff sync when no net"); m_dwSyncFlags = CSC_SYNC_LOGOFF; if (CConfig::eSyncFull == CConfig::GetSingleton().SyncAtLogoff()) m_dwSyncFlags |= CSC_SYNC_OUT | CSC_SYNC_IN_FULL; else m_dwSyncFlags |= CSC_SYNC_IN_QUICK; break; case SYNCMGRFLAG_INVOKE: // CscUpdateCache if (pCookie != NULL && cbCookie > 0) { PCSC_UPDATE_DATA pUpdateData = (PCSC_UPDATE_DATA)pCookie; TraceAssert(cbCookie >= sizeof(CSC_UPDATE_DATA)); DWORD dwUpdateFlags = pUpdateData->dwUpdateFlags; if (dwUpdateFlags & CSC_UPDATE_SELECTION) { TraceAssert(cbCookie > sizeof(CSC_UPDATE_DATA)); // Create the filelist from the selection provided m_pFileList = new CscFilenameList((PCSC_NAMELIST_HDR)ByteOffset(pUpdateData, pUpdateData->dwFileBufferOffset), true); if (!m_pFileList) ExitGracefully(hr, E_OUTOFMEMORY, "Unable to create CscFilenameList object"); if (!m_pFileList->IsValid()) ExitGracefully(hr, E_FAIL, "Unable to initialize CscFilenameList object"); if (CSC_UPDATE_SHOWUI_ALWAYS & dwUpdateFlags) { m_dwSyncFlags |= CSC_SYNC_SHOWUI_ALWAYS; } else if (0 == m_pFileList->GetShareCount()) ExitGracefully(hr, E_UNEXPECTED, "CSC_UPDATE_SELECTION with no selection"); } if (dwUpdateFlags & CSC_UPDATE_RECONNECT) { m_dwSyncFlags |= CSC_SYNC_RECONNECT; } if (dwUpdateFlags & CSC_UPDATE_UNATTENDED) { dwSyncFlags &= ~SYNCMGRFLAG_MAYBOTHERUSER; } if (dwUpdateFlags & CSC_UPDATE_NOTIFY_DONE) { // // Caller of CscUpdateCache want's systray notification // when sync is complete. // m_dwSyncFlags |= CSC_SYNC_NOTIFY_SYSTRAY; } if (dwUpdateFlags & CSC_UPDATE_FILL_ALL) m_dwSyncFlags |= CSC_SYNC_IN_FULL; else if (dwUpdateFlags & CSC_UPDATE_FILL_QUICK) m_dwSyncFlags |= CSC_SYNC_IN_QUICK; if (dwUpdateFlags & CSC_UPDATE_REINT) m_dwSyncFlags |= CSC_SYNC_OUT; if (dwUpdateFlags & CSC_UPDATE_PIN_RECURSE) m_dwSyncFlags |= CSC_SYNC_PINFILES | CSC_SYNC_PIN_RECURSE | CSC_SYNC_IN_QUICK; else if (dwUpdateFlags & CSC_UPDATE_PINFILES) m_dwSyncFlags |= CSC_SYNC_PINFILES | CSC_SYNC_IN_QUICK; if (dwUpdateFlags & CSC_UPDATE_IGNORE_ACCESS) m_dwSyncFlags |= CSC_SYNC_IGNORE_ACCESS; } break; case SYNCMGRFLAG_IDLE: // Auto-sync at idle time if (bNoNet) ExitGracefully(hr, E_FAIL, "No idle sync when no net"); m_dwSyncFlags = CSC_SYNC_OUT | CSC_SYNC_IN_QUICK | CSC_SYNC_IDLE | CSC_SYNC_NOTIFY_SYSTRAY; break; case SYNCMGRFLAG_MANUAL: // Run "mobsync.exe" m_dwSyncFlags = CSC_SYNC_OUT | CSC_SYNC_IN_FULL | CSC_SYNC_NOTIFY_SYSTRAY | CSC_SYNC_RECONNECT; break; case SYNCMGRFLAG_SCHEDULED: // User scheduled sync m_dwSyncFlags = CSC_SYNC_OUT | CSC_SYNC_IN_FULL | CSC_SYNC_NOTIFY_SYSTRAY; break; } if (!(m_dwSyncFlags & CSC_SYNC_PINFILES)) m_dwSyncFlags |= CSC_SYNC_EFS_PIN_NONE; // skip EFS if not pinning if (dwSyncFlags & SYNCMGRFLAG_SETTINGS) m_dwSyncFlags |= CSC_SYNC_SETTINGS; if (!m_dwSyncFlags) ExitGracefully(hr, E_UNEXPECTED, "Nothing to do"); if (dwSyncFlags & SYNCMGRFLAG_MAYBOTHERUSER) m_dwSyncFlags |= CSC_SYNC_MAYBOTHERUSER; if (bNoNet) m_dwSyncFlags |= CSC_SYNC_NONET; hr = GetSilentFolderList(); if (FAILED(hr)) { m_dwSyncFlags = 0; } exit_gracefully: TraceLeaveResult(hr); } STDMETHODIMP CCscUpdate::GetHandlerInfo(LPSYNCMGRHANDLERINFO *ppSyncMgrHandlerInfo) { HRESULT hr = S_OK; LPSYNCMGRHANDLERINFO pHandlerInfo; TraceEnter(TRACE_UPDATE, "CCscUpdate::GetHandlerInfo"); if (NULL == ppSyncMgrHandlerInfo) TraceLeaveResult(E_INVALIDARG); *ppSyncMgrHandlerInfo = NULL; pHandlerInfo = (LPSYNCMGRHANDLERINFO)CoTaskMemAlloc(sizeof(SYNCMGRHANDLERINFO)); if (NULL == pHandlerInfo) ExitGracefully(hr, E_OUTOFMEMORY, "LocalAlloc failed"); pHandlerInfo->cbSize = sizeof(SYNCMGRHANDLERINFO); pHandlerInfo->hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_CSCUI_ICON)); pHandlerInfo->SyncMgrHandlerFlags = (m_dwSyncFlags & CSC_SYNC_LOGOFF) ? 0 : (SYNCMGRHANDLER_HASPROPERTIES | SYNCMGRHANDLER_MAYESTABLISHCONNECTION); LoadStringW(g_hInstance, IDS_APPLICATION, pHandlerInfo->wszHandlerName, ARRAYSIZE(pHandlerInfo->wszHandlerName)); *ppSyncMgrHandlerInfo = pHandlerInfo; exit_gracefully: TraceLeaveResult(hr); } STDMETHODIMP CCscUpdate::EnumSyncMgrItems(LPSYNCMGRENUMITEMS *ppenum) { HRESULT hr; PUPDATEENUM pNewEnum; TraceEnter(TRACE_UPDATE, "CCscUpdate::EnumSyncMgrItems"); *ppenum = NULL; pNewEnum = new CUpdateEnumerator(this); if (pNewEnum) { hr = pNewEnum->QueryInterface(IID_ISyncMgrEnumItems, (LPVOID*)ppenum); pNewEnum->Release(); } else hr = E_OUTOFMEMORY; TraceLeaveResult(hr); } STDMETHODIMP CCscUpdate::GetItemObject(REFSYNCMGRITEMID /*rItemID*/, REFIID /*riid*/, LPVOID * /*ppv*/) { return E_NOTIMPL; } STDMETHODIMP CCscUpdate::ShowProperties(HWND hWndParent, REFSYNCMGRITEMID rItemID) { CSCEntry *pShareEntry; LPCTSTR pszShareName = TEXT(""); pShareEntry = m_ShareLog.Get(rItemID); // We don't enumerate shares to SyncMgr unless a share entry // exists in the registry, so m_ShareLog.Get should never fail here. if (pShareEntry) pszShareName = pShareEntry->Name(); COfflineFilesFolder::Open(); // Notify SyncMgr that the ShowProperties is done. if (NULL != m_pSyncMgrCB) m_pSyncMgrCB->ShowPropertiesCompleted(S_OK); return S_OK; } STDMETHODIMP CCscUpdate::SetProgressCallback(LPSYNCMGRSYNCHRONIZECALLBACK pCallback) { TraceEnter(TRACE_UPDATE, "CCscUpdate::SetProgressCallback"); DoRelease(m_pSyncMgrCB); m_pSyncMgrCB = pCallback; if (m_pSyncMgrCB) m_pSyncMgrCB->AddRef(); TraceLeaveResult(S_OK); } STDMETHODIMP CCscUpdate::PrepareForSync(ULONG cNumItems, SYNCMGRITEMID *pItemID, HWND /*hWndParent*/, DWORD /*dwReserved*/) { HRESULT hr = S_OK; TraceEnter(TRACE_UPDATE, "CCscUpdate::PrepareForSync"); TraceAssert(0 != cNumItems); TraceAssert(NULL != pItemID); // // Copy the list of item ID's // if (NULL == m_hSyncItems) { m_hSyncItems = DSA_Create(sizeof(SYNCMGRITEMID), 4); if (NULL == m_hSyncItems) ExitGracefully(hr, E_OUTOFMEMORY, "Unable to create DSA for SYNCMGRITEMID list"); } else DSA_DeleteAllItems(m_hSyncItems); while (cNumItems--) DSA_AppendItem(m_hSyncItems, pItemID++); exit_gracefully: // ISyncMgrSynchronize::PrepareForSync is now an asynchronous call // so we could create another thread to do the work and return from // this call immediately. However, since all we do is copy the list // of Item IDs, let's do it here and call // m_pSyncMgrCB->PrepareForSyncCompleted before returning. if (NULL != m_pSyncMgrCB) m_pSyncMgrCB->PrepareForSyncCompleted(hr); TraceLeaveResult(hr); } STDMETHODIMP CCscUpdate::Synchronize(HWND hWndParent) { HRESULT hr = E_FAIL; ULONG cItems = 0; BOOL bConnectionEstablished = FALSE; TraceEnter(TRACE_UPDATE, "CCscUpdate::Synchronize"); if (NULL != m_hSyncItems) cItems = DSA_GetItemCount(m_hSyncItems); // // Don't want systray UI updates while syncing. // Whenever the systray UI is updated, the code checks first // for this global mutex object. If it's non-signaled, the // systray knows there's a sync in progress and the UI isn't // updated. // TraceAssert(NULL == m_hSyncInProgMutex); m_hSyncInProgMutex = CreateMutex(NULL, TRUE, c_szSyncInProgMutex); if (0 == cItems) { ExitGracefully(hr, E_UNEXPECTED, "Nothing to synchronize"); } else if (1 == cItems) { SYNCMGRITEMID *pItemID = (SYNCMGRITEMID*)DSA_GetItemPtr(m_hSyncItems, 0); if (NULL != pItemID && IsEqualGUID(GUID_CscNullSyncItem, *pItemID)) { // // A single item in the DSA and it's our "null sync" GUID. // This means we really have nothing to sync but the invoker // of the sync wants to see some SyncMgr progress UI. In // this scenario the update item enumerator already enumerated // the "null sync" item. Here we set this single item's progress // UI info to 100% complete and skip any sync activity. // SYNCMGRPROGRESSITEM spi = {0}; spi.mask = SYNCMGRPROGRESSITEM_STATUSTYPE | SYNCMGRPROGRESSITEM_STATUSTEXT | SYNCMGRPROGRESSITEM_PROGVALUE | SYNCMGRPROGRESSITEM_MAXVALUE; spi.cbSize = sizeof(spi); spi.dwStatusType = SYNCMGRSTATUS_SUCCEEDED; spi.lpcStatusText = L" "; spi.iProgValue = 1; spi.iMaxValue = 1; m_pSyncMgrCB->Progress(GUID_CscNullSyncItem, &spi); m_pSyncMgrCB->SynchronizeCompleted(S_OK); if (CSC_SYNC_RECONNECT & m_dwSyncFlags) { // // We have nothing to sync but one or more servers // may still be offline. The user's expectation is that the // sync will transition these to online regardless of link // speed. Add them to the "reconnect" list. // _BuildOfflineShareList(&m_ReconnectList); } ExitGracefully(hr, NOERROR, "Nothing to sync. Progress UI displayed"); } } m_hwndDlgParent = hWndParent; // We can pin autocached files without a net (no sync required); // otherwise we need to establish a RAS connection to do anything. if ((m_dwSyncFlags & CSC_SYNC_NONET) && !(m_dwSyncFlags & CSC_SYNC_PINFILES)) { hr = m_pSyncMgrCB->EstablishConnection(NULL, 0); FailGracefully(hr, "Unable to establish RAS connection"); bConnectionEstablished = TRUE; } // For each share, kick off a thread to do the work while (cItems > 0) { SYNCMGRITEMID *pItemID; CSCEntry *pShareEntry; --cItems; pItemID = (SYNCMGRITEMID*)DSA_GetItemPtr(m_hSyncItems, cItems); pShareEntry = m_ShareLog.Get(*pItemID); // We don't enumerate shares to SyncMgr unless a share entry // exists in the registry, so m_ShareLog.Get should never fail here. if (NULL == pShareEntry) ExitGracefully(hr, E_UNEXPECTED, "No share entry"); hr = SynchronizeShare(pItemID, pShareEntry->Name(), bConnectionEstablished); DSA_DeleteItem(m_hSyncItems, cItems); FailGracefully(hr, "Unable to create sync thread"); } TraceAssert(0 == DSA_GetItemCount(m_hSyncItems)); exit_gracefully: if (FAILED(hr)) SetItemStatus(GUID_NULL, SYNCMGRSTATUS_STOPPED); TraceLeaveResult(hr); } // // Try to reconnect any server that is currently offline. // void CCscUpdate::_BuildOfflineShareList( CscFilenameList *pfnl ) { WIN32_FIND_DATA fd; DWORD dwStatus = 0; CCscFindHandle hFind = CacheFindFirst(NULL, &fd, &dwStatus, NULL, NULL, NULL); if (hFind.IsValid()) { do { if (FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwStatus) { CscFilenameList::HSHARE hShare; pfnl->AddShare(fd.cFileName, &hShare); } } while(CacheFindNext(hFind, &fd, &dwStatus, NULL, NULL, NULL)); } } STDMETHODIMP CCscUpdate::SetItemStatus(REFSYNCMGRITEMID rItemID, DWORD dwSyncMgrStatus) { HRESULT hr = E_FAIL; ULONG cItems; BOOL bAllItems; TraceEnter(TRACE_UPDATE, "CCscUpdate::SetItemStatus"); if (SYNCMGRSTATUS_SKIPPED != dwSyncMgrStatus && SYNCMGRSTATUS_STOPPED != dwSyncMgrStatus) TraceLeaveResult(E_NOTIMPL); bAllItems = FALSE; if (SYNCMGRSTATUS_STOPPED == dwSyncMgrStatus) { bAllItems = TRUE; m_dwSyncFlags |= CSC_SYNC_CANCELLED; } // SetItemStatus can be called between PrepareForSync and Synchronize, in // in which case the correct thing to do is remove the item from m_hSyncItems. if (NULL != m_hSyncItems) { cItems = DSA_GetItemCount(m_hSyncItems); while (cItems > 0) { SYNCMGRITEMID *pItemID; --cItems; pItemID = (SYNCMGRITEMID*)DSA_GetItemPtr(m_hSyncItems, cItems); if (bAllItems || (NULL != pItemID && IsEqualGUID(rItemID, *pItemID))) { // Remove the item from the list of items to sync DSA_DeleteItem(m_hSyncItems, cItems); if (!bAllItems) ExitGracefully(hr, S_OK, "Skipping item"); } } } // Lookup the thread for the item ID and set its status // to cause it to terminate. hr = SetSyncThreadStatus(SyncStop, bAllItems ? GUID_NULL : rItemID); exit_gracefully: TraceLeaveResult(hr); } STDMETHODIMP CCscUpdate::ShowError(HWND /*hWndParent*/ , REFSYNCMGRERRORID /*ErrorID*/) { return E_NOTIMPL; } HRESULT CCscUpdate::SynchronizeShare(SYNCMGRITEMID *pItemID, LPCTSTR pszShareName, BOOL bRasConnected) { HRESULT hr = S_OK; DWORD dwThreadID; PSYNCTHREADDATA pThreadData; ULONG cbShareName = 0; TraceEnter(TRACE_UPDATE, "CCscUpdate::SynchronizeShare"); TraceAssert(NULL != pItemID); TraceAssert(NULL != pszShareName); TraceAssert(*pszShareName); EnterCriticalSection(&m_csThreadList); if (NULL == m_hSyncThreads) m_hSyncThreads = DPA_Create(4); LeaveCriticalSection(&m_csThreadList); if (NULL == m_hSyncThreads) ExitGracefully(hr, E_OUTOFMEMORY, "Unable to create DPA for threads"); cbShareName = StringByteSize(pszShareName); pThreadData = (PSYNCTHREADDATA)LocalAlloc(LPTR, sizeof(SYNCTHREADDATA) + cbShareName); if (!pThreadData) ExitGracefully(hr, E_OUTOFMEMORY, "LocalAlloc failed"); pThreadData->pThis = this; pThreadData->ItemID = *pItemID; pThreadData->pszShareName = (LPTSTR)(pThreadData + 1); CopyMemory(pThreadData->pszShareName, pszShareName, cbShareName); // // If we established a RAS connection, then it will go away // right after the sync completes, so there's no point trying // to reconnect. That is, only check CSC_SYNC_RECONNECT and // add the share to the reconnect list if we aren't doing RAS. // if (bRasConnected) { pThreadData->dwSyncStatus |= SDS_SYNC_RAS_CONNECTED; } else if (m_dwSyncFlags & CSC_SYNC_RECONNECT) { CscFilenameList::HSHARE hShare; m_ReconnectList.AddShare(pszShareName, &hShare); } // Give the thread a reference to this object and the DLL AddRef(); LoadLibrary(c_szDllName); pThreadData->hThread = CreateThread(NULL, 0, _SyncThread, pThreadData, CREATE_SUSPENDED, &dwThreadID); if (NULL != pThreadData->hThread) { EnterCriticalSection(&m_csThreadList); DPA_AppendPtr(m_hSyncThreads, pThreadData); LeaveCriticalSection(&m_csThreadList); ResumeThread(pThreadData->hThread); } else { DWORD dwErr = GetLastError(); LocalFree(pThreadData); LPTSTR pszErr = GetErrorText(GetLastError()); LogError(*pItemID, SYNCMGRLOGLEVEL_ERROR, IDS_FILL_SPARSE_FILES_ERROR, pszShareName, pszErr); LocalFreeString(&pszErr); hr = HRESULT_FROM_WIN32(dwErr); Release(); FreeLibrary(g_hInstance); } exit_gracefully: TraceLeaveResult(hr); } void CCscUpdate::SetLastSyncTime(LPCTSTR pszShareName) { HKEY hKey = NULL; hKey = m_ShareLog.OpenKey(pszShareName, KEY_SET_VALUE); if (hKey) { FILETIME ft = {0}; GetSystemTimeAsFileTime(&ft); RegSetValueEx(hKey, c_szLastSync, 0, REG_BINARY, (LPBYTE)&ft, sizeof(ft)); RegCloseKey(hKey); } } DWORD CCscUpdate::GetLastSyncTime(LPCTSTR pszShareName, LPFILETIME pft) { DWORD dwResult = ERROR_PATH_NOT_FOUND; HKEY hKey = NULL; hKey = m_ShareLog.OpenKey(pszShareName, KEY_QUERY_VALUE); if (hKey) { DWORD dwSize = sizeof(*pft); dwResult = RegQueryValueEx(hKey, c_szLastSync, NULL, NULL, (LPBYTE)pft, &dwSize); RegCloseKey(hKey); } return dwResult; } void CCscUpdate::SyncThreadCompleted(PSYNCTHREADDATA pSyncData) { int iThread; TraceEnter(TRACE_UPDATE, "CCscUpdate::SyncThreadCompleted"); TraceAssert(NULL != pSyncData); TraceAssert(NULL != m_hSyncThreads); EnterCriticalSection(&m_csThreadList); iThread = DPA_GetPtrIndex(m_hSyncThreads, pSyncData); TraceAssert(-1 != iThread); DPA_DeletePtr(m_hSyncThreads, iThread); CloseHandle(pSyncData->hThread); pSyncData->hThread = NULL; iThread = DPA_GetPtrCount(m_hSyncThreads); LeaveCriticalSection(&m_csThreadList); if (0 == iThread) { SyncCompleted(); } TraceLeaveVoid(); } void CCscUpdate::SyncCompleted(void) { if ((m_dwSyncFlags & CSC_SYNC_RECONNECT) && !(m_dwSyncFlags & CSC_SYNC_CANCELLED)) { m_dwSyncFlags &= ~CSC_SYNC_RECONNECT; ReconnectServers(&m_ReconnectList, FALSE, FALSE); } if (NULL != m_hSyncInProgMutex) { // We're not syncing so reset the global event ReleaseMutex(m_hSyncInProgMutex); CloseHandle(m_hSyncInProgMutex); m_hSyncInProgMutex = NULL; } if (m_dwSyncFlags & CSC_SYNC_NOTIFY_SYSTRAY) { // Notify systray that we're done PostToSystray(CSCWM_DONESYNCING, 0, 0); m_dwSyncFlags &= ~CSC_SYNC_NOTIFY_SYSTRAY; } // Notify SyncMgr that the sync is done if (NULL != m_pSyncMgrCB) { m_pSyncMgrCB->SynchronizeCompleted(S_OK); } HANDLE hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, c_szSyncCompleteEvent); if (NULL != hEvent) { // // Let anyone interested know that the sync is complete. // SetEvent(hEvent); CloseHandle(hEvent); } } UINT GetErrorFormat(DWORD dwErr, BOOL bMerging = FALSE) { UINT idString = 0; // These are all just initial guesses. Not sure // which error codes we'll get from CSC. switch (dwErr) { case ERROR_DISK_FULL: // "The server disk is full." // "The local disk is full." idString = bMerging ? IDS_SERVER_FULL_ERROR : IDS_LOCAL_DISK_FULL_ERROR; break; case ERROR_LOCK_VIOLATION: case ERROR_SHARING_VIOLATION: case ERROR_OPEN_FILES: case ERROR_ACTIVE_CONNECTIONS: case ERROR_DEVICE_IN_USE: // "'%1' is in use on %2" idString = IDS_FILE_OPEN_ERROR; break; case ERROR_BAD_NETPATH: case ERROR_DEV_NOT_EXIST: case ERROR_NETNAME_DELETED: case ERROR_BAD_NET_NAME: case ERROR_SHARING_PAUSED: case ERROR_REQ_NOT_ACCEP: case ERROR_REDIR_PAUSED: case ERROR_BAD_DEVICE: case ERROR_CONNECTION_UNAVAIL: case ERROR_NO_NET_OR_BAD_PATH: case ERROR_NO_NETWORK: case ERROR_CONNECTION_REFUSED: case ERROR_GRACEFUL_DISCONNECT: case ERROR_NETWORK_UNREACHABLE: case ERROR_HOST_UNREACHABLE: case ERROR_PROTOCOL_UNREACHABLE: case ERROR_PORT_UNREACHABLE: case ERROR_LOGON_FAILURE: // "Unable to connect to '%1.' %2" idString = IDS_SHARE_CONNECT_ERROR; break; case ERROR_OPEN_FAILED: case ERROR_UNEXP_NET_ERR: case ERROR_NETWORK_BUSY: case ERROR_BAD_NET_RESP: // "Unable to access '%1' on %2. %3" idString = IDS_NET_ERROR; break; case ERROR_ACCESS_DENIED: case ERROR_NETWORK_ACCESS_DENIED: // "Access to '%1' is denied on %2" idString = IDS_ACCESS_ERROR; break; case ERROR_BAD_FORMAT: // "The Offline Files cache is corrupt. Restart the computer to correct the cache." idString = IDS_CACHE_CORRUPT; break; default: // "Error accessing '%1' on %2. %3" idString = IDS_UNKNOWN_SYNC_ERROR; break; } return idString; } HRESULT CCscUpdate::LogError(REFSYNCMGRITEMID rItemID, LPCTSTR pszText, DWORD dwLogLevel, REFSYNCMGRERRORID ErrorID) { HRESULT hr; SYNCMGRLOGERRORINFO slei; USES_CONVERSION; TraceEnter(TRACE_UPDATE, "CCscUpdate::LogError"); if (NULL == m_pSyncMgrCB) TraceLeaveResult(E_UNEXPECTED); slei.cbSize = sizeof(slei); slei.mask = SYNCMGRLOGERROR_ITEMID | SYNCMGRLOGERROR_ERRORID; slei.ItemID = rItemID; slei.ErrorID = ErrorID; // if we have a jumptext associated with this item then // set the enable jumptext flag if (ErrorID != GUID_NULL) { slei.mask |= SYNCMGRLOGERROR_ERRORFLAGS; slei.dwSyncMgrErrorFlags = SYNCMGRERRORFLAG_ENABLEJUMPTEXT; } Trace((pszText)); hr = m_pSyncMgrCB->LogError(dwLogLevel, T2CW(pszText), &slei); TraceLeaveResult(hr); } DWORD CCscUpdate::LogError(REFSYNCMGRITEMID rItemID, DWORD dwLogLevel, UINT nFormatID, ...) { LPTSTR pszError = NULL; va_list args; va_start(args, nFormatID); if (vFormatStringID(&pszError, g_hInstance, nFormatID, &args)) { LogError(rItemID, pszError, dwLogLevel); LocalFree(pszError); } va_end(args); return 0; } DWORD CCscUpdate::LogError(REFSYNCMGRITEMID rItemID, UINT nFormatID, LPCTSTR pszName, DWORD dwErr, DWORD dwLogLevel) { // // Break the filename into "file" and "path" components // TCHAR szPath[MAX_PATH] = TEXT("\\"); LPCTSTR pszFile = NULL; if (pszName) { pszFile = PathFindFileName(pszName); lstrcpyn(szPath, pszName, (int)min(ARRAYSIZE(szPath),(int)(pszFile-pszName))); } // // Get the system error text and format the error // LPTSTR pszErr = GetErrorText(dwErr); LogError(rItemID, dwLogLevel, nFormatID, pszFile, szPath, pszErr); LocalFreeString(&pszErr); return 0; } BOOL MakeDriveLetterPath(LPCTSTR pszUNC, LPCTSTR pszShare, LPCTSTR pszDrive, LPTSTR *ppszResult) { BOOL bResult = FALSE; ULONG cchShare; if (!pszUNC || !pszShare || !ppszResult) return FALSE; *ppszResult = NULL; cchShare = lstrlen(pszShare); // If the path is on the share, use the drive letter instead if (pszDrive && *pszDrive && CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, pszUNC, cchShare, pszShare, cchShare)) { *ppszResult = (LPTSTR)LocalAlloc(LPTR, MAX(StringByteSize(pszUNC), MAX_PATH_BYTES)); if (*ppszResult) { PathCombine(*ppszResult, pszDrive, pszUNC + cchShare); bResult = TRUE; } } return bResult; } DWORD CCscUpdate::CopyLocalFileWithDriveMapping(LPCTSTR pszSrc, LPCTSTR pszDst, LPCTSTR pszShare, LPCTSTR pszDrive, BOOL bDirectory) { DWORD dwErr = NOERROR; LPTSTR szDst = NULL; if (!pszSrc || !pszDst || !pszShare) return ERROR_INVALID_PARAMETER; // If the destination is on the share, use the drive letter instead if (MakeDriveLetterPath(pszDst, pszShare, pszDrive, &szDst)) pszDst = szDst; if (bDirectory) { // We don't need to copy the directory contents here, just create // the tree structure on the server. if (!CreateDirectory(pszDst, NULL)) { dwErr = GetLastError(); if (ERROR_ALREADY_EXISTS == dwErr) dwErr = NOERROR; } } else { LPTSTR pszTmpName = NULL; if (!CSCCopyReplica(pszSrc, &pszTmpName) || !MoveFileEx(pszTmpName, pszDst, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { dwErr = GetLastError(); } if (NULL != pszTmpName) { DeleteFile(pszTmpName); LocalFree(pszTmpName); } } if (ERROR_PATH_NOT_FOUND == dwErr) { // The parent directory doesn't exist, create it now. TCHAR szParent[MAX_PATH]; lstrcpyn(szParent, pszDst, ARRAYSIZE(szParent)); PathRemoveFileSpec(szParent); dwErr = CopyLocalFileWithDriveMapping(pszSrc, szParent, pszShare, NULL, TRUE); // If that worked, retry the original operation. if (NOERROR == dwErr) dwErr = CopyLocalFileWithDriveMapping(pszSrc, pszDst, pszShare, NULL, bDirectory); } LocalFreeString(&szDst); return dwErr; } BOOL HandleConflictLocally(PSYNCTHREADDATA pSyncData, LPCTSTR pszPath, DWORD dwCscStatus, DWORD dwLocalAttr, DWORD dwRemoteAttr = 0) { BOOL bResult = FALSE; LPTSTR szParent = NULL; // If it's super-hidden or not modified locally, we can always // handle the conflict locally. if (!(dwCscStatus & CSC_LOCALLY_MODIFIED) || IsHiddenSystem(dwLocalAttr) || IsHiddenSystem(dwRemoteAttr)) return TRUE; // If we're dealing with 2 folders, the worst that happens is that the // underlying files/folders get merged. if ((FILE_ATTRIBUTE_DIRECTORY & dwLocalAttr) && (FILE_ATTRIBUTE_DIRECTORY & dwRemoteAttr)) return TRUE; // // Next, check whether the parent path is super-hidden. // // For example, recycle bin makes super-hidden folders and puts // metadata files in them. // // Do this on the server, since CSC has exclusive access to the database // while merging, causing GetFileAttributes to fail with Access Denied. // // // Do this on the server, since CSC has exclusive access to the database // while merging, causing GetFileAttributes to fail with Access Denied. // if (MakeDriveLetterPath(pszPath, pSyncData->pszShareName, pSyncData->szDrive, &szParent)) { // // Don't check attributes at the root, just stop. // WinSE 16781. // for(PathRemoveFileSpec(szParent); !PathIsRoot(szParent); PathRemoveFileSpec(szParent)) { dwRemoteAttr = GetFileAttributes(szParent); if ((DWORD)-1 == dwRemoteAttr) { // Path doesn't exist, access denied, etc. break; } if (IsHiddenSystem(dwRemoteAttr)) { bResult = TRUE; break; } } } LocalFreeString(&szParent); return bResult; } DWORD CCscUpdate::HandleFileConflict(PSYNCTHREADDATA pSyncData, LPCTSTR pszName, DWORD dwStatus, DWORD dwHintFlags, LPWIN32_FIND_DATA pFind32) { DWORD dwResult = CSCPROC_RETURN_CONTINUE; DWORD dwErr = NOERROR; int nErrorResolution = RFC_KEEPBOTH; LPTSTR pszNewName = NULL; LPTSTR szFullPath = NULL; BOOL bApplyToAll = FALSE; TraceEnter(TRACE_UPDATE, "CCscUpdate::HandleFileConflict"); Trace((TEXT("File conflict: %s"), pszName)); TraceAssert(pSyncData->dwSyncStatus & SDS_SYNC_OUT); szFullPath = (LPTSTR)LocalAlloc(LPTR, StringByteSize(pszName) + MAX_PATH*sizeof(TCHAR)); if (!szFullPath) { dwErr = ERROR_OUTOFMEMORY; ExitGracefully(dwResult, CSCPROC_RETURN_SKIP, "LocalAlloc failed"); } HANDLE hFind; WIN32_FIND_DATA fdRemote; PathCombine(szFullPath, pSyncData->szDrive, pszName + lstrlen(pSyncData->pszShareName)); hFind = FindFirstFile(szFullPath, &fdRemote); // Does the net version still exist? if (hFind == INVALID_HANDLE_VALUE) ExitGracefully(dwResult, HandleDeleteConflict(pSyncData, pszName, dwStatus, dwHintFlags, pFind32), "Net file deleted"); // Still exists, continue FindClose(hFind); // If only the attributes or file times were modified locally, // or if the file is hidden+system, keep the server copy and // don't bother the user. (e.g. desktop.ini) if (HandleConflictLocally(pSyncData, pszName, dwStatus, pFind32->dwFileAttributes, fdRemote.dwFileAttributes)) { ExitGracefully(dwResult, CSCPROC_RETURN_FORCE_INWARD, "Ignoring conflict"); } else if (IsSilentFolder(pszName)) { // It's in a per-user shell special folder. Last writer wins. if (CompareFileTime(&pFind32->ftLastWriteTime, &fdRemote.ftLastWriteTime) < 0) { ExitGracefully(dwResult, CSCPROC_RETURN_FORCE_INWARD, "Handling special folder conflict - server copy wins"); } else { ExitGracefully(dwResult, CSCPROC_RETURN_FORCE_OUTWARD, "Handling special folder conflict - local copy wins"); } } dwErr = GetNewVersionName(pszName, pSyncData->pszShareName, pSyncData->szDrive, &pszNewName); if (NOERROR != dwErr) { ExitGracefully(dwResult, CSCPROC_RETURN_SKIP, "GetNewVersionName failed"); } switch (SDS_SYNC_FILE_CONFLICT_MASK & pSyncData->dwSyncStatus) { case 0: if (CSC_SYNC_MAYBOTHERUSER & m_dwSyncFlags) { nErrorResolution = ShowConflictDialog(m_hwndDlgParent, pszName, pszNewName, pSyncData->pszShareName, pSyncData->szDrive, pFind32, &fdRemote); if (RFC_APPLY_TO_ALL & nErrorResolution) { bApplyToAll = TRUE; nErrorResolution &= ~RFC_APPLY_TO_ALL; } } break; case SDS_SYNC_CONFLICT_KEEPLOCAL: nErrorResolution = RFC_KEEPLOCAL; break; case SDS_SYNC_CONFLICT_KEEPNET: nErrorResolution = RFC_KEEPNETWORK; break; case SDS_SYNC_CONFLICT_KEEPBOTH: nErrorResolution = RFC_KEEPBOTH; break; } // Self-host notification callback CSCUI_NOTIFYHOOK((CSCH_UpdateConflict, TEXT("Update conflict: %1, resolution %2!d!"), pszName, nErrorResolution)); switch (nErrorResolution) { default: case RFC_KEEPBOTH: if (bApplyToAll) pSyncData->dwSyncStatus |= SDS_SYNC_CONFLICT_KEEPBOTH; lstrcpy(szFullPath, pszName); PathRemoveFileSpec(szFullPath); if (FILE_ATTRIBUTE_DIRECTORY & pFind32->dwFileAttributes) { // Rename the local version in the cache and merge again. lstrcpyn(pFind32->cFileName, pszNewName, ARRAYSIZE(pFind32->cFileName)); if (!CSCDoLocalRenameEx(pszName, szFullPath, pFind32, TRUE, TRUE)) { dwErr = GetLastError(); ExitGracefully(dwResult, CSCPROC_RETURN_SKIP, "CSCDoLocalRenameEx failed"); } // Because CSCDoLocalRenameEx and CSCMergeShare are separate operations, // we have to abort the current merge operation and start over. // Otherwise, the current merge operation fails due to the "left // hand not knowing what the right hande is doing". Trace((TEXT("Restarting merge on: %s"), pSyncData->pszShareName)); pSyncData->dwSyncStatus |= SDS_SYNC_RESTART_MERGE; dwResult = CSCPROC_RETURN_ABORT; } else { // Note that CSCDoLocalRenameEx would work for files also, but we // prefer to avoid restarting CSCMergeShare so do these ourselves. PathAppend(szFullPath, pszNewName); dwErr = CopyLocalFileWithDriveMapping(pszName, szFullPath, pSyncData->pszShareName, pSyncData->szDrive); if (NOERROR != dwErr) ExitGracefully(dwResult, CSCPROC_RETURN_SKIP, "CopyLocalFileWithDriveMapping failed"); // If the original file was pinned, we want to pin the copy also. // Unfortunately, we can't reliably pin during a merge, so we have // to remember these in a list and pin them later. if (dwHintFlags & (FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN)) { if (!m_pConflictPinList) m_pConflictPinList = new CscFilenameList; if (m_pConflictPinList) { m_pConflictPinList->AddFile(szFullPath, !!(pFind32->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)); } } // Tell CSCMergeShare to copy the server copy to the cache // (with the old name). This clears the dirty cache. dwResult = CSCPROC_RETURN_FORCE_INWARD; } break; case RFC_KEEPNETWORK: // Tell CSCMergeShare to copy the server copy to the cache dwResult = CSCPROC_RETURN_FORCE_INWARD; if (bApplyToAll) pSyncData->dwSyncStatus |= SDS_SYNC_CONFLICT_KEEPNET; break; case RFC_KEEPLOCAL: // Tell CSCMergeShare to push the local copy to the server dwResult = CSCPROC_RETURN_FORCE_OUTWARD; if (bApplyToAll) pSyncData->dwSyncStatus |= SDS_SYNC_CONFLICT_KEEPLOCAL; break; case RFC_CANCEL: TraceMsg("HandleFileConflict: Cancelling sync - user bailed"); SetItemStatus(GUID_NULL, SYNCMGRSTATUS_STOPPED); dwResult = CSCPROC_RETURN_ABORT; break; } exit_gracefully: if (CSCPROC_RETURN_FORCE_INWARD == dwResult) { // CSCMergeShare truncates (makes sparse) the // file if we return this. We'd like to fill // it during this sync. pSyncData->cFilesToSync++; pSyncData->dwSyncStatus |= SDS_SYNC_FORCE_INWARD; } if (NOERROR != dwErr) { pszName += lstrlen(pSyncData->pszShareName); if (*pszName == TEXT('\\')) pszName++; LogError(pSyncData->ItemID, IDS_NAME_CONFLICT_ERROR, pszName, dwErr); pSyncData->dwSyncStatus |= SDS_SYNC_ERROR; } LocalFreeString(&szFullPath); LocalFreeString(&pszNewName); TraceLeaveResult(dwResult); } // Returns values for the Resolve Delete Conflict dialog #define RDC_CANCEL 0x00 #define RDC_DELETE 0x01 #define RDC_RESTORE 0x02 #define RDC_APPLY_ALL 0x04 #define RDC_DELETE_ALL (RDC_APPLY_ALL | RDC_DELETE) #define RDC_RESTORE_ALL (RDC_APPLY_ALL | RDC_RESTORE) TCHAR const c_szDeleteSelection[] = TEXT("DeleteConflictSelection"); INT_PTR CALLBACK DeleteConflictProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { int nResult; switch (uMsg) { case WM_INITDIALOG: { TCHAR szShare[MAX_PATH]; LPCTSTR pszPath = (LPCTSTR)lParam; LPTSTR pszT = NULL; szShare[0] = TEXT('\0'); lstrcpyn(szShare, pszPath, ARRAYSIZE(szShare)); PathStripToRoot(szShare); // Format the file name PathSetDlgItemPath(hDlg, IDC_FILENAME, pszPath); // Build the "Do this for all on " string FormatStringID(&pszT, g_hInstance, IDS_FMT_DELETE_APPLY_ALL, szShare); if (pszT) { SetDlgItemText(hDlg, IDC_APPLY_TO_ALL, pszT); LocalFreeString(&pszT); } // else default text is OK (no share name) // Select whatever the user chose last time, default is "restore" DWORD dwPrevSelection = RDC_RESTORE; DWORD dwType; DWORD cbData = sizeof(dwPrevSelection); SHGetValue(HKEY_CURRENT_USER, c_szCSCKey, c_szDeleteSelection, &dwType, &dwPrevSelection, &cbData); dwPrevSelection = (RDC_DELETE == dwPrevSelection ? IDC_DELETE_LOCAL : IDC_KEEP_LOCAL); CheckRadioButton(hDlg, IDC_KEEP_LOCAL, IDC_DELETE_LOCAL, dwPrevSelection); // Get the file-type icon pszT = PathFindExtension(pszPath); if (pszT) { SHFILEINFO sfi = {0}; SHGetFileInfo(pszT, 0, &sfi, sizeof(sfi), SHGFI_ICON); if (sfi.hIcon) { SendDlgItemMessage(hDlg, IDC_DLGTYPEICON, STM_SETICON, (WPARAM)sfi.hIcon, 0L); } } } return TRUE; case WM_COMMAND: nResult = -1; switch (LOWORD(wParam)) { case IDCANCEL: nResult = RDC_CANCEL; break; case IDOK: if (BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_DELETE_LOCAL)) nResult = RDC_DELETE; else nResult = RDC_RESTORE; // Remember the selection for next time SHSetValue(HKEY_CURRENT_USER, c_szCSCKey, c_szDeleteSelection, REG_DWORD, &nResult, sizeof(nResult)); if (BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_APPLY_TO_ALL)) nResult |= RDC_APPLY_ALL; break; } if (-1 != nResult) { EndDialog(hDlg, nResult); return TRUE; } break; } return FALSE; } BOOL CALLBACK ConflictPurgeCallback(LPCWSTR /*pszFile*/, LPARAM lParam) { PSYNCTHREADDATA pSyncData = (PSYNCTHREADDATA)lParam; return !(SDS_SYNC_CANCELLED & pSyncData->dwSyncStatus); } DWORD CCscUpdate::HandleDeleteConflict(PSYNCTHREADDATA pSyncData, LPCTSTR pszName, DWORD dwStatus, DWORD dwHintFlags, LPWIN32_FIND_DATA pFind32) { DWORD dwResult = CSCPROC_RETURN_CONTINUE; int nErrorResolution = RDC_DELETE; // default action BOOL bDirectory = (FILE_ATTRIBUTE_DIRECTORY & pFind32->dwFileAttributes); TraceEnter(TRACE_UPDATE, "CCscUpdate::HandleDeleteConflict"); Trace((TEXT("Net file deleted: %s"), pszName)); // // We already know that the net file was deleted, or HandleDeleteConflict // wouldn't be called. If the local copy was also deleted, then there // isn't really a conflict and we can continue without prompting. // // Handle the conflict silently if only attributes changed or it's super-hidden. // // Finally, if the file lives in certain special folder locations, // such as AppData, handle the conflict silently. // // If we get past all that, ask the user what to do, but only bother // the user as a last resort. // if ( !(dwStatus & FLAG_CSC_COPY_STATUS_LOCALLY_DELETED) && !HandleConflictLocally(pSyncData, pszName, dwStatus, pFind32->dwFileAttributes) && !IsSilentFolder(pszName) ) { // The file is either pinned or modified locally, so // default action is now "restore". nErrorResolution = RDC_RESTORE; switch (SDS_SYNC_DELETE_CONFLICT_MASK & pSyncData->dwSyncStatus) { case 0: if (CSC_SYNC_MAYBOTHERUSER & m_dwSyncFlags) { int idDialog = (bDirectory ? IDD_FOLDER_CONFLICT_DELETE : IDD_FILE_CONFLICT_DELETE); nErrorResolution = (int)DialogBoxParam(g_hInstance, MAKEINTRESOURCE(idDialog), m_hwndDlgParent, DeleteConflictProc, (LPARAM)pszName); if (RDC_DELETE_ALL == nErrorResolution) { pSyncData->dwSyncStatus |= SDS_SYNC_DELETE_DELETE; nErrorResolution = RDC_DELETE; } else if (RDC_RESTORE_ALL == nErrorResolution) { pSyncData->dwSyncStatus |= SDS_SYNC_DELETE_RESTORE; nErrorResolution = RDC_RESTORE; } } break; case SDS_SYNC_DELETE_DELETE: nErrorResolution = RDC_DELETE; break; case SDS_SYNC_DELETE_RESTORE: nErrorResolution = RDC_RESTORE; break; } // Self-host notification callback CSCUI_NOTIFYHOOK((CSCH_DeleteConflict, TEXT("Delete conflict: %1, resolution %2!d!"), pszName, nErrorResolution)); } switch (nErrorResolution) { default: case RDC_RESTORE: Trace((TEXT("HandleDeleteConflict: restoring %s"), pszName)); // Tell CSCMergeShare to push the local copy to the server dwResult = CSCPROC_RETURN_FORCE_OUTWARD; break; case RDC_DELETE: Trace((TEXT("HandleDeleteConflict: deleting %s"), pszName)); if (bDirectory) { // Deep delete CSCUIRemoveFolderFromCache(pszName, 0, ConflictPurgeCallback, (LPARAM)pSyncData); } else { if (ERROR_SUCCESS == CscDelete(pszName)) { ShellChangeNotify(pszName, pFind32, TRUE, SHCNE_DELETE); } } dwResult = CSCPROC_RETURN_SKIP; break; case RDC_CANCEL: TraceMsg("HandleDeleteConflict: Cancelling sync - user bailed"); SetItemStatus(GUID_NULL, SYNCMGRSTATUS_STOPPED); dwResult = CSCPROC_RETURN_ABORT; break; } TraceLeaveResult(dwResult); } DWORD CCscUpdate::CscCallback(PSYNCTHREADDATA pSyncData, LPCTSTR pszName, DWORD dwStatus, DWORD dwHintFlags, DWORD dwPinCount, LPWIN32_FIND_DATA pFind32, DWORD dwReason, DWORD dwParam1, DWORD dwParam2) { DWORD dwResult = CSCPROC_RETURN_CONTINUE; SYNCMGRPROGRESSITEM spi = { sizeof(spi), 0 }; TraceEnter(TRACE_UPDATE, "CCscUpdate::CscCallback"); TraceAssert(pSyncData != NULL); TraceAssert(pSyncData->pThis == this); // Check for Cancel if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) { TraceMsg("Cancelling sync operation"); TraceLeaveValue(CSCPROC_RETURN_ABORT); } switch (dwReason) { case CSCPROC_REASON_BEGIN: // First thing to do is determine if this is for the entire share // or an individual file in the share. if (!(pSyncData->dwSyncStatus & SDS_SYNC_STARTED)) { // SHARE BEGIN pSyncData->dwSyncStatus |= SDS_SYNC_STARTED; TraceAssert(!lstrcmpi(pszName, pSyncData->pszShareName)); Trace((TEXT("Share begin: %s"), pszName)); if (pSyncData->dwSyncStatus & SDS_SYNC_OUT) { // Save the drive letter to use for net operations Trace((TEXT("Drive %s"), pFind32->cFileName)); lstrcpyn(pSyncData->szDrive, pFind32->cFileName, ARRAYSIZE(pSyncData->szDrive)); } else { pSyncData->szDrive[0] = TEXT('\0'); } // Remember whether it's an autocache share or not switch (dwStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK) { case FLAG_CSC_SHARE_STATUS_AUTO_REINT: case FLAG_CSC_SHARE_STATUS_VDO: pSyncData->dwSyncStatus |= SDS_SYNC_AUTOCACHE; break; } } else { // FILE BEGIN BOOL bSkipFile = FALSE; TraceAssert(lstrlen(pszName) > lstrlen(pSyncData->pszShareName)); if (!(pFind32->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { // If we're updating a file selection and this file // isn't part of the selection, skip it. if (m_pFileList && !m_pFileList->FileExists(pszName, false)) { bSkipFile = TRUE; } else if (!(pSyncData->dwSyncStatus & (SDS_SYNC_AUTOCACHE | SDS_SYNC_OUT)) && !(dwHintFlags & (FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN)) && !IsSpecialFolder(pszName)) { // Skip autocached files when filling on a // non-autocache share. Raid #341786 bSkipFile = TRUE; } else if (!(pSyncData->dwSyncStatus & CSC_SYNC_IGNORE_ACCESS)) { // dwReserved0 is the current user's access mask // dwReserved1 is the Guest access mask DWORD dwCurrentAccess = pFind32->dwReserved0 | pFind32->dwReserved1; if (pSyncData->dwSyncStatus & SDS_SYNC_OUT) { // // If the current user doesn't have sufficient access // to merge offline changes, then don't bother trying. // (It must be some other user's file.) // // Have the attributes changed offline? if (FLAG_CSC_COPY_STATUS_ATTRIB_LOCALLY_MODIFIED & dwStatus) { // Yes. Continue if the current user has // write-attribute access. bSkipFile = !(dwCurrentAccess & FILE_WRITE_ATTRIBUTES); } // Have the contents changed offline? if (!bSkipFile && ((FLAG_CSC_COPY_STATUS_DATA_LOCALLY_MODIFIED | FLAG_CSC_COPY_STATUS_LOCALLY_CREATED | FLAG_CSC_COPY_STATUS_LOCALLY_DELETED) & dwStatus)) { // Yes. Continue if the current user has // write-data access. bSkipFile = !(dwCurrentAccess & FILE_WRITE_DATA); } } else { // // We're filling. Continue if the current user has // read-data access, otherwise skip. // bSkipFile = !(dwCurrentAccess & FILE_READ_DATA); } } } else if (!(pSyncData->dwSyncStatus & SDS_SYNC_OUT)) { // It's a directory and we're in CSCFillSparseFiles. // // Note that we never skip directories when merging (we may be // interested in a file further down the tree) although we // can skip directories when filling. // If it's not in the file selection, skip it. if (m_pFileList && !m_pFileList->FileExists(pszName, false)) { bSkipFile = TRUE; } } if (bSkipFile) { Trace((TEXT("Skipping: %s"), pszName)); dwResult = CSCPROC_RETURN_SKIP; pSyncData->dwSyncStatus |= SDS_SYNC_FILE_SKIPPED; break; } Trace((TEXT("File begin: %s"), pszName)); // // Since we sometimes don't skip directories, even when it turns // out they have nothing that the current user is interested in, // don't display directory names in SyncMgr. // // If we sync a file farther down the tree, we will display the // filename and the intervening directory names will be visible // at that time. // if (!(pFind32->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { USES_CONVERSION; // Tell SyncMgr what we're doing spi.mask = SYNCMGRPROGRESSITEM_STATUSTEXT; spi.lpcStatusText = T2CW(pszName + lstrlen(pSyncData->pszShareName) + 1); NotifySyncMgr(pSyncData, &spi); } // dwParam1 is non-zero when there is a conflict, i.e. both the // local and remote versions of the file have been modified. if (dwParam1) { if (dwParam2) // indicates server file deleted { Trace((TEXT("Delete conflict: %d"), dwParam2)); dwResult = HandleDeleteConflict(pSyncData, pszName, dwStatus, dwHintFlags, pFind32); } else { Trace((TEXT("Update conflict: %d"), dwParam1)); dwResult = HandleFileConflict(pSyncData, pszName, dwStatus, dwHintFlags, pFind32); } } } break; case CSCPROC_REASON_END: // dwParam2 == error code (winerror.h) if (3000 <= dwParam2 && dwParam2 <= 3200) { // Private error codes used in cscdll Trace((TEXT("CSC error: %d"), dwParam2)); dwParam2 = NOERROR; } else if (ERROR_OPERATION_ABORTED == dwParam2) { // We returned CSCPROC_RETURN_ABORT for some reason. // Whatever it was, we already reported it. dwParam2 = NOERROR; dwResult = CSCPROC_RETURN_ABORT; } if (lstrlen(pszName) == lstrlen(pSyncData->pszShareName)) { // SHARE END TraceAssert(!lstrcmpi(pszName, pSyncData->pszShareName)); Trace((TEXT("Share end: %s"), pszName)); pSyncData->dwSyncStatus &= ~SDS_SYNC_STARTED; } else { BOOL bUpdateProgress = FALSE; // FILE END if (!(pSyncData->dwSyncStatus & SDS_SYNC_FILE_SKIPPED)) { Trace((TEXT("File end: %s"), pszName)); bUpdateProgress = TRUE; // Special case errors switch (dwParam2) { case ERROR_ACCESS_DENIED: if (FILE_ATTRIBUTE_DIRECTORY & pFind32->dwFileAttributes) { // 317751 directories are not per-user, so if a // different user syncs, we can hit this. Don't want // to show an error message unless we are ignoring // access (when the user explicitly selected something // to pin/sync). // // 394362 BrianV hit this running as an admin, so don't // show this error for admins either. // if (!(pSyncData->dwSyncStatus & CSC_SYNC_IGNORE_ACCESS)) { TraceMsg("Suppressing ERROR_ACCESS_DENIED on folder"); dwParam2 = NOERROR; } } break; case ERROR_GEN_FAILURE: TraceMsg("Received ERROR_GEN_FAILURE from cscdll"); if (dwStatus & FLAG_CSC_COPY_STATUS_FILE_IN_USE) dwParam2 = ERROR_OPEN_FILES; break; case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: // We either handle the error here or the user is // prompted, so no need for another error message. dwParam2 = NOERROR; // If this is an autocache file and has not been modified // offline, nuke it now. Otherwise, prompt for action. if (CSCPROC_RETURN_FORCE_OUTWARD == HandleDeleteConflict(pSyncData, pszName, dwStatus, dwHintFlags, pFind32)) { dwParam2 = CopyLocalFileWithDriveMapping(pszName, pszName, pSyncData->pszShareName, pSyncData->szDrive, (FILE_ATTRIBUTE_DIRECTORY & pFind32->dwFileAttributes)); } break; case ERROR_DISK_FULL: // There's no point continuing dwResult = CSCPROC_RETURN_ABORT; break; default: // nothing break; } } else { pSyncData->dwSyncStatus &= ~SDS_SYNC_FILE_SKIPPED; dwParam2 = NOERROR; // If doing full sync, then we count progress for skipped // files as well. Not true for quick fill or merge. if (pSyncData->dwSyncStatus & SDS_SYNC_IN_FULL) bUpdateProgress = TRUE; } // Update progress in SyncMgr if (bUpdateProgress) { pSyncData->cFilesDone++; spi.mask = SYNCMGRPROGRESSITEM_PROGVALUE; spi.iProgValue = min(pSyncData->cFilesDone, pSyncData->cFilesToSync - 1); Trace((TEXT("%d of %d files done"), spi.iProgValue, pSyncData->cFilesToSync)); NotifySyncMgr(pSyncData, &spi); } } if (dwParam2 != NOERROR) { UINT idsError = GetErrorFormat(dwParam2, boolify(pSyncData->dwSyncStatus & SDS_SYNC_OUT)); if (IDS_SHARE_CONNECT_ERROR == idsError) { LPTSTR pszErr = GetErrorText(dwParam2); // // Special-case the "can't connect to share" error. // Display only the share name in the error message // and abort the synchronization of this share. // LogError(pSyncData->ItemID, SYNCMGRLOGLEVEL_ERROR, idsError, pSyncData->pszShareName, pszErr ? pszErr : TEXT("")); LocalFreeString(&pszErr); dwResult = CSCPROC_RETURN_ABORT; } else { LogError(pSyncData->ItemID, idsError, pszName, dwParam2); } pSyncData->dwSyncStatus |= SDS_SYNC_ERROR; } break; } // Check for Cancel if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) { TraceMsg("Cancelling sync operation"); dwResult = CSCPROC_RETURN_ABORT; } TraceLeaveValue(dwResult); } void CCscUpdate::NotifySyncMgr(PSYNCTHREADDATA pSyncData, LPSYNCMGRPROGRESSITEM pspi) { LPSYNCMGRSYNCHRONIZECALLBACK pSyncMgr = pSyncData->pThis->m_pSyncMgrCB; if (pSyncMgr) { HRESULT hr = pSyncMgr->Progress(pSyncData->ItemID, pspi); if (hr == S_SYNCMGR_CANCELITEM || hr == S_SYNCMGR_CANCELALL) pSyncData->dwSyncStatus |= SDS_SYNC_CANCELLED; } } DWORD WINAPI CCscUpdate::_CscCallback(LPCTSTR pszName, DWORD dwStatus, DWORD dwHintFlags, DWORD dwPinCount, LPWIN32_FIND_DATA pFind32, DWORD dwReason, DWORD dwParam1, DWORD dwParam2, DWORD_PTR dwContext) { DWORD dwResult = CSCPROC_RETURN_ABORT; PSYNCTHREADDATA pSyncData = (PSYNCTHREADDATA)dwContext; if (pSyncData != NULL && pSyncData->pThis != NULL) dwResult = pSyncData->pThis->CscCallback(pSyncData, pszName, dwStatus, dwHintFlags, dwPinCount, pFind32, dwReason, dwParam1, dwParam2); return dwResult; } BOOL CCscUpdate::PinLinkTarget(LPCTSTR pszName, PSYNCTHREADDATA pSyncData) { BOOL bResult = FALSE; LPTSTR pszTarget = NULL; TraceEnter(TRACE_SHELLEX, "PinLinkTarget"); GetLinkTarget(pszName, &pszTarget); if (pszTarget) { DWORD dwAttr = GetFileAttributes(pszTarget); if ((DWORD)-1 == dwAttr) ExitGracefully(bResult, FALSE, "Link target not found"); TraceAssert(!(dwAttr & FILE_ATTRIBUTE_DIRECTORY)); // Check for EFS if ((FILE_ATTRIBUTE_ENCRYPTED & dwAttr) && SkipEFSPin(pSyncData, pszTarget)) ExitGracefully(bResult, FALSE, "Skipping EFS link target"); if (!(pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)) { HRESULT hr = m_NoPinList.IsPinAllowed(pszTarget); if (S_OK == hr) { if (CSCPinFile(pszTarget, pSyncData->dwPinHints, NULL, NULL, NULL)) { WIN32_FIND_DATA fd = {0}; LPCTSTR pszT = PathFindFileName(pszTarget); fd.dwFileAttributes = dwAttr; lstrcpyn(fd.cFileName, pszT ? pszT : pszTarget, ARRAYSIZE(fd.cFileName)); ShellChangeNotify(pszTarget, &fd, FALSE); bResult = TRUE; if ((FILE_ATTRIBUTE_ENCRYPTED & dwAttr) && !m_bCacheIsEncrypted) { LogError(pSyncData->ItemID, IDS_PIN_ENCRYPT_WARNING, pszTarget, NOERROR, SYNCMGRLOGLEVEL_WARNING); } } } else if (S_FALSE == hr) { if (FILE_ATTRIBUTE_DIRECTORY & dwAttr) { LogError(pSyncData->ItemID, SYNCMGRLOGLEVEL_WARNING, IDS_PIN_NOPINFOLDER_POLICY_WARNING, pszTarget); } else { LogError(pSyncData->ItemID, IDS_PIN_NOPINFILE_POLICY_WARNING, pszTarget, NOERROR, SYNCMGRLOGLEVEL_WARNING); } } } } exit_gracefully: LocalFreeString(&pszTarget); TraceLeaveValue(bResult); } BOOL CCscUpdate::ShouldPinRecurse(LPCTSTR pszName) { // // NTRAID#NTBUG9-508029-2001/12/18-jeffreys // // If CSC_SYNC_PIN_RECURSE is set, the answer is always TRUE. Otherwise, // if we're not pinning files (typically running the FrankAr code), we // automatically recurse on special folders. // return ((m_dwSyncFlags & CSC_SYNC_PIN_RECURSE) || (!(m_dwSyncFlags & CSC_SYNC_PINFILES) && !CConfig::GetSingleton().NoAdminPinSpecialFolders() && IsSpecialFolder(pszName))); } DWORD WINAPI CCscUpdate::_PinNewFilesW32Callback(LPCTSTR pszName, ENUM_REASON eReason, LPWIN32_FIND_DATA pFind32, LPARAM lpContext) { DWORD dwResult = CSCPROC_RETURN_CONTINUE; PSYNCTHREADDATA pSyncData = (PSYNCTHREADDATA)lpContext; DWORD dwHintFlags = 0; DWORD dwErr = NOERROR; LPTSTR pszConnectionName = NULL; // This callback is used when enumerating a pinned folder looking // for new files on the server. Since the parent folder is pinned, // any files in it that aren't pinned get pinned here. TraceEnter(TRACE_UPDATE, "CCscUpdate::_PinNewFilesW32Callback"); TraceAssert(pSyncData != NULL); // Check for Cancel if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) { TraceMsg("Cancelling sync operation"); TraceLeaveValue(CSCPROC_RETURN_ABORT); } // Always ignore folder_end and ignore folder_begin if we // aren't doing a recursive pin operation. if (eReason == ENUM_REASON_FOLDER_END || (eReason == ENUM_REASON_FOLDER_BEGIN && !pSyncData->pThis->ShouldPinRecurse(pszName))) { TraceLeaveValue(CSCPROC_RETURN_SKIP); } if (eReason == ENUM_REASON_FOLDER_BEGIN) { DWORD dwShareStatus = 0; // Folders may be DFS junctions, so make sure it's cacheable. if (!ShareIsCacheable(pszName, FALSE, &pszConnectionName, &dwShareStatus)) { ExitGracefully(dwResult, CSCPROC_RETURN_SKIP, "Skipping no-cache folder"); } } if (S_FALSE == pSyncData->pThis->m_NoPinList.IsPinAllowed(pszName)) { if (FILE_ATTRIBUTE_DIRECTORY & pFind32->dwFileAttributes) { pSyncData->pThis->LogError(pSyncData->ItemID, SYNCMGRLOGLEVEL_WARNING, IDS_PIN_NOPINFOLDER_POLICY_WARNING, pszName); } else { pSyncData->pThis->LogError(pSyncData->ItemID, IDS_PIN_NOPINFILE_POLICY_WARNING, pszName, NOERROR, SYNCMGRLOGLEVEL_WARNING); } ExitGracefully(dwResult, CSCPROC_RETURN_SKIP, "Skipping per no-pin policy"); } // At this point, we either have 1) a file or 2) folder_begin + recurse, // so pin anything that isn't pinned. // Is this file already pinned? if (!CSCQueryFileStatus(pszName, NULL, NULL, &dwHintFlags)) dwErr = GetLastError(); if (ERROR_FILE_NOT_FOUND == dwErr || (NOERROR == dwErr && !(dwHintFlags & (FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN)))) { // Check for EFS BOOL bIsEFSFile = (FILE_ATTRIBUTE_ENCRYPTED & pFind32->dwFileAttributes) && !(FILE_ATTRIBUTE_DIRECTORY & pFind32->dwFileAttributes); if (bIsEFSFile && pSyncData->pThis->SkipEFSPin(pSyncData, pszName)) ExitGracefully(dwResult, CSCPROC_RETURN_SKIP, "Skipping EFS file"); if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) ExitGracefully(dwResult, CSCPROC_RETURN_ABORT, "Sync cancelled"); // Pin it now. if (CSCPinFile(pszName, pSyncData->dwPinHints, NULL, NULL, NULL)) { pSyncData->cFilesToSync++; ShellChangeNotify(pszName, pFind32, FALSE); if (bIsEFSFile && !pSyncData->pThis->m_bCacheIsEncrypted) { pSyncData->pThis->LogError(pSyncData->ItemID, IDS_PIN_ENCRYPT_WARNING, pszName, NOERROR, SYNCMGRLOGLEVEL_WARNING); } // If this is a link file, pin the target (if appropriate) LPTSTR pszExtn = PathFindExtension(pszName); if (pszExtn && !lstrcmpi(pszExtn, c_szLNK)) { if (pSyncData->pThis->PinLinkTarget(pszName, pSyncData)) pSyncData->cFilesToSync++; } } else { DWORD dwError = GetLastError(); UINT idsError = GetErrorFormat(dwError); if (IDS_SHARE_CONNECT_ERROR == idsError) { LPTSTR pszErr = GetErrorText(dwError); // // Special-case the "can't connect to share" error. // Display only the share name in the error message // and abort the pinning of this share. // pSyncData->pThis->LogError(pSyncData->ItemID, SYNCMGRLOGLEVEL_ERROR, idsError, pSyncData->pszShareName, pszErr ? pszErr : TEXT("")); LocalFreeString(&pszErr); pSyncData->dwSyncStatus |= SDS_SYNC_CANCELLED; } else { DWORD dwSyncMgrLogLevel = SYNCMGRLOGLEVEL_ERROR; if (ERROR_INVALID_NAME == dwError) { // // File type is in the exclusion list. // This is a warning, not an error. // dwSyncMgrLogLevel = SYNCMGRLOGLEVEL_WARNING; } pSyncData->pThis->LogError(pSyncData->ItemID, IDS_PIN_FILE_ERROR, pszName, dwError, dwSyncMgrLogLevel); } pSyncData->dwSyncStatus |= SDS_SYNC_ERROR; } LPTSTR pszScanMsg = NULL; SYNCMGRPROGRESSITEM spi; spi.cbSize = sizeof(spi); spi.mask = SYNCMGRPROGRESSITEM_STATUSTEXT; spi.lpcStatusText = L" "; // Skip the share name TraceAssert(PathIsPrefix(pSyncData->pszShareName, pszName)); pszName += lstrlen(pSyncData->pszShareName); if (*pszName == TEXT('\\')) pszName++; LPCTSTR pszFile = PathFindFileName(pszName); TCHAR szPath[MAX_PATH] = TEXT("\\"); if (*pszName) lstrcpyn(szPath, pszName, (int)min(ARRAYSIZE(szPath),(int)(pszFile-pszName))); // If we still have a name, build a string like // "scanning: dir\foo.txt" to display in SyncMgr if (FormatStringID(&pszScanMsg, g_hInstance, IDS_NEW_SCAN, pszFile, szPath)) { USES_CONVERSION; spi.lpcStatusText = T2CW(pszScanMsg); } NotifySyncMgr(pSyncData, &spi); LocalFreeString(&pszScanMsg); } else if ((dwHintFlags & (FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN)) && (pSyncData->pThis->m_dwSyncFlags & CSC_SYNC_PINFILES)) { // FLAG_CSC_HINT_PIN_USER being set implies that CSCQueryFileStatus // succeeded above. // The item was already pinned. Save it in the undo exclusion list. if (!pSyncData->pUndoExclusionList) pSyncData->pUndoExclusionList = new CscFilenameList; if (pSyncData->pUndoExclusionList) pSyncData->pUndoExclusionList->AddFile(pszName); } exit_gracefully: if (pszConnectionName) { WNetCancelConnection2(pszConnectionName, 0, FALSE); LocalFreeString(&pszConnectionName); } TraceLeaveValue(dwResult); } DWORD WINAPI CCscUpdate::_PinNewFilesCSCCallback(LPCTSTR pszName, ENUM_REASON eReason, DWORD /*dwStatus*/, DWORD dwHintFlags, DWORD /*dwPinCount*/, LPWIN32_FIND_DATA /*pFind32*/, LPARAM lpContext) { PSYNCTHREADDATA pSyncData = (PSYNCTHREADDATA)lpContext; PCSCUPDATE pThis; // This callback is used when enumerating the CSC database looking // for pinned folders, with the intention of pinning new files // in those folders on the server. TraceEnter(TRACE_UPDATE, "CCscUpdate::_PinNewFilesCSCCallback"); TraceAssert(pSyncData != NULL); TraceAssert(pSyncData->pThis != NULL); pThis = pSyncData->pThis; // Check for Cancel if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) { TraceMsg("Cancelling sync operation"); TraceLeaveValue(CSCPROC_RETURN_ABORT); } // If this isn't a directory with the user hint flag, keep looking. if (eReason != ENUM_REASON_FOLDER_BEGIN || !(dwHintFlags & (FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN))) { TraceLeaveValue(CSCPROC_RETURN_CONTINUE); } // If we have a file list and this directory isn't in the list, // continue without doing anything here. if (pSyncData->pThis->m_pFileList && !pSyncData->pThis->m_pFileList->FileExists(pszName, false)) { TraceLeaveValue(CSCPROC_RETURN_CONTINUE); } // Ok, we've found a directory with the user hint flag set. Walk // this directory on the server, pinning any files that aren't pinned. pSyncData->dwPinHints = dwHintFlags; _Win32EnumFolder(pszName, FALSE, _PinNewFilesW32Callback, (LPARAM)pSyncData); TraceLeaveValue(CSCPROC_RETURN_CONTINUE); } DWORD WINAPI CCscUpdate::_SyncThread(LPVOID pThreadData) { PSYNCTHREADDATA pSyncData = (PSYNCTHREADDATA)pThreadData; PCSCUPDATE pThis; HRESULT hrComInit = E_FAIL; SYNCMGRPROGRESSITEM spi = {0}; DWORD dwErr = NOERROR; CSCSHARESTATS shareStats; CSCGETSTATSINFO si = { SSEF_NONE, SSUF_NONE, false, // No access info reqd (faster). false }; ULONG cDirtyFiles = 0; ULONG cStaleFiles = 0; DWORD dwShareStatus = 0; BOOL bShareOnline = FALSE; DWORD dwConnectionSpeed = 0; TraceEnter(TRACE_UPDATE, "CCscUpdate::_SyncThread"); TraceAssert(pSyncData); TraceAssert(pSyncData->pThis); TraceAssert(pSyncData->pszShareName && *pSyncData->pszShareName); pThis = pSyncData->pThis; spi.cbSize = sizeof(spi); hrComInit = CoInitialize(NULL); if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) ExitGracefully(dwErr, NOERROR, "Cancelling sync operation"); // Figure out how many files need updating pSyncData->cFilesDone = 0; pSyncData->cFilesToSync = 0; _GetShareStatisticsForUser(pSyncData->pszShareName, &si, &shareStats); // Get share status CSCQueryFileStatus(pSyncData->pszShareName, &dwShareStatus, NULL, NULL); // The root of a special folder is pinned with a pin count, but // not the user-hint flag, so _GetShareStats doesn't count it. // We need to count this for some of the checks below. // (If the share is manual-cache, these look exactly like the // Siemens scenario in 341786, but we want to sync them.) if (shareStats.cTotal && pThis->IsSpecialFolderShare(pSyncData->pszShareName)) { shareStats.cPinned++; // // At logoff, we want to run the FrankAr code on all // 'special' folder shares. // Customers expect folder redirection of special folders // to ensure all contents are cached. // if (pThis->m_dwSyncFlags & CSC_SYNC_LOGOFF) { pSyncData->dwSyncStatus |= SDS_SYNC_FORCE_INWARD; } } if (pThis->m_dwSyncFlags & CSC_SYNC_OUT) { cDirtyFiles = shareStats.cModified; // // Force the merge code if there are open files, so we are // sure to do the open file warning. The danger here is that we // don't warn because the share with open files has nothing dirty, // but we merge changes on another share and then transition online. // We don't want to transition online without warning about open files. // if (0 == cDirtyFiles) { if ((FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwShareStatus) && (FLAG_CSC_SHARE_STATUS_FILES_OPEN & dwShareStatus)) { cDirtyFiles++; } } } if (pThis->m_dwSyncFlags & CSC_SYNC_IN_FULL) { // For full inward sync, always set cStaleFiles to at least 1 to force // a call to CSCFillSparseFiles. // Also, we get callbacks for each file and folder, even if they // are not sparse or stale, so go with cTotal here to make // the progress bar look right. cStaleFiles = max(shareStats.cTotal, 1); } else if (pThis->m_dwSyncFlags & CSC_SYNC_IN_QUICK) { cStaleFiles = shareStats.cSparse; // If we're pinning, then it's possible that nothing is sparse yet, // but we'll need to call CSCFillSparseFiles. if (pThis->m_dwSyncFlags & CSC_SYNC_PINFILES) pSyncData->dwSyncStatus |= SDS_SYNC_FORCE_INWARD; } // Self-host notification callback CSCUI_NOTIFYHOOK((CSCH_SyncShare, TEXT("Sync: %1, Dirty: %2!d!, Stale: %3!d!"), pSyncData->pszShareName, cDirtyFiles, cStaleFiles)); if (dwShareStatus & FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP) { // Can't call CSCFillSparseFiles when disconnected (it just fails) cStaleFiles = 0; } else if ((dwShareStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK) == FLAG_CSC_SHARE_STATUS_MANUAL_REINT && 0 == shareStats.cPinned && !(pThis->m_dwSyncFlags & CSC_SYNC_PINFILES)) { // On a manual share, if nothing is pinned (and we aren't pinning) // then we prefer not to call CSCFillSparseFiles on the share. // Raid #341786 Trace((TEXT("Manual cache share '%s' has only autocached files"), pSyncData->pszShareName)); cStaleFiles = 0; } pSyncData->cFilesToSync = cDirtyFiles + cStaleFiles; // // At this point, if pSyncData->cFilesToSync is nonzero, then we are doing // a sync, and will be calling CSCBeginSynchronization to connect to the // share (with prompt for credentials if necessary). // // If SDS_SYNC_FORCE_INWARD is on, then we are pinning files. We will only // call CSCFillSparseFiles is the server is in connected mode, and we will // only call CSCBeginSynchronization if pSyncData->cFilesToSync is nonzero // (to pin something, you must already have a connection to the share). // if (0 == pSyncData->cFilesToSync && !(pSyncData->dwSyncStatus & SDS_SYNC_FORCE_INWARD)) ExitGracefully(dwErr, NOERROR, "Nothing to synchronize"); // Tell SyncMgr how many files we're updating spi.mask = SYNCMGRPROGRESSITEM_STATUSTYPE | SYNCMGRPROGRESSITEM_PROGVALUE | SYNCMGRPROGRESSITEM_MAXVALUE; spi.dwStatusType = SYNCMGRSTATUS_UPDATING; spi.iProgValue = 0; spi.iMaxValue = pSyncData->cFilesToSync; Trace((TEXT("%d files to sync on %s"), spi.iMaxValue, pSyncData->pszShareName)); NotifySyncMgr(pSyncData, &spi); #if 0 // RAS isn't notifying RDR before returning success, so // do this bogus delay. Remove this when RAS is fixed. // // This was fixed a few weeks before Windows 2000 RTM. // [jeffreys - 1/24/2000] // if (pSyncData->dwSyncStatus & SDS_SYNC_RAS_CONNECTED) { // // We've just established a RAS connection, but there's a timing // problem with RDR. Wait until we can connect to the share, but // don't wait longer than RAS_CONNECT_DELAY. // Trace((TEXT("RAS delay for %s"), pSyncData->pszShareName)); DWORD dwDelayTime = GetTickCount() + RAS_CONNECT_DELAY; #ifdef DEBUG int cAttempts = 0; #endif while (!bShareOnline && GetTickCount() < dwDelayTime && !(pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)) { WaitForSingleObject(pThis->m_hSyncMutex, INFINITE); #ifdef DEBUG cAttempts++; #endif bShareOnline = CSCBeginSynchronization(pSyncData->pszShareName, &dwConnectionSpeed, &pSyncData->dwCscContext); ReleaseMutex(pThis->m_hSyncMutex); } Trace((TEXT("%s %s after %d attempts (%dms)"), pSyncData->pszShareName, (bShareOnline ? TEXT("connected") : TEXT("not reachable")), cAttempts, GetTickCount() - (dwDelayTime - RAS_CONNECT_DELAY))); } else #endif // RAS delay if (pSyncData->cFilesToSync) { // // CSCBeginSynchronization makes a net connection to the share // using the "interactive" flag. This causes a credential popup // if the current user doesn't have access to the share. // Use the sync mutex to avoid multiple concurrent popups. // WaitForSingleObject(pThis->m_hSyncMutex, INFINITE); bShareOnline = CSCBeginSynchronization(pSyncData->pszShareName, &dwConnectionSpeed, &pSyncData->dwCscContext); ReleaseMutex(pThis->m_hSyncMutex); } if (pSyncData->cFilesToSync && !bShareOnline) { // The share isn't reachable, so there's no point in continuing. dwErr = GetLastError(); if (ERROR_CANCELLED == dwErr) { // The user cancelled the credential popup pSyncData->dwSyncStatus |= SDS_SYNC_CANCELLED; ExitGracefully(dwErr, NOERROR, "User cancelled sync"); } LPTSTR pszErr = GetErrorText(dwErr); pThis->LogError(pSyncData->ItemID, SYNCMGRLOGLEVEL_ERROR, IDS_SHARE_CONNECT_ERROR, pSyncData->pszShareName, pszErr); LocalFreeString(&pszErr); ExitGracefully(dwErr, dwErr, "Share not reachable"); } if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) ExitGracefully(dwErr, NOERROR, "Cancelling sync operation"); // Note the time of this sync pThis->SetLastSyncTime(pSyncData->pszShareName); // Merge if (0 != cDirtyFiles) { dwErr = pThis->MergeShare(pSyncData); if (NOERROR != dwErr) { LPTSTR pszErr = GetErrorText(dwErr); pThis->LogError(pSyncData->ItemID, SYNCMGRLOGLEVEL_ERROR, IDS_MERGE_SHARE_ERROR, pSyncData->pszShareName, pszErr); LocalFreeString(&pszErr); ExitGracefully(dwErr, dwErr, "Aborting due to merge error"); } } if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) ExitGracefully(dwErr, NOERROR, "Cancelling sync operation"); // Fill if (0 != cStaleFiles || (pSyncData->dwSyncStatus & SDS_SYNC_FORCE_INWARD)) { dwErr = pThis->FillShare(pSyncData, shareStats.cPinned, dwConnectionSpeed); } exit_gracefully: // If we called CSCBeginSynchronization and it succeeded, // we need to call CSCEndSynchronization. if (bShareOnline) CSCEndSynchronization(pSyncData->pszShareName, pSyncData->dwCscContext); // Tell SyncMgr that we're done (succeeded, failed, or stopped) spi.mask = SYNCMGRPROGRESSITEM_STATUSTYPE | SYNCMGRPROGRESSITEM_STATUSTEXT | SYNCMGRPROGRESSITEM_PROGVALUE | SYNCMGRPROGRESSITEM_MAXVALUE; spi.dwStatusType = SYNCMGRSTATUS_SUCCEEDED; // Assume success if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) spi.dwStatusType = SYNCMGRSTATUS_STOPPED; if (NOERROR != dwErr || (pSyncData->dwSyncStatus & SDS_SYNC_ERROR)) spi.dwStatusType = SYNCMGRSTATUS_FAILED; spi.lpcStatusText = L" "; spi.iProgValue = spi.iMaxValue = pSyncData->cFilesToSync; // This tells syncmgr that the item is done NotifySyncMgr(pSyncData, &spi); if ((pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) && (pThis->m_dwSyncFlags & CSC_SYNC_PINFILES)) { // We cancelled a pin operation, roll back to the previous state CscUnpinFileList(pThis->m_pFileList, (pThis->m_dwSyncFlags & CSC_SYNC_PIN_RECURSE), (FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwShareStatus), pSyncData->pszShareName, _UndoProgress, (LPARAM)pSyncData); } // Tell the Update Handler that this thread is exiting // This may use OLE to notify SyncMgr that the sync is done, // so do this before CoUninitialize. Trace((TEXT("%s finished"), pSyncData->pszShareName)); pThis->SyncThreadCompleted(pSyncData); if (SUCCEEDED(hrComInit)) CoUninitialize(); delete pSyncData->pUndoExclusionList; LocalFree(pSyncData); // Release our ref on the object // (also release our ref on the DLL) pThis->Release(); TraceLeave(); FreeLibraryAndExitThread(g_hInstance, dwErr); return 0; } DWORD CCscUpdate::MergeShare(PSYNCTHREADDATA pSyncData) { DWORD dwErr = NOERROR; BOOL bMergeResult = TRUE; TraceEnter(TRACE_UPDATE, "CCscUpdate::MergeShare"); // CSCMergeShare fails if another thread (or process) is // currently merging. This is because CSCMergeShare uses // a drive letter connection to the share to bypass CSC, // and we don't want to use up all of the drive letters. // So let's protect the call to CSCMergeShare with a mutex // (rather than dealing with failure and retrying, etc.) WaitForSingleObject(m_hSyncMutex, INFINITE); if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) ExitGracefully(dwErr, NOERROR, "Merge cancelled"); // // It would be nice to skip the open file warning if we knew // that the open files were on a "silent folder". The best // we can do, though, is detect that the open files are on // the same share as a silent folder. There's no guarantee // that the open files are not from a different folder on // the same share, so we have to show the warning. // //if (!IsSilentShare(pSyncData->pszShareName)) { DWORD dwShareStatus = 0; CSCQueryFileStatus(pSyncData->pszShareName, &dwShareStatus, NULL, NULL); if (FLAG_CSC_SHARE_STATUS_FILES_OPEN & dwShareStatus) { if (CSC_SYNC_MAYBOTHERUSER & m_dwSyncFlags) { // Only show this warning once per sync (not per thread) if (!(CSC_SYNC_OFWARNINGDONE & m_dwSyncFlags)) { m_dwSyncFlags |= CSC_SYNC_OFWARNINGDONE; // // This dialog we're going to display can use one of // two templates. // // 1. Single user logged on. Tell user to close all files. // Dialog provides [OK] and [Cancel] options. User can // choose to continue or cancel. // // 2. Multiple users logged on. Tell user that sync cannot // be performed with multiple users logged on. Dialog // presents only an [OK] button. However, it's ID is // IDCANCEL so pressing it will cause us to stop the // merge. // if (IDOK != OpenFilesWarningDialog()) { TraceMsg("Cancelling sync - user bailed at open file warning"); SetItemStatus(GUID_NULL, SYNCMGRSTATUS_STOPPED); } } // else we already put up the warning on another thread. If the // user cancelled, SDS_SYNC_CANCELLED will be set. } else { // Don't merge, but continue otherwise. LogError(pSyncData->ItemID, SYNCMGRLOGLEVEL_WARNING, IDS_OPENFILE_MERGE_WARNING, pSyncData->pszShareName); ExitGracefully(dwErr, NOERROR, "Skipping merge due to open files"); } } } // // Conflict resolution may require stopping and restarting CSCMergeShare, // so do this in a loop // while (!(pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)) { Trace((TEXT("Calling CSCMergeShare(%s)"), pSyncData->pszShareName)); pSyncData->dwSyncStatus = SDS_SYNC_OUT; bMergeResult = CSCMergeShare(pSyncData->pszShareName, CCscUpdate::_CscCallback, (DWORD_PTR)pSyncData); Trace((TEXT("CSCMergeShare(%s) returned"), pSyncData->pszShareName)); // Do we need to merge again? if (!(SDS_SYNC_RESTART_MERGE & pSyncData->dwSyncStatus)) break; } if (!(pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) && !bMergeResult) { dwErr = GetLastError(); if (ERROR_OPERATION_ABORTED == dwErr) dwErr = NOERROR; } exit_gracefully: ReleaseMutex(m_hSyncMutex); TraceLeaveValue(dwErr); } DWORD CCscUpdate::FillShare(PSYNCTHREADDATA pSyncData, int cPinned, DWORD dwConnectionSpeed) { DWORD dwErr = NOERROR; DWORD dwShareStatus = 0; DWORD dwShareHints = 0; TraceEnter(TRACE_UPDATE, "CCscUpdate::FillShare"); CSCQueryFileStatus(pSyncData->pszShareName, &dwShareStatus, NULL, &dwShareHints); // // At logoff, we want to run the FrankAr code on all // 'special' folder shares. // Customers expect folder redirection of special folders // to ensure all contents are cached. // if ((m_dwSyncFlags & CSC_SYNC_IN_FULL) || ((m_dwSyncFlags & CSC_SYNC_LOGOFF) && IsSpecialFolderShare(pSyncData->pszShareName))) { pSyncData->dwSyncStatus = SDS_SYNC_IN_FULL; Trace((TEXT("Full sync at %d00 bps"), dwConnectionSpeed)); // // Check the server for new files that should be pinned. // // We can't do this when disconnected. Also, this is // time consuming, so don't do it on a slow connection. // if (!(FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwShareStatus) && cPinned && !_PathIsSlow(dwConnectionSpeed)) { // // Look for pinned folders on this share by enumerating // in the CSC database. Go out to the server only if/when // we find a pinned folder. // TraceMsg("Running FrankAr code"); // if (CConfig::GetSingleton().AlwaysPinSubFolders()) { // // If the "AlwaysPinSubFolders" policy is set, we // do a recursive pin. This will cause any content // (including folders) of a pinned folder to become pinned. // pSyncData->pThis->m_dwSyncFlags |= CSC_SYNC_PIN_RECURSE; } // First check the root folder if (_PinNewFilesCSCCallback(pSyncData->pszShareName, ENUM_REASON_FOLDER_BEGIN, 0, dwShareHints, 0, NULL, (LPARAM)pSyncData) == CSCPROC_RETURN_CONTINUE) { _CSCEnumDatabase(pSyncData->pszShareName, TRUE, _PinNewFilesCSCCallback, (LPARAM)pSyncData); } TraceMsg("FrankAr code complete"); } } else { pSyncData->dwSyncStatus = SDS_SYNC_IN_QUICK; if (m_dwSyncFlags & CSC_SYNC_PINFILES) { // // Enumerate the file list and pin everything, checking with // SyncMgr periodically. // PinFiles(pSyncData); } } if (m_pConflictPinList) { // Make sure that any files we created because of merge // conflicts are pinned. PinFiles(pSyncData, TRUE); } // Can't fill when disconnected if (!(FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwShareStatus)) { // Clear the status text and update the max count in case we // pinned somthing above SYNCMGRPROGRESSITEM spi; spi.cbSize = sizeof(spi); spi.mask = SYNCMGRPROGRESSITEM_STATUSTEXT | SYNCMGRPROGRESSITEM_MAXVALUE; spi.lpcStatusText = L" "; spi.iMaxValue = pSyncData->cFilesToSync; Trace((TEXT("%d files to sync on %s"), spi.iMaxValue, pSyncData->pszShareName)); NotifySyncMgr(pSyncData, &spi); if (!(pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)) { Trace((TEXT("Calling CSCFillSparseFiles(%s, %s)"), pSyncData->pszShareName, (pSyncData->dwSyncStatus & SDS_SYNC_IN_FULL) ? TEXT("full") : TEXT("quick"))); if (!CSCFillSparseFiles(pSyncData->pszShareName, !!(pSyncData->dwSyncStatus & SDS_SYNC_IN_FULL), CCscUpdate::_CscCallback, (DWORD_PTR)pSyncData)) { dwErr = GetLastError(); if (ERROR_OPERATION_ABORTED == dwErr) dwErr = NOERROR; } Trace((TEXT("CSCFillSparseFiles(%s) complete"), pSyncData->pszShareName)); } } else { Trace((TEXT("Skipping CSCFillSparseFiles(%s) - server is offline"), pSyncData->pszShareName)); } TraceLeaveValue(dwErr); } void CCscUpdate::PinFiles(PSYNCTHREADDATA pSyncData, BOOL bConflictPinList) { CscFilenameList *pfnl; CscFilenameList::HSHARE hShare; LPCTSTR pszFile; TraceEnter(TRACE_UPDATE, "CCscUpdate::PinFiles"); TraceAssert((m_dwSyncFlags & CSC_SYNC_PINFILES) || bConflictPinList); pfnl = m_pFileList; if (bConflictPinList) { pfnl = m_pConflictPinList; } if (!pfnl || !pfnl->GetShareHandle(pSyncData->pszShareName, &hShare)) { TraceLeaveVoid(); } CscFilenameList::FileIter fi = pfnl->CreateFileIterator(hShare); // Iterate over the filenames associated with the share. while (pszFile = fi.Next()) { TCHAR szFullPath[MAX_PATH]; TCHAR szRelativePath[MAX_PATH]; WIN32_FIND_DATA fd; ULONG cchFile = lstrlen(pszFile) + 1; // include NULL // Check for Cancel if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) break; ZeroMemory(&fd, sizeof(fd)); // Directories have a trailing "\*" if (StrChr(pszFile, TEXT('*'))) { // It's a directory. Trim off the "\*" cchFile -= 2; // When pinning at the share level, pszFile points to "*" // and cchFile is now zero. } szRelativePath[0] = TEXT('\0'); lstrcpyn(szRelativePath, pszFile, (int)min(cchFile, ARRAYSIZE(szRelativePath))); // Build the full path PathCombine(szFullPath, pSyncData->pszShareName, szRelativePath); // Get attributes and test for existence fd.dwFileAttributes = GetFileAttributes(szFullPath); if ((DWORD)-1 == fd.dwFileAttributes) continue; if (S_FALSE == m_NoPinList.IsPinAllowed(szFullPath)) { // // Policy says don't pin this file/folder. // if (FILE_ATTRIBUTE_DIRECTORY & fd.dwFileAttributes) { LogError(pSyncData->ItemID, SYNCMGRLOGLEVEL_WARNING, IDS_PIN_NOPINFOLDER_POLICY_WARNING, szFullPath); } else { LogError(pSyncData->ItemID, IDS_PIN_NOPINFILE_POLICY_WARNING, szFullPath, NOERROR, SYNCMGRLOGLEVEL_WARNING); } continue; } pszFile = PathFindFileName(szFullPath); lstrcpyn(fd.cFileName, pszFile ? pszFile : szFullPath, ARRAYSIZE(fd.cFileName)); // Check for EFS BOOL bIsEFSFile; bIsEFSFile = (FILE_ATTRIBUTE_ENCRYPTED & fd.dwFileAttributes) && !(FILE_ATTRIBUTE_DIRECTORY & fd.dwFileAttributes); if (bIsEFSFile && SkipEFSPin(pSyncData, szFullPath)) continue; if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) break; // Pin it pSyncData->dwPinHints = FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_INHERIT_USER; if (CSCPinFile(szFullPath, pSyncData->dwPinHints, NULL, NULL, NULL)) { if (bConflictPinList && m_pFileList) m_pFileList->AddFile(szFullPath, !!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)); pSyncData->cFilesToSync++; ShellChangeNotify(szFullPath, &fd, FALSE); if (bIsEFSFile && !m_bCacheIsEncrypted) { LogError(pSyncData->ItemID, IDS_PIN_ENCRYPT_WARNING, szFullPath, NOERROR, SYNCMGRLOGLEVEL_WARNING); } } else { DWORD dwError = GetLastError(); UINT idsError = GetErrorFormat(dwError); if (IDS_SHARE_CONNECT_ERROR == idsError) { LPTSTR pszErr = GetErrorText(dwError); // // Special-case the "can't connect to share" error. // Display only the share name in the error message // and abort the pinning of this share. // LogError(pSyncData->ItemID, SYNCMGRLOGLEVEL_ERROR, idsError, pSyncData->pszShareName, pszErr ? pszErr : TEXT("")); LocalFreeString(&pszErr); pSyncData->dwSyncStatus |= SDS_SYNC_CANCELLED; } else { DWORD dwSyncMgrLogLevel = SYNCMGRLOGLEVEL_ERROR; if (ERROR_INVALID_NAME == dwError) { // // File type is in the exclusion list. // This is a warning, not an error. // dwSyncMgrLogLevel = SYNCMGRLOGLEVEL_WARNING; } LogError(pSyncData->ItemID, IDS_PIN_FILE_ERROR, szFullPath, dwError, dwSyncMgrLogLevel); } pSyncData->dwSyncStatus |= SDS_SYNC_ERROR; } // If it's a directory, pin its contents if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { _Win32EnumFolder(szFullPath, !bConflictPinList && ShouldPinRecurse(szFullPath), CCscUpdate::_PinNewFilesW32Callback, (LPARAM)pSyncData); } } // Flush the shell notify queue ShellChangeNotify(NULL, TRUE); TraceLeaveVoid(); } void CCscUpdate::NotifyUndo(PSYNCTHREADDATA pSyncData, LPCTSTR pszName) { LPTSTR pszMsg; SYNCMGRPROGRESSITEM spi; spi.cbSize = sizeof(spi); spi.mask = SYNCMGRPROGRESSITEM_STATUSTEXT; spi.lpcStatusText = L" "; // Skip the share name if (PathIsPrefix(pSyncData->pszShareName, pszName)) { pszName += lstrlen(pSyncData->pszShareName); if (*pszName == TEXT('\\')) pszName++; } LPCTSTR pszFile = PathFindFileName(pszName); TCHAR szPath[MAX_PATH] = TEXT("\\"); if (*pszName) lstrcpyn(szPath, pszName, (int)min(ARRAYSIZE(szPath),(int)(pszFile-pszName))); // If we still have a name, build a string like // "undo: dir\foo.txt" to display in SyncMgr if (FormatStringID(&pszMsg, g_hInstance, IDS_UNDO_SCAN, pszFile, szPath)) { USES_CONVERSION; spi.lpcStatusText = T2CW(pszMsg); } NotifySyncMgr(pSyncData, &spi); LocalFreeString(&pszMsg); } DWORD WINAPI CCscUpdate::_UndoProgress(LPCTSTR pszItem, LPARAM lpContext) { PSYNCTHREADDATA pSyncData = reinterpret_cast(lpContext); if (pSyncData->pUndoExclusionList && pSyncData->pUndoExclusionList->FileExists(pszItem)) { return CSCPROC_RETURN_SKIP; } // Update SyncMgr pSyncData->pThis->NotifyUndo(pSyncData, pszItem); return CSCPROC_RETURN_CONTINUE; } // // The user's response to the "confirm pin encrypted" dialog is encoded // to fit into the return value from EndDialog. The PINEFS_XXXXX macros // describe this encoding. // // Bits 0 and 1 represent the user's [Yes][No][Cancel] choice. // #define PINEFS_YES 0x00000001 #define PINEFS_NO 0x00000002 #define PINEFS_CANCEL 0x00000003 #define PINEFS_YNC_MASK 0x00000003 // // Bit 31 indicates if the user checked the "Apply to all" checkbox. // #define PINEFS_APPLYTOALL 0x80000000 // // Convenience macros for indicating yes-to-all and no-to-all. // #define PINEFS_NO_TOALL (PINEFS_NO | PINEFS_APPLYTOALL) #define PINEFS_YES_TOALL (PINEFS_YES | PINEFS_APPLYTOALL) // // Returns (by way of EndDialog) one of the PINEFS_XXXXXX codes. // // PINEFS_YES - Pin this file but ask again on the next. // PINEFS_YES_TOALL - Pin this file and all encrypted files encountered. // PINEFS_NO - Don't pin this file but ask again on the next. // PINEFS_NO_TOALL - Don't pin this file nor any other encrypted files. // PINEFS_CANCEL - Don't pin this file. Cancel entire operation. // INT_PTR CALLBACK ConfirmEFSPinProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { INT_PTR nResult = 0; switch (uMsg) { case WM_INITDIALOG: { LPTSTR pszMsg = NULL; LPCTSTR pszFile = PathFindFileName((LPCTSTR)lParam); FormatStringID(&pszMsg, g_hInstance, IDS_FMT_PIN_EFS_MSG, pszFile); if (pszMsg) { SetDlgItemText(hDlg, IDC_EFS_MSG, pszMsg); LocalFree(pszMsg); } else { // // Let's be safe. On failure we won't pin an encrypted file // to a non-encrypted cache. // EndDialog(hDlg, PINEFS_NO_TOALL); } } nResult = TRUE; break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDYES: EndDialog(hDlg, BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_PINEFS_APPLYTOALL) ? PINEFS_YES_TOALL : PINEFS_YES); nResult = TRUE; break; case IDNO: EndDialog(hDlg, BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_PINEFS_APPLYTOALL) ? PINEFS_NO_TOALL : PINEFS_NO); nResult = TRUE; break; case IDCANCEL: EndDialog(hDlg, PINEFS_CANCEL); nResult = TRUE; break; default: break; } break; } return nResult; } BOOL CCscUpdate::SkipEFSPin(PSYNCTHREADDATA pSyncData, LPCTSTR pszItem) { BOOL bSkip = FALSE; if (!m_bCacheIsEncrypted) { int iResult; EnterCriticalSection(&m_csThreadList); if ((CSC_SYNC_EFS_PIN_NONE & m_dwSyncFlags) || !(CSC_SYNC_MAYBOTHERUSER & m_dwSyncFlags)) { iResult = PINEFS_NO; } else if (CSC_SYNC_EFS_PIN_ALL & m_dwSyncFlags) { iResult = PINEFS_YES; } else { // Suspend the other sync threads SetSyncThreadStatus(SyncPause, pSyncData->ItemID); iResult = (int)DialogBoxParam(g_hInstance, MAKEINTRESOURCE(IDD_CONFIRM_PIN_EFS), m_hwndDlgParent, ConfirmEFSPinProc, (LPARAM)pszItem); if (PINEFS_APPLYTOALL & iResult) { // // User checked the "apply to all" checkbox. // Persist a [Yes][No] button selection. // if (PINEFS_NO == (PINEFS_YNC_MASK & iResult)) { m_dwSyncFlags |= CSC_SYNC_EFS_PIN_NONE; } else if (PINEFS_YES == (PINEFS_YNC_MASK & iResult)) { m_dwSyncFlags |= CSC_SYNC_EFS_PIN_ALL; } } // Resume syncing SetSyncThreadStatus(SyncResume, pSyncData->ItemID); } LeaveCriticalSection(&m_csThreadList); switch (PINEFS_YNC_MASK & iResult) { default: case PINEFS_NO: bSkip = TRUE; break; case PINEFS_YES: // continue break; case PINEFS_CANCEL: // stop all threads SetItemStatus(GUID_NULL, SYNCMGRSTATUS_STOPPED); break; } } return bSkip; } HRESULT CCscUpdate::SetSyncThreadStatus(eSetSyncStatus status, REFGUID rItemID) { // Assume success here. If we don't find the thread, // it's probably already finished. HRESULT hr = S_OK; BOOL bOneItem; TraceEnter(TRACE_UPDATE, "CCscUpdate::SetSyncThreadStatus"); bOneItem = (SyncStop == status && !IsEqualGUID(rItemID, GUID_NULL)); EnterCriticalSection(&m_csThreadList); if (NULL != m_hSyncThreads) { int cItems = DPA_GetPtrCount(m_hSyncThreads); SYNCMGRPROGRESSITEM spi = {0}; DWORD (WINAPI *pfnStartStop)(HANDLE); pfnStartStop = ResumeThread; spi.cbSize = sizeof(spi); spi.mask = SYNCMGRPROGRESSITEM_STATUSTYPE | SYNCMGRPROGRESSITEM_STATUSTEXT; spi.lpcStatusText = L" "; spi.dwStatusType = SYNCMGRSTATUS_UPDATING; if (SyncPause == status) { spi.dwStatusType = SYNCMGRSTATUS_PAUSED; pfnStartStop = SuspendThread; } while (cItems > 0) { PSYNCTHREADDATA pSyncData; --cItems; pSyncData = (PSYNCTHREADDATA)DPA_FastGetPtr(m_hSyncThreads, cItems); TraceAssert(NULL != pSyncData); if (SyncStop == status) { // Tell the thread to abort if (!bOneItem || IsEqualGUID(rItemID, pSyncData->ItemID)) { pSyncData->dwSyncStatus |= SDS_SYNC_CANCELLED; if (bOneItem) break; } } else { // Suspend or resume the thread if it's not the current thread if (!IsEqualGUID(rItemID, pSyncData->ItemID)) (*pfnStartStop)(pSyncData->hThread); m_pSyncMgrCB->Progress(pSyncData->ItemID, &spi); } } } LeaveCriticalSection(&m_csThreadList); TraceLeaveResult(hr); } HRESULT CCscUpdate::GetSilentFolderList(void) { HRESULT hr = S_OK; delete m_pSilentFolderList; m_pSilentFolderList = new CscFilenameList; delete m_pSpecialFolderList; m_pSpecialFolderList = new CscFilenameList; if (NULL == m_pSilentFolderList || NULL == m_pSpecialFolderList) { delete m_pSilentFolderList; m_pSilentFolderList = NULL; delete m_pSpecialFolderList; m_pSpecialFolderList = NULL; hr = E_OUTOFMEMORY; } else { BuildSilentFolderList(m_pSilentFolderList, m_pSpecialFolderList); if (0 == m_pSilentFolderList->GetShareCount()) { delete m_pSilentFolderList; m_pSilentFolderList = NULL; } if (0 == m_pSpecialFolderList->GetShareCount()) { delete m_pSpecialFolderList; m_pSpecialFolderList = NULL; } } return hr; } void BuildSilentFolderList(CscFilenameList *pfnlSilentFolders, CscFilenameList *pfnlSpecialFolders) { // // We will silently handle sync conflicts in any of the folders // below that have a '1' after them. // // If we get complaints about conflicts in folders that we // think we can handle silently and safely, add them. // // Note that CSIDL_PERSONAL (MyDocs) and CSIDL_MYPICTURES // should probably never be silent, since the user // interacts with them directly. // // This list corresponds to the list of shell folders that may // be redirected. See also // HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders // static const int s_csidlFolders[][2] = { { CSIDL_PROGRAMS, 0 }, { CSIDL_PERSONAL, 0 }, { CSIDL_FAVORITES, 0 }, { CSIDL_STARTUP, 0 }, { CSIDL_RECENT, 1 }, { CSIDL_SENDTO, 0 }, { CSIDL_STARTMENU, 1 }, { CSIDL_DESKTOPDIRECTORY, 0 }, { CSIDL_NETHOOD, 0 }, { CSIDL_TEMPLATES, 0 }, { CSIDL_APPDATA, 1 }, { CSIDL_PRINTHOOD, 0 }, { CSIDL_MYPICTURES, 0 }, { CSIDL_PROFILE, 1 }, { CSIDL_ADMINTOOLS, 0 }, }; TCHAR szPath[MAX_PATH]; for (int i = 0; i < ARRAYSIZE(s_csidlFolders); i++) { if (SHGetSpecialFolderPath(NULL, szPath, s_csidlFolders[i][0] | CSIDL_FLAG_DONT_VERIFY, FALSE)) { // We only want UNC net paths LPTSTR pszUNC = NULL; GetRemotePath(szPath, &pszUNC); if (!pszUNC) continue; if (s_csidlFolders[i][1]) { if (pfnlSilentFolders) pfnlSilentFolders->AddFile(pszUNC, true); } else { if (pfnlSpecialFolders) pfnlSpecialFolders->AddFile(pszUNC, true); } LocalFreeString(&pszUNC); } } } /////////////////////////////////////////////////////////////////////////////// // // // SyncMgr integration (ISyncMgrEnumItems) // // // /////////////////////////////////////////////////////////////////////////////// CUpdateEnumerator::CUpdateEnumerator(PCSCUPDATE pUpdate) : m_cRef(1), m_pUpdate(pUpdate), m_hFind(INVALID_HANDLE_VALUE), m_bEnumFileSelection(FALSE), m_cCheckedItemsEnumerated(0) { DllAddRef(); if (m_pUpdate) { m_pUpdate->AddRef(); if (m_pUpdate->m_pFileList) { m_bEnumFileSelection = TRUE; m_SelectionIterator = m_pUpdate->m_pFileList->CreateShareIterator(); } } } CUpdateEnumerator::~CUpdateEnumerator() { if (m_hFind != INVALID_HANDLE_VALUE) CSCFindClose(m_hFind); DoRelease(m_pUpdate); DllRelease(); } STDMETHODIMP CUpdateEnumerator::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CUpdateEnumerator, ISyncMgrEnumItems), { 0 }, }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_(ULONG) CUpdateEnumerator::AddRef() { return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) CUpdateEnumerator::Release() { if (InterlockedDecrement(&m_cRef)) return m_cRef; delete this; return 0; } STDMETHODIMP CUpdateEnumerator::Next(ULONG celt, LPSYNCMGRITEM rgelt, PULONG pceltFetched) { HRESULT hr = S_OK; ULONG cFetched = 0; LPSYNCMGRITEM pItem = rgelt; WIN32_FIND_DATA fd = {0}; DWORD dwShareStatus = 0; DWORD dwSyncFlags; CscFilenameList::HSHARE hShare; LPCTSTR pszShareName = NULL; TraceEnter(TRACE_UPDATE, "CUpdateEnumerator::Next"); TraceAssert(m_pUpdate != NULL); if (NULL == rgelt) TraceLeaveResult(E_INVALIDARG); dwSyncFlags = m_pUpdate->m_dwSyncFlags; while (cFetched < celt) { CSCEntry *pShareEntry; CSCSHARESTATS shareStats; CSCGETSTATSINFO si = { SSEF_NONE, SSUF_TOTAL | SSUF_PINNED | SSUF_MODIFIED | SSUF_SPARSE | SSUF_DIRS, false, false }; if (m_bEnumFileSelection) { if (!m_SelectionIterator.Next(&hShare)) break; pszShareName = m_pUpdate->m_pFileList->GetShareName(hShare); CSCQueryFileStatus(pszShareName, &dwShareStatus, NULL, NULL); fd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; lstrcpyn(fd.cFileName, pszShareName, ARRAYSIZE(fd.cFileName)); } else { if (m_hFind == INVALID_HANDLE_VALUE) { m_hFind = CacheFindFirst(NULL, &fd, &dwShareStatus, NULL, NULL, NULL); if (m_hFind == INVALID_HANDLE_VALUE) { // The database is empty, so there's nothing to enumerate.. break; } pszShareName = fd.cFileName; } else if (CacheFindNext(m_hFind, &fd, &dwShareStatus, NULL, NULL, NULL)) { pszShareName = fd.cFileName; } else break; } TraceAssert(pszShareName); // // This was proposed as a fix for part of 383011. However, // that bug only applies in a multi-user scenario, and if there // are more than 3 users using the machine, this would cause a // user whose SID had been expelled from the CSC database to not // be able to sync a share where they do indeed have access. // // // If the current user has no access to the share, don't enumerate it. // if (!(CscAccessUser(dwShareStatus) || CscAccessGuest(dwShareStatus))) // continue; // Count the # of pinned files, sparse files, etc. _GetShareStatisticsForUser(pszShareName, &si, &shareStats); // The root of a special folder is pinned with a pin count, but // not the user-hint flag, so _GetShareStats doesn't count it. // We need to count this for some of the checks below. // (If the share is manual-cache, these look exactly like the // Siemens scenario in 341786, but we want to sync them.) if (shareStats.cTotal && m_pUpdate->IsSpecialFolderShare(pszShareName)) { shareStats.cPinned++; if (dwSyncFlags & CSC_SYNC_LOGOFF) { // // At logoff, we want to run the FrankAr code on all // 'special' folder shares. // Customers expect folder redirection of special folders // to ensure all contents are cached. // dwSyncFlags |= CSC_SYNC_IN_FULL; } } // If we're pinning, then even if nothing is sparse now, // there will be sparse files after we pin them. if (dwSyncFlags & CSC_SYNC_PINFILES) { shareStats.cSparse++; shareStats.cTotal++; } // If there's nothing cached on this share, then don't even // enumerate it to SyncMgr. This avoids listing extra junk // in SyncMgr. if ((0 == shareStats.cTotal) || (shareStats.cTotal == shareStats.cDirs && 0 == shareStats.cPinned)) { // Either there is nothing cached for this share, or the only // things found were unpinned dirs (no files, no pinned dirs). // The second case can happen if you delete files from the viewer, // in which case you think you deleted everything but the viewer // doesn't show directories, so they weren't deleted. Trace((TEXT("Nothing cached on %s, not enumerating"), pszShareName)); continue; } if ((dwShareStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK) == FLAG_CSC_SHARE_STATUS_NO_CACHING) { // // Don't enumerate "no-cache" shares if there's nothing to merge. // // These can exist in the cache if the share was previously // cacheable, but has since been changed to "no caching". // // If there's something to merge, we should still sync it to // get everything squared away. // if (!((dwSyncFlags & CSC_SYNC_OUT) && (shareStats.cModified))) { Trace((TEXT("Not enumerating no-cache share %s"), pszShareName)); continue; } Trace((TEXT("Enumerating no-cache share %s with offline changes."), pszShareName)); } // Explorer has shut down by the time we do a logoff sync. Hide the // Properties button at logoff so we don't end up restarting Explorer. pItem->dwFlags = (dwSyncFlags & CSC_SYNC_LOGOFF) ? 0 : SYNCMGRITEM_HASPROPERTIES; if ((dwShareStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK) == FLAG_CSC_SHARE_STATUS_MANUAL_REINT && 0 == shareStats.cPinned && !(dwSyncFlags & CSC_SYNC_PINFILES)) { // On a manual share, if nothing is pinned (and we aren't pinning) // then we don't want to call CSCFillSparseFiles on the share. // Raid #341786 Trace((TEXT("Manual cache share '%s' has only autocached files"), pszShareName)); // However, if there is something to merge, then we need to sync. if (!((dwSyncFlags & CSC_SYNC_OUT) && shareStats.cModified)) { Trace((TEXT("Not enumerating manual-cache share %s"), pszShareName)); continue; } // There is something to merge, so enumerate the share but // tell SyncMgr that it's temporary so it doesn't save state // for this share. pItem->dwFlags |= SYNCMGRITEM_TEMPORARY; } // // In some circumstances, we may want to merge even if there // are no modified files, in order to show the open files warning. // // See comments in CCscUpdate::_SyncThread // if (0 == shareStats.cModified) { if ((FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwShareStatus) && (FLAG_CSC_SHARE_STATUS_FILES_OPEN & dwShareStatus)) { shareStats.cModified++; } } // Enumerate this share cFetched++; // Get existing share entry or create a new one pShareEntry = m_pUpdate->m_ShareLog.Add(pszShareName); if (!pShareEntry) TraceLeaveResult(E_OUTOFMEMORY); pItem->cbSize = sizeof(SYNCMGRITEM); pItem->ItemID = pShareEntry->Guid(); pItem->hIcon = g_hCscIcon; // SYNCMGRITEM_TEMPORARY causes items to not show up in // SyncMgr's logon/logoff settings page. Raid #237288 //if (0 == shareStats.cPinned) // pItem->dwFlags |= SYNCMGRITEM_TEMPORARY; if (ERROR_SUCCESS == m_pUpdate->GetLastSyncTime(pszShareName, &pItem->ftLastUpdate)) pItem->dwFlags |= SYNCMGRITEM_LASTUPDATETIME; // // Determine whether this share needs syncing. // // At settings time, assume everything needs syncing (check everything) // // If outbound, shares with modified files are checked // If inbound (full), shares with sparse or pinned files are checked // If inbound (quick), shares with sparse files are checked // // Anything else doesn't need to be sync'ed at this time (unchecked) // pItem->dwItemState = SYNCMGRITEMSTATE_CHECKED; if (!(dwSyncFlags & CSC_SYNC_SETTINGS) && !((dwSyncFlags & CSC_SYNC_OUT) && shareStats.cModified) && !((dwSyncFlags & CSC_SYNC_IN_FULL) && shareStats.cTotal ) && !((dwSyncFlags & CSC_SYNC_IN_QUICK) && shareStats.cSparse )) { pItem->dwItemState = SYNCMGRITEMSTATE_UNCHECKED; } // Get friendly share name here LPITEMIDLIST pidl = NULL; SHFILEINFO sfi = {0}; if (SUCCEEDED(SHSimpleIDListFromFindData(pszShareName, &fd, &pidl))) { SHGetFileInfo((LPCTSTR)pidl, 0, &sfi, sizeof(sfi), SHGFI_PIDL | SHGFI_DISPLAYNAME); SHFree(pidl); } if (TEXT('\0') != sfi.szDisplayName[0]) pszShareName = sfi.szDisplayName; SHTCharToUnicode((LPTSTR)pszShareName, pItem->wszItemName, ARRAYSIZE(pItem->wszItemName)); if (SYNCMGRITEMSTATE_CHECKED == pItem->dwItemState) { m_cCheckedItemsEnumerated++; Trace((TEXT("Enumerating %s, checked"), pszShareName)); } else { Trace((TEXT("Enumerating %s, unchecked"), pszShareName)); } pItem++; } if (pceltFetched) *pceltFetched = cFetched; if (cFetched != celt) hr = S_FALSE; if ((S_FALSE == hr) && 0 == m_cCheckedItemsEnumerated && (CSC_SYNC_SHOWUI_ALWAYS & dwSyncFlags)) { // // Special-case where we're synching nothing but still // want to display SyncMgr progress UI. We enumerate a // special string rather than a share name for display in // the status UI. Force hr == S_OK so the caller will accept // this "dummy" item. Next() will be called once more but // m_cCheckedItemsEnumerated will be 1 so this block won't be // entered and we'll return S_FALSE indicating the end of the // enumeration. // pItem->cbSize = sizeof(SYNCMGRITEM); pItem->hIcon = g_hCscIcon; pItem->dwFlags = 0; pItem->dwItemState = SYNCMGRITEMSTATE_CHECKED; pItem->ItemID = GUID_CscNullSyncItem; UINT idString = IDS_NULLSYNC_ITEMNAME; if ((CSC_SYNC_OUT & dwSyncFlags) && !((CSC_SYNC_IN_QUICK | CSC_SYNC_IN_FULL) & dwSyncFlags)) { // Use different text if we are only merging idString = IDS_NULLMERGE_ITEMNAME; } LoadStringW(g_hInstance, idString, pItem->wszItemName, ARRAYSIZE(pItem->wszItemName)); m_cCheckedItemsEnumerated = 1; TraceMsg("Enumerating NULL item"); hr = S_OK; } TraceLeaveResult(hr); } STDMETHODIMP CUpdateEnumerator::Skip(ULONG celt) { return Next(celt, NULL, NULL); } STDMETHODIMP CUpdateEnumerator::Reset() { m_cCheckedItemsEnumerated = 0; if (m_bEnumFileSelection) { m_SelectionIterator.Reset(); } else if (m_hFind != INVALID_HANDLE_VALUE) { CSCFindClose(m_hFind); m_hFind = INVALID_HANDLE_VALUE; } return S_OK; } STDMETHODIMP CUpdateEnumerator::Clone(LPSYNCMGRENUMITEMS *ppenum) { return E_NOTIMPL; }