|
|
//+-------------------------------------------------------------------------
//
// 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 <openfile.h> // 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<max+1>).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 <this share>" 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<PSYNCTHREADDATA>(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; }
|