ZaStoGram_desktop/Telegram/SourceFiles/tests/test_updater_win.cpp

232 lines
6.6 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "_other/updater.h"
#include <sddl.h>
#include <iostream>
#include <limits>
#include <memory>
namespace {
using Handle = std::unique_ptr<void, decltype(&CloseHandle)>;
auto Failures = 0;
void Check(bool condition, const char *name) {
if (!condition) {
++Failures;
std::cerr << "FAILED: " << name << std::endl;
}
}
ULONGLONG CreationTime(HANDLE process) {
auto created = FILETIME();
auto exited = FILETIME();
auto kernel = FILETIME();
auto user = FILETIME();
const auto result = GetProcessTimes(
process,
&created,
&exited,
&kernel,
&user);
Check(result != FALSE, "read test process identity");
return (ULONGLONG(created.dwHighDateTime) << 32) | created.dwLowDateTime;
}
void TestArguments() {
auto id = DWORD(0);
auto created = ULONGLONG(0);
Check(
ParseUpdateProcess(L"1234", L"134000000000000000", id, created),
"accept launcher identity");
Check(
id == 1234 && created == 134000000000000000ULL,
"preserve full creation time");
Check(
ParseUpdateProcess(L"4294967295", L"18446744073709551615", id, created),
"accept representable upper bounds");
Check(
id == std::numeric_limits<DWORD>::max()
&& created == std::numeric_limits<ULONGLONG>::max(),
"preserve upper bounds");
const auto invalid = {
L"", L"0", L"-1", L"+1", L" 1", L"1 ", L"1x", L"1.0",
L"18446744073709551616", L"999999999999999999999999999999",
};
for (const auto value : invalid) {
id = 17;
created = 19;
Check(
!ParseUpdateProcess(value, L"1", id, created),
"reject malformed process id");
Check(
!ParseUpdateProcess(L"1", value, id, created),
"reject malformed creation time");
Check(id == 17 && created == 19, "failed parse preserves outputs");
}
Check(
!ParseUpdateProcess(L"4294967296", L"1", id, created),
"reject process id overflow");
}
void TestErrors() {
const auto message = wstring(8192, L'x') + L"\u4e2d\u6587";
const auto text = FormatUpdateError(message, ERROR_SHARING_VIOLATION);
Check(text.starts_with(message), "preserve long Unicode error context");
Check(
text.find(L"Error code: 32") != wstring::npos,
"report original numeric error");
const auto unknown = FormatUpdateError(L"test", 0xE0000001);
Check(
unknown.find(L"3758096385") != wstring::npos,
"format unsigned error code");
Check(
unknown.find(L"(Unknown error)") != wstring::npos,
"handle unavailable system error description");
}
void TestProcess(const wchar_t *executable, bool force, bool denyTermination) {
const auto eventName = L"Local\\TelegramUpdaterTest-"
+ std::to_wstring(GetCurrentProcessId())
+ L"-" + std::to_wstring(GetTickCount64());
const auto event = Handle(
CreateEvent(nullptr, TRUE, FALSE, eventName.c_str()),
&CloseHandle);
Check(event != nullptr, "create test exit event");
if (!event) {
return;
}
auto command = L"\"" + wstring(executable)
+ L"\" --child " + eventName;
auto startup = STARTUPINFO();
startup.cb = sizeof(startup);
auto information = PROCESS_INFORMATION();
const auto started = CreateProcess(
executable,
command.data(),
nullptr,
nullptr,
FALSE,
CREATE_NO_WINDOW,
nullptr,
nullptr,
&startup,
&information);
Check(started != FALSE, "start owned test child");
if (!started) {
return;
}
auto process = Handle(information.hProcess, &CloseHandle);
auto thread = Handle(information.hThread, &CloseHandle);
const auto created = CreationTime(process.get());
Check(
FinishUpdateProcess(information.dwProcessId, created + 1)
== ERROR_SUCCESS,
"ignore reused process id");
Check(
WaitForSingleObject(process.get(), 0) == WAIT_TIMEOUT,
"identity mismatch leaves process running");
if (denyTermination) {
auto descriptor = PSECURITY_DESCRIPTOR(nullptr);
const auto parsed = ConvertStringSecurityDescriptorToSecurityDescriptor(
L"D:(D;;0x00000001;;;WD)(A;;0x001FFFFF;;;WD)",
SDDL_REVISION_1,
&descriptor,
nullptr);
const auto guard = std::unique_ptr<void, decltype(&LocalFree)>(
descriptor,
&LocalFree);
Check(parsed != FALSE, "create test process permissions");
if (parsed) {
Check(
SetKernelObjectSecurity(
process.get(),
DACL_SECURITY_INFORMATION,
descriptor) != FALSE,
"deny termination of test child");
}
}
if (!force) {
Check(SetEvent(event.get()) != FALSE, "request normal test exit");
}
const auto startedAt = GetTickCount64();
const auto result = FinishUpdateProcess(information.dwProcessId, created);
if (force) {
Check(
GetTickCount64() - startedAt >= 4900,
"allow normal exit grace period");
}
if (force && denyTermination) {
Check(result == ERROR_ACCESS_DENIED, "report denied termination");
Check(
WaitForSingleObject(process.get(), 0) == WAIT_TIMEOUT,
"denied termination leaves process running");
} else {
Check(result == ERROR_SUCCESS, "finish matching process");
Check(
WaitForSingleObject(process.get(), 0) == WAIT_OBJECT_0,
"success guarantees process exited");
auto code = DWORD(0);
Check(
GetExitCodeProcess(process.get(), &code) != FALSE,
"read test exit code");
Check(
code == (force ? ERROR_PROCESS_ABORTED : 37),
"use forced exit only after timeout");
Check(
FinishUpdateProcess(information.dwProcessId, created)
== ERROR_SUCCESS,
"accept already exited process");
}
Check(SetEvent(event.get()) != FALSE, "release test child");
Check(
WaitForSingleObject(process.get(), 35000) == WAIT_OBJECT_0,
"test child cleanup");
if (!denyTermination) {
thread.reset();
process.reset();
Check(
FinishUpdateProcess(information.dwProcessId, created)
== ERROR_SUCCESS,
"accept absent process");
}
}
} // namespace
int wmain(int argc, wchar_t *argv[]) {
if (argc == 3 && wstring(argv[1]) == L"--child") {
const auto event = Handle(
OpenEvent(SYNCHRONIZE, FALSE, argv[2]),
&CloseHandle);
if (!event || WaitForSingleObject(event.get(), 30000) != WAIT_OBJECT_0) {
return 38;
}
Sleep(250);
return 37;
}
TestArguments();
TestErrors();
Check(
FinishUpdateProcess(0, 1) == ERROR_INVALID_PARAMETER,
"reject zero process id");
Check(
FinishUpdateProcess(
GetCurrentProcessId(),
CreationTime(GetCurrentProcess())) == ERROR_INVALID_PARAMETER,
"refuse to terminate updater itself");
TestProcess(argv[0], false, false);
TestProcess(argv[0], true, false);
TestProcess(argv[0], false, true);
TestProcess(argv[0], true, true);
std::cout << "Updater tests: " << Failures << " failures." << std::endl;
return Failures ? 1 : 0;
}