Last active
June 5, 2021 09:10
-
-
Save kennykerr/d72b59a7674001f51431cb973df84cdd to your computer and use it in GitHub Desktop.
Curious how SyncTools (https://kennykerr.ca/2013/01/04/synctools-for-sysinternals/) was implemented? Code circa 2012
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include"Precompiled.h" | |
static char const Constant_LinkExpression[] = ">([^<]+)<A HREF=\"([^\"]+)\">"; | |
static char const Constant_DefaultUrl[] = "http://live.sysinternals.com/"; | |
static char const Constant_DefaultIgnore[] = "*.sys;*.html;*.cnt;*.scr;*.hlp;*.txt;*.asp;*.aspx"; | |
static char const Constant_Banner[] = "SyncTools for Sysinternals. Copyright (c) 2013 Kenny Kerr\n\n"; | |
static char const Constant_SyncStatus[] = ".SyncStatus"; | |
static char const Constant_SyncIgnore[] = ".SyncIgnore"; | |
static UINT const Constant_SyncStatusFormat = 2; | |
static char const Report_CheckingForUpdates[] = "Checking for updates"; | |
static char const Report_UpToDateSingular[] = "1 file is up to date\n"; | |
static char const Report_UpToDatePlural[] = "%d files are up to date\n"; | |
static char const Report_UpdateSingular[] = "1 update to download\n"; | |
static char const Report_UpdatePlural[] = "%d updates to download\n"; | |
static char const Report_NewSingular[] = "1 new file to download\n"; | |
static char const Report_NewPlural[] = "%d new files to download\n"; | |
static char const Report_UpdateNone[] = "No updates are available\n"; | |
static char const Report_NewFile[] = "* %s"; | |
static char const Report_UpdatedFile[] = "u %s"; | |
static char const Error_UrlExpected[] = "The -u argument expects an URL"; | |
static char const Error_DirectoryExpected[] = "The -d argument expects a directory"; | |
static char const Error_InvalidArgument[] = "Only the -u and -d arguments may be used"; | |
static char const Error_SaveStatusFailed[] = "Failed to save .SyncStatus > "; | |
using namespace Microsoft::WRL; | |
using namespace Microsoft::WRL::Wrappers; | |
using namespace std; | |
using namespace msl::utilities; | |
struct ProgressCallback : RuntimeClass<RuntimeClassFlags<ClassicCom>, IBindStatusCallback> | |
{ | |
HANDLE m_console; | |
COORD m_pos; | |
CONSOLE_CURSOR_INFO m_show; | |
ProgressCallback() : | |
m_console(GetStdHandle(STD_OUTPUT_HANDLE)) | |
{ | |
VERIFY(GetConsoleCursorInfo(m_console, &m_show)); | |
m_show.bVisible = false; | |
VERIFY(SetConsoleCursorInfo(m_console, &m_show)); | |
} | |
~ProgressCallback() | |
{ | |
m_show.bVisible = true; | |
VERIFY(SetConsoleCursorInfo(m_console, &m_show)); | |
} | |
COORD GetPosition() const | |
{ | |
CONSOLE_SCREEN_BUFFER_INFO info; | |
VERIFY(GetConsoleScreenBufferInfo(m_console, &info)); | |
return info.dwCursorPosition; | |
} | |
void SetPosition(COORD const & pos) const | |
{ | |
VERIFY(SetConsoleCursorPosition(m_console, pos)); | |
} | |
void Before() | |
{ | |
CONSOLE_SCREEN_BUFFER_INFO info; | |
VERIFY(GetConsoleScreenBufferInfo(m_console, &info)); | |
m_pos = info.dwCursorPosition; | |
} | |
void After() | |
{ | |
VERIFY(SetConsoleCursorPosition(m_console, m_pos)); | |
printf(" "); | |
VERIFY(SetConsoleCursorPosition(m_console, m_pos)); | |
} | |
HRESULT __stdcall OnProgress(ULONG progress, ULONG progressMax, ULONG, LPCWSTR) | |
{ | |
if (0 < progress && progress <= progressMax) | |
{ | |
float percentF = progress * 100.0f / progressMax; | |
UINT percentU = min(100U, static_cast<UINT>(percentF)); | |
VERIFY(SetConsoleCursorPosition(m_console, m_pos)); | |
printf("%3d%%", percentU); | |
} | |
return S_OK; | |
} | |
HRESULT __stdcall OnStartBinding(DWORD, IBinding *) { return E_NOTIMPL; } | |
HRESULT __stdcall GetPriority(LONG *) { return E_NOTIMPL; } | |
HRESULT __stdcall OnLowResource(DWORD) { return E_NOTIMPL; } | |
HRESULT __stdcall OnStopBinding(HRESULT, LPCWSTR) { return E_NOTIMPL; } | |
HRESULT __stdcall GetBindInfo(DWORD *, BINDINFO *) { return E_NOTIMPL; } | |
HRESULT __stdcall OnDataAvailable(DWORD, DWORD, FORMATETC *, STGMEDIUM *) { return E_NOTIMPL; } | |
HRESULT __stdcall OnObjectAvailable(REFIID, IUnknown *) { return E_NOTIMPL; } | |
}; | |
struct LessNoCase : public binary_function<CString, CString, bool> | |
{ | |
bool operator()(CString const & left, CString const & right) | |
{ | |
return 0 > left.CompareNoCase(right); | |
} | |
}; | |
class MapFile | |
{ | |
FileHandle m_mapping; | |
void const * m_view; | |
LARGE_INTEGER m_size; | |
public: | |
MapFile() : | |
m_view(), | |
m_size() | |
{ | |
} | |
~MapFile() | |
{ | |
if (m_view) | |
{ | |
VERIFY(UnmapViewOfFile(m_view)); | |
} | |
} | |
bool Map(PCSTR filename) | |
{ | |
ASSERT(!m_mapping.IsValid()); | |
ASSERT(nullptr == m_view); | |
FileHandle file(CreateFile(filename, | |
GENERIC_READ, | |
FILE_SHARE_READ, | |
nullptr, | |
OPEN_EXISTING, | |
FILE_FLAG_OVERLAPPED, | |
nullptr)); | |
if (!file.IsValid()) | |
{ | |
return false; | |
} | |
if (!GetFileSizeEx(file.Get(), &m_size)) | |
{ | |
return false; | |
} | |
if (0 == m_size.QuadPart) | |
{ | |
SetLastError(ERROR_FILE_INVALID); | |
return false; | |
} | |
m_mapping.Attach(CreateFileMapping(file.Get(), | |
nullptr, | |
PAGE_READONLY, | |
m_size.HighPart, | |
m_size.LowPart, | |
nullptr)); | |
if (!m_mapping.IsValid()) | |
{ | |
return false; | |
} | |
m_view = MapViewOfFile(m_mapping.Get(), | |
FILE_MAP_READ, | |
0, // offset high | |
0, // offset low | |
0); // entire file | |
return nullptr != m_view; | |
} | |
void const * View() const | |
{ | |
ASSERT(m_view); | |
return m_view; | |
} | |
ULONGLONG Size() const | |
{ | |
return m_size.QuadPart; | |
} | |
}; | |
struct Download | |
{ | |
Download(CString const & fileName, | |
CString const & signature, | |
bool newFile) : | |
FileName(fileName), | |
Signature(signature), | |
New(newFile) | |
{ | |
} | |
CString FileName; | |
CString Signature; | |
bool New; | |
}; | |
typedef map<CString, CString, LessNoCase> Status; | |
static void PrepareUrl(CString & url) | |
{ | |
if (url.IsEmpty()) | |
{ | |
url = Constant_DefaultUrl; | |
} | |
else | |
{ | |
if ('/' != url[url.GetLength() - 1]) | |
{ | |
url += '/'; | |
} | |
} | |
} | |
static CString GetCurrentDirectory() | |
{ | |
DWORD size = GetCurrentDirectoryA(0, nullptr); | |
ASSERT(size); | |
CString directory; | |
auto buffer = directory.GetBufferSetLength(size - 1); | |
VERIFY(GetCurrentDirectoryA(size, buffer)); | |
return directory; | |
} | |
static bool PrepareDirectory(CString & directory) | |
{ | |
CPath path; | |
if (directory.IsEmpty()) | |
{ | |
directory = GetCurrentDirectory(); | |
} | |
else if (PathIsRelative(directory)) | |
{ | |
path.Combine(GetCurrentDirectory(), directory); | |
directory = path.m_strPath; | |
} | |
if (!PathFileExists(directory)) | |
{ | |
DWORD error = SHCreateDirectoryEx(nullptr, // no window | |
directory, | |
nullptr); // security | |
if (ERROR_SUCCESS != error) | |
{ | |
SetLastError(error); | |
return false; | |
} | |
} | |
return true; | |
} | |
template <typename T> | |
bool BinaryRead(BYTE const *& pos, | |
BYTE const * end, | |
T & value) | |
{ | |
return BinaryRead(pos, | |
end, | |
&value, | |
sizeof(T)); | |
} | |
template <typename T> | |
bool BinaryWrite(HANDLE file, | |
T const & value) | |
{ | |
return BinaryWrite(file, | |
&value, | |
sizeof(T)); | |
} | |
static bool BinaryRead(BYTE const *& pos, | |
BYTE const * end, | |
void * value, | |
UINT valueSize) | |
{ | |
ASSERT(pos); | |
ASSERT(end); | |
ASSERT(pos <= end); | |
ASSERT(value); | |
ASSERT(0 < valueSize); | |
if (SafeInt<UINT>(end - pos) < valueSize) | |
{ | |
SetLastError(ERROR_HANDLE_EOF); | |
return false; | |
} | |
memcpy(value, | |
pos, | |
valueSize); | |
pos += valueSize; | |
return true; | |
} | |
static bool BinaryRead(BYTE const *& pos, | |
BYTE const * end, | |
CString& value) | |
{ | |
ASSERT(pos); | |
ASSERT(end); | |
ASSERT(pos <= end); | |
UINT length; | |
if (!BinaryRead(pos, end, length)) | |
{ | |
SetLastError(ERROR_HANDLE_EOF); | |
return false; | |
} | |
if (SafeInt<UINT>(end - pos) < ((length + 1))) | |
{ | |
SetLastError(ERROR_HANDLE_EOF); | |
return false; | |
} | |
value = CString(reinterpret_cast<char const *>(pos), | |
length); | |
pos += (length + 1); | |
return true; | |
} | |
static bool BinaryWrite(HANDLE file, | |
void const * value, | |
UINT valueSize) | |
{ | |
ASSERT(INVALID_HANDLE_VALUE != file); | |
ASSERT(value); | |
ASSERT(0 < valueSize); | |
DWORD bytesCopied; | |
BOOL result = WriteFile(file, | |
value, | |
valueSize, | |
&bytesCopied, | |
nullptr); // overlapped | |
return result && valueSize == bytesCopied; | |
} | |
static bool BinaryWrite(HANDLE file, | |
const CString& value) | |
{ | |
ASSERT(INVALID_HANDLE_VALUE != file); | |
if (!BinaryWrite(file, value.GetLength())) | |
{ | |
return false; | |
} | |
DWORD const bytesToCopy = ((value.GetLength() + 1)); | |
DWORD bytesCopied; | |
BOOL result = WriteFile(file, | |
value.GetString(), | |
bytesToCopy, | |
&bytesCopied, | |
nullptr); // overlapped | |
return result && bytesToCopy == bytesCopied; | |
} | |
static bool SaveStatus(CString const & directory, Status const & status) | |
{ | |
CPath path = directory; | |
path.Append(Constant_SyncStatus); | |
FileHandle file(CreateFile(path, | |
GENERIC_WRITE, | |
0, // no sharing while writin | |
nullptr, // security | |
CREATE_ALWAYS, | |
FILE_ATTRIBUTE_NORMAL, | |
nullptr)); // template | |
if (!file.IsValid()) | |
{ | |
return false; | |
} | |
if (!BinaryWrite(file.Get(), Constant_SyncStatusFormat)) | |
{ | |
return false; | |
} | |
if (!BinaryWrite(file.Get(), status.size())) return false; | |
for (auto iter = status.begin(); iter != status.end(); ++iter) | |
{ | |
if (!BinaryWrite(file.Get(), iter->first)) return false; | |
if (!BinaryWrite(file.Get(), iter->second)) return false; | |
} | |
return true; | |
} | |
static bool LoadStatus(CString const & directory, Status & status) | |
{ | |
CPath path = directory; | |
path.Append(Constant_SyncStatus); | |
MapFile map; | |
if (!map.Map(path)) | |
{ | |
return false; | |
} | |
auto pos = static_cast<BYTE const *>(map.View()); | |
auto end = pos + map.Size(); | |
UINT version; | |
if (!BinaryRead(pos, end, version)) | |
{ | |
return false; | |
} | |
if (Constant_SyncStatusFormat != version) | |
{ | |
return false; | |
} | |
UINT files; | |
if (!BinaryRead(pos, end, files)) | |
{ | |
return false; | |
} | |
for (UINT i = 0; i < files; ++i) | |
{ | |
CString name; | |
if (!BinaryRead(pos, end, name)) | |
{ | |
return false; | |
} | |
CString signature; | |
if (!BinaryRead(pos, end, signature)) | |
{ | |
return false; | |
} | |
status[name] = signature; | |
} | |
return true; | |
} | |
static bool LoadIgnore(CString const & directory, CString & ignore) | |
{ | |
CPath path = directory; | |
path.Append(Constant_SyncIgnore); | |
MapFile map; | |
if (!map.Map(path)) | |
{ | |
return false; | |
} | |
auto chars = static_cast<char const *>(map.View()); | |
int size = SafeInt<int>(map.Size()); | |
if (!size) | |
{ | |
return false; | |
} | |
ignore = CString(chars, size); | |
ignore.Trim(); | |
ignore.Replace("\r\n", ";"); | |
ignore.Replace("\n", ";"); | |
return !ignore.IsEmpty(); | |
} | |
static CString Format(DWORD errorCode, | |
HMODULE module = nullptr) | |
{ | |
PSTR buffer = nullptr; | |
CString message; | |
DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; | |
if (module) | |
{ | |
flags |= FORMAT_MESSAGE_FROM_HMODULE; | |
} | |
if (FormatMessage(flags, | |
module, | |
errorCode, | |
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | |
reinterpret_cast<PSTR>(&buffer), | |
0, // size | |
nullptr)) // args | |
{ | |
message = buffer; | |
LocalFree(buffer); | |
message.Trim(); | |
} | |
return message; | |
}; | |
static void PrintFileError(DWORD error) | |
{ | |
printf("file error: %s", Format(error)); | |
} | |
static void PrintHttpError(DWORD error) | |
{ | |
printf("web error: %s", Format(error)); | |
} | |
int main(int argc, char ** argv) | |
{ | |
printf(Constant_Banner); | |
// 1. Check for -u <URL> and -d <DIR> arguments | |
CString url; | |
CString directory; | |
if (1 < argc) | |
{ | |
auto begin = &argv[1]; | |
auto end = begin + argc - 1; | |
for (auto arg = begin; arg != end; ++arg) | |
{ | |
if (0 == strcmp("-u", *arg)) | |
{ | |
++arg; | |
if (arg == end) | |
{ | |
printf(Error_UrlExpected); | |
return 0; | |
} | |
url = *arg; | |
} | |
else if (0 == strcmp("-d", *arg)) | |
{ | |
++arg; | |
if (arg == end) | |
{ | |
printf(Error_DirectoryExpected); | |
return 0; | |
} | |
directory = *arg; | |
} | |
else | |
{ | |
printf(Error_InvalidArgument); | |
return 0; | |
} | |
} | |
} | |
PrepareUrl(url); | |
if (!PrepareDirectory(directory)) | |
{ | |
PrintFileError(GetLastError()); | |
return 0; | |
} | |
// 2. Load .syncstatus | |
Status status; | |
if (!LoadStatus(directory, status)) | |
{ | |
status.clear(); | |
} | |
// 3. Load .syncignore | |
CString ignore; | |
if (!LoadIgnore(directory, ignore)) | |
{ | |
ignore = Constant_DefaultIgnore; | |
} | |
// 4. Download index | |
ProgressCallback progress; | |
progress.Before(); | |
char cache[1024]; | |
auto hr = URLDownloadToCacheFile(nullptr, // caller | |
url, | |
cache, | |
_countof(cache), | |
0, // reserved | |
&progress); | |
progress.After(); | |
if (S_OK != hr) | |
{ | |
PrintHttpError(hr); | |
return 0; | |
} | |
MapFile index; | |
if (!index.Map(cache)) | |
{ | |
PrintFileError(GetLastError()); | |
return 0; | |
} | |
regex link(Constant_LinkExpression); | |
CPath fullName; | |
auto begin = static_cast<PCSTR>(index.View()); | |
auto end = begin + index.Size(); | |
vector<Download> downloads; | |
UINT countUpToDate = 0; | |
UINT countNew = 0; | |
UINT countUpdates = 0; | |
for (auto i = cregex_iterator(begin, end, link); i != cregex_iterator(); ++i) | |
{ | |
auto & match = *i; | |
auto & signatureMatch = match[1]; | |
auto & pathMatch = match[2]; | |
CString path(pathMatch.first, pathMatch.length()); | |
int const fileNamePos = path.ReverseFind(L'/'); | |
if (path.GetLength() - 1 == fileNamePos) // skip directories identified by trailing slashes | |
{ | |
continue; | |
} | |
auto fileName = path.GetString() + fileNamePos + 1; | |
if (S_OK == PathMatchSpecEx(fileName, | |
ignore, | |
PMSF_MULTIPLE)) | |
{ | |
continue; | |
} | |
CString signature(signatureMatch.first, signatureMatch.length()); | |
signature.Trim(); | |
auto fileStatus = status.find(fileName); | |
fullName.Combine(directory, fileName); | |
bool newFile = !PathFileExists(fullName); | |
if (fileStatus == status.end() || | |
signature != fileStatus->second || | |
newFile) | |
{ | |
downloads.emplace_back(fileName, signature, newFile); | |
if (newFile) | |
{ | |
++countNew; | |
} | |
else | |
{ | |
++countUpdates; | |
} | |
} | |
else | |
{ | |
++countUpToDate; | |
} | |
} | |
if (0 < countUpToDate) | |
{ | |
if (1 == countUpToDate) | |
{ | |
printf(Report_UpToDateSingular); | |
} | |
else | |
{ | |
printf(Report_UpToDatePlural, countUpToDate); | |
} | |
} | |
if (downloads.empty()) | |
{ | |
printf(Report_UpdateNone); | |
return 0; | |
} | |
if (0 < countUpdates) | |
{ | |
if (1 == countUpdates) | |
{ | |
printf(Report_UpdateSingular); | |
} | |
else if (1 < countUpdates) | |
{ | |
printf(Report_UpdatePlural, countUpdates); | |
} | |
} | |
if (0 < countNew) | |
{ | |
if (1 == countNew) | |
{ | |
printf(Report_NewSingular); | |
} | |
else if (1 < countNew) | |
{ | |
printf(Report_NewPlural, countNew); | |
} | |
} | |
printf("\n"); | |
// 5. Compare each link with the .syncstatus | |
// 6. Filter out any files from .syncignore | |
// 7. Download remaining files marking them with *-new u-updated !-error | |
UINT total = 0; | |
for (auto i = downloads.begin(); i != downloads.end(); ++i) | |
{ | |
CString fileUrl = url + i->FileName; | |
auto pos = progress.GetPosition(); | |
printf(" %s ", i->FileName); | |
progress.Before(); | |
hr = URLDownloadToCacheFile(nullptr, // caller | |
fileUrl, | |
cache, | |
_countof(cache), | |
0, // reserved | |
&progress); | |
progress.After(); | |
progress.SetPosition(pos); | |
if (S_OK != hr) | |
{ | |
printf("! %s > ", i->FileName); | |
PrintHttpError(hr); | |
printf("\n"); | |
continue; | |
} | |
fullName.Combine(directory, i->FileName); | |
auto move = [&] () -> bool | |
{ | |
return 0 != MoveFileEx(cache, | |
fullName, | |
MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH); | |
}; | |
bool moveSucceeded; | |
int moveRetry = 0; | |
for (;;) | |
{ | |
moveSucceeded = move(); | |
if (!moveSucceeded && ERROR_SHARING_VIOLATION == GetLastError() && ++moveRetry < 4) | |
{ | |
Sleep(500); | |
} | |
else | |
{ | |
break; | |
} | |
} | |
if (!moveSucceeded) | |
{ | |
printf("! %s > ", i->FileName); | |
PrintFileError(GetLastError()); | |
printf("\n"); | |
continue; | |
} | |
++total; | |
if (i->New) | |
{ | |
printf("* "); | |
} | |
else | |
{ | |
printf("u "); | |
} | |
printf(i->FileName); | |
status[i->FileName] = i->Signature; | |
DWORD handle = 0; | |
DWORD size = ::GetFileVersionInfoSize(fullName, &handle); | |
if (0 != size) | |
{ | |
CAtlArray<BYTE> buffer; | |
if (buffer.SetCount(size)) | |
{ | |
if (::GetFileVersionInfo(fullName, | |
handle, | |
size, | |
buffer.GetData())) | |
{ | |
VS_FIXEDFILEINFO* info = 0; | |
UINT length = 0; | |
if (::VerQueryValue(buffer.GetData(), | |
"\\", | |
reinterpret_cast<void**>(&info), | |
&length)) | |
{ | |
CString version; | |
version.Format("%d.%d.%d.%d", | |
HIWORD(info->dwFileVersionMS), | |
LOWORD(info->dwFileVersionMS), | |
HIWORD(info->dwFileVersionLS), | |
LOWORD(info->dwFileVersionLS)); | |
int newLength = version.GetLength(); | |
while (0 <= newLength - 2 && | |
L'0' == version[newLength - 1] && | |
L'.' == version[newLength - 2]) | |
{ | |
newLength -= 2; | |
} | |
version.Truncate(newLength); | |
if (!version.IsEmpty()) | |
{ | |
printf(" v%s", version.GetString()); | |
} | |
} | |
} | |
} | |
} | |
printf("\n"); | |
} | |
if (!SaveStatus(directory, status)) | |
{ | |
printf(Error_SaveStatusFailed); | |
PrintFileError(GetLastError()); | |
printf("\n"); | |
} | |
return total; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment