/*
Copyright (C) 2009 Red Hat, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include "vdcommon.h"
#include "virtio_vdi_port.h"
#include "pci_vdi_port.h"
//#define DEBUG_VDSERVICE
#define VD_SERVICE_DISPLAY_NAME TEXT("RHEV Spice Agent")
#define VD_SERVICE_NAME TEXT("vdservice")
#define VD_SERVICE_DESCRIPTION TEXT("Enables Spice event injection and display configuration.")
#define VD_SERVICE_LOG_PATH TEXT("%svdservice.log")
#define VD_SERVICE_LOAD_ORDER_GROUP TEXT("Pointer Port")
#define VD_AGENT_PATH TEXT("%s\\vdagent.exe")
#define VD_AGENT_TIMEOUT 10000
#define VD_AGENT_MAX_RESTARTS 10
#define VD_AGENT_RESTART_INTERVAL 3000
#define VD_AGENT_RESTART_COUNT_RESET_INTERVAL 60000
#define WINLOGON_FILENAME TEXT("winlogon.exe")
#define CREATE_PROC_MAX_RETRIES 10
#define CREATE_PROC_INTERVAL_MS 500
// This enum simplifies WaitForMultipleEvents for static
// events, that is handles that are guranteed non NULL.
// It doesn't include:
// VirtioVDIPort Handles - these are filled by an interface because
// of variable handle number.
// VDAgent handle - this can be 1 or 0 (NULL or not), so it is also added at
// the end of VDService::_events
enum {
VD_EVENT_PIPE_READ = 0,
VD_EVENT_PIPE_WRITE,
VD_EVENT_CONTROL,
VD_STATIC_EVENTS_COUNT // Must be last
};
enum {
VD_CONTROL_IDLE = 0,
VD_CONTROL_STOP,
VD_CONTROL_LOGON,
VD_CONTROL_RESTART_AGENT,
};
typedef std::queue VDControlQueue;
class VDService {
public:
static VDService* get();
~VDService();
bool run();
bool install();
bool uninstall();
private:
VDService();
bool execute();
void stop();
static DWORD WINAPI control_handler(DWORD control, DWORD event_type,
LPVOID event_data, LPVOID context);
static VOID WINAPI main(DWORD argc, TCHAR * argv[]);
bool init_vdi_port();
void set_control_event(int control_command);
void handle_control_event();
void pipe_write_completion();
void pipe_read_completion();
void write_agent_control(uint32_t type, uint32_t opaque);
void read_pipe();
void handle_pipe_data(DWORD bytes);
void handle_port_data();
bool handle_agent_control(VDPipeMessage* msg);
bool restart_agent(bool normal_restart);
bool launch_agent();
bool kill_agent();
unsigned fill_agent_event() {
ASSERT(_events);
if (_agent_proc_info.hProcess) {
_events[_events_count - 1] = _agent_proc_info.hProcess;
return _events_count;
} else {
return _events_count - 1;
}
}
private:
static VDService* _singleton;
SERVICE_STATUS _status;
SERVICE_STATUS_HANDLE _status_handle;
PROCESS_INFORMATION _agent_proc_info;
HANDLE _control_event;
HANDLE* _events;
TCHAR _agent_path[MAX_PATH];
VDIPort* _vdi_port;
VDPipeState _pipe_state;
VDControlQueue _control_queue;
mutex_t _control_mutex;
mutex_t _agent_mutex;
uint32_t _connection_id;
DWORD _session_id;
DWORD _chunk_port;
DWORD _chunk_size;
DWORD _last_agent_restart_time;
int _agent_restarts;
int _system_version;
bool _pipe_connected;
bool _pending_reset;
bool _pending_write;
bool _pending_read;
bool _agent_alive;
bool _running;
VDLog* _log;
unsigned _events_count;
unsigned _events_vdi_port_base;
};
VDService* VDService::_singleton = NULL;
VDService* VDService::get()
{
if (!_singleton) {
_singleton = new VDService();
}
return (VDService*)_singleton;
}
enum SystemVersion {
SYS_VER_UNSUPPORTED,
SYS_VER_WIN_XP_CLASS, // also Server 2003/R2
SYS_VER_WIN_7_CLASS, // also Server 2008/R2 & Vista
};
int supported_system_version()
{
OSVERSIONINFOEX osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
if (!GetVersionEx((OSVERSIONINFO*)&osvi)) {
vd_printf("GetVersionEx() failed: %u", GetLastError());
return 0;
}
if (osvi.dwMajorVersion == 5 && (osvi.dwMinorVersion == 1 || osvi.dwMinorVersion == 2)) {
return SYS_VER_WIN_XP_CLASS;
} else if (osvi.dwMajorVersion == 6 && (osvi.dwMinorVersion == 0 || osvi.dwMinorVersion == 1)) {
return SYS_VER_WIN_7_CLASS;
}
return 0;
}
VDService::VDService()
: _status_handle (0)
, _events (NULL)
, _vdi_port (NULL)
, _connection_id (0)
, _session_id (0)
, _chunk_port (0)
, _chunk_size (0)
, _last_agent_restart_time (0)
, _agent_restarts (0)
, _pipe_connected (false)
, _pending_reset (false)
, _pending_write (false)
, _pending_read (false)
, _agent_alive (false)
, _running (false)
, _log (NULL)
, _events_count(0)
, _events_vdi_port_base(0)
{
ZeroMemory(&_agent_proc_info, sizeof(_agent_proc_info));
ZeroMemory(&_pipe_state, sizeof(_pipe_state));
_system_version = supported_system_version();
_control_event = CreateEvent(NULL, FALSE, FALSE, NULL);
_pipe_state.write.overlap.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
_pipe_state.read.overlap.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
_agent_path[0] = wchar_t('\0');
MUTEX_INIT(_agent_mutex);
MUTEX_INIT(_control_mutex);
_singleton = this;
}
VDService::~VDService()
{
CloseHandle(_pipe_state.read.overlap.hEvent);
CloseHandle(_pipe_state.write.overlap.hEvent);
CloseHandle(_control_event);
delete _events;
delete _log;
}
bool VDService::run()
{
#ifndef DEBUG_VDSERVICE
SERVICE_TABLE_ENTRY service_table[] = {{VD_SERVICE_NAME, main}, {0, 0}};
return !!StartServiceCtrlDispatcher(service_table);
#else
main(0, NULL);
return true;
#endif
}
bool VDService::install()
{
bool ret = false;
SC_HANDLE service_control_manager = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE);
if (!service_control_manager) {
printf("OpenSCManager failed\n");
return false;
}
TCHAR path[_MAX_PATH + 1];
if (!GetModuleFileName(0, path, sizeof(path) / sizeof(path[0]))) {
printf("GetModuleFileName failed\n");
CloseServiceHandle(service_control_manager);
return false;
}
SC_HANDLE service = CreateService(service_control_manager, VD_SERVICE_NAME,
VD_SERVICE_DISPLAY_NAME, SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START,
SERVICE_ERROR_IGNORE, path, VD_SERVICE_LOAD_ORDER_GROUP,
0, 0, 0, 0);
if (service) {
SERVICE_DESCRIPTION descr;
descr.lpDescription = VD_SERVICE_DESCRIPTION;
if (!ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &descr)) {
printf("ChangeServiceConfig2 failed\n");
}
CloseServiceHandle(service);
printf("Service installed successfully\n");
ret = true;
} else if (GetLastError() == ERROR_SERVICE_EXISTS) {
printf("Service already exists\n");
ret = true;
} else {
printf("Service not installed successfully, error %d\n", GetLastError());
}
CloseServiceHandle(service_control_manager);
return ret;
}
bool VDService::uninstall()
{
bool ret = false;
SC_HANDLE service_control_manager = OpenSCManager(0, 0, SC_MANAGER_CONNECT);
if (!service_control_manager) {
printf("OpenSCManager failed\n");
return false;
}
SC_HANDLE service = OpenService(service_control_manager, VD_SERVICE_NAME,
SERVICE_QUERY_STATUS | DELETE);
if (!service) {
printf("OpenService failed\n");
CloseServiceHandle(service_control_manager);
return false;
}
SERVICE_STATUS status;
if (!QueryServiceStatus(service, &status)) {
printf("QueryServiceStatus failed\n");
} else if (status.dwCurrentState != SERVICE_STOPPED) {
printf("Service is still running\n");
} else if (DeleteService(service)) {
printf("Service removed successfully\n");
ret = true;
} else {
switch (GetLastError()) {
case ERROR_ACCESS_DENIED:
printf("Access denied while trying to remove service\n");
break;
case ERROR_INVALID_HANDLE:
printf("Handle invalid while trying to remove service\n");
break;
case ERROR_SERVICE_MARKED_FOR_DELETE:
printf("Service already marked for deletion\n");
break;
}
}
CloseServiceHandle(service);
CloseServiceHandle(service_control_manager);
return ret;
}
const char* session_events[] = {
"INVALID", "CONNECT", "DISCONNECT", "REMOTE_CONNECT", "REMOTE_DISCONNECT", "LOGON", "LOGOFF",
"LOCK", "UNLOCK", "REMOTE_CONTROL"
};
void VDService::set_control_event(int control_command)
{
MUTEX_LOCK(_control_mutex);
_control_queue.push(control_command);
if (_control_event && !SetEvent(_control_event)) {
vd_printf("SetEvent() failed: %u", GetLastError());
}
MUTEX_UNLOCK(_control_mutex);
}
void VDService::handle_control_event()
{
MUTEX_LOCK(_control_mutex);
while (_control_queue.size()) {
int control_command = _control_queue.front();
_control_queue.pop();
vd_printf("Control command %d", control_command);
switch (control_command) {
case VD_CONTROL_STOP:
_running = false;
break;
case VD_CONTROL_LOGON:
write_agent_control(VD_AGENT_SESSION_LOGON, 0);
break;
case VD_CONTROL_RESTART_AGENT:
_running = restart_agent(true);
break;
default:
vd_printf("Unsupported control command %u", control_command);
}
}
MUTEX_UNLOCK(_control_mutex);
}
DWORD WINAPI VDService::control_handler(DWORD control, DWORD event_type, LPVOID event_data,
LPVOID context)
{
VDService* s = _singleton;
DWORD ret = NO_ERROR;
ASSERT(s);
switch (control) {
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
vd_printf("Stop service");
s->_status.dwCurrentState = SERVICE_STOP_PENDING;
SetServiceStatus(s->_status_handle, &s->_status);
s->stop();
break;
case SERVICE_CONTROL_INTERROGATE:
vd_printf("Interrogate service");
SetServiceStatus(s->_status_handle, &s->_status);
break;
case SERVICE_CONTROL_SESSIONCHANGE: {
DWORD session_id = ((WTSSESSION_NOTIFICATION*)event_data)->dwSessionId;
vd_printf("Session %u %s", session_id, session_events[event_type]);
SetServiceStatus(s->_status_handle, &s->_status);
if (s->_system_version != SYS_VER_UNSUPPORTED) {
if (event_type == WTS_CONSOLE_CONNECT) {
s->_session_id = session_id;
s->set_control_event(VD_CONTROL_RESTART_AGENT);
} else if (event_type == WTS_SESSION_LOGON) {
s->set_control_event(VD_CONTROL_LOGON);
}
}
break;
}
default:
vd_printf("Unsupported control %u", control);
ret = ERROR_CALL_NOT_IMPLEMENTED;
}
return ret;
}
#define VDSERVICE_ACCEPTED_CONTROLS \
(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_SESSIONCHANGE)
VOID WINAPI VDService::main(DWORD argc, TCHAR* argv[])
{
VDService* s = _singleton;
SERVICE_STATUS* status;
TCHAR log_path[MAX_PATH];
TCHAR full_path[MAX_PATH];
TCHAR temp_path[MAX_PATH];
TCHAR* slash;
ASSERT(s);
if (GetModuleFileName(NULL, full_path, MAX_PATH) && (slash = wcsrchr(full_path, TCHAR('\\'))) &&
GetTempPath(MAX_PATH, temp_path)) {
*slash = TCHAR('\0');
swprintf_s(s->_agent_path, MAX_PATH, VD_AGENT_PATH, full_path);
swprintf_s(log_path, MAX_PATH, VD_SERVICE_LOG_PATH, temp_path);
s->_log = VDLog::get(log_path);
}
vd_printf("***Service started***");
log_version();
if (!SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS)) {
vd_printf("SetPriorityClass failed %u", GetLastError());
}
status = &s->_status;
status->dwServiceType = SERVICE_WIN32;
status->dwCurrentState = SERVICE_STOPPED;
status->dwControlsAccepted = 0;
status->dwWin32ExitCode = NO_ERROR;
status->dwServiceSpecificExitCode = NO_ERROR;
status->dwCheckPoint = 0;
status->dwWaitHint = 0;
s->_status_handle = RegisterServiceCtrlHandlerEx(VD_SERVICE_NAME, &VDService::control_handler,
NULL);
if (!s->_status_handle) {
printf("RegisterServiceCtrlHandler failed\n");
#ifndef DEBUG_VDSERVICE
return;
#endif // DEBUG_VDSERVICE
}
// service is starting
status->dwCurrentState = SERVICE_START_PENDING;
SetServiceStatus(s->_status_handle, status);
// service running
status->dwControlsAccepted |= VDSERVICE_ACCEPTED_CONTROLS;
status->dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(s->_status_handle, status);
s->_running = true;
s->execute();
// service was stopped
status->dwCurrentState = SERVICE_STOP_PENDING;
SetServiceStatus(s->_status_handle, status);
// service is stopped
status->dwControlsAccepted &= ~VDSERVICE_ACCEPTED_CONTROLS;
status->dwCurrentState = SERVICE_STOPPED;
#ifndef DEBUG_VDSERVICE
SetServiceStatus(s->_status_handle, status);
#endif //DEBUG_VDSERVICE
}
VDIPort *create_virtio_vdi_port()
{
return new VirtioVDIPort();
}
VDIPort *create_pci_vdi_port()
{
return new PCIVDIPort();
}
bool VDService::init_vdi_port()
{
VDIPort* (*creators[])(void) = { create_virtio_vdi_port, create_pci_vdi_port };
for (int i = 0 ; i < sizeof(creators)/sizeof(creators[0]); ++i) {
_vdi_port = creators[i]();
if (_vdi_port->init()) {
return true;
}
delete _vdi_port;
}
return false;
}
bool VDService::execute()
{
SECURITY_ATTRIBUTES sec_attr;
SECURITY_DESCRIPTOR* sec_desr;
HANDLE pipe;
sec_desr = (SECURITY_DESCRIPTOR*)LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
InitializeSecurityDescriptor(sec_desr, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(sec_desr, TRUE, (PACL)NULL, FALSE);
sec_attr.nLength = sizeof(sec_attr);
sec_attr.bInheritHandle = TRUE;
sec_attr.lpSecurityDescriptor = sec_desr;
pipe = CreateNamedPipe(VD_SERVICE_PIPE_NAME, PIPE_ACCESS_DUPLEX |
FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, BUF_SIZE, BUF_SIZE,
VD_AGENT_TIMEOUT, &sec_attr);
LocalFree(sec_desr);
if (pipe == INVALID_HANDLE_VALUE) {
vd_printf("CreatePipe() failed: %u", GetLastError());
return false;
}
_pipe_state.pipe = pipe;
_session_id = WTSGetActiveConsoleSessionId();
if (_session_id == 0xFFFFFFFF) {
vd_printf("WTSGetActiveConsoleSessionId() failed");
CloseHandle(pipe);
return false;
}
if (!launch_agent()) {
CloseHandle(pipe);
return false;
}
if (!init_vdi_port()) {
vd_printf("Failed to create VDIPort instance");
CloseHandle(pipe);
return false;
}
vd_printf("created %s", _vdi_port->name());
_events_count = VD_STATIC_EVENTS_COUNT + _vdi_port->get_num_events() + 1 /*for agent*/;
_events = new HANDLE[_events_count];
_events_vdi_port_base = VD_STATIC_EVENTS_COUNT;
ZeroMemory(_events, _events_count);
vd_printf("Connected to server");
_events[VD_EVENT_PIPE_READ] = _pipe_state.read.overlap.hEvent;
_events[VD_EVENT_PIPE_WRITE] = _pipe_state.write.overlap.hEvent;
_events[VD_EVENT_CONTROL] = _control_event;
_vdi_port->fill_events(&_events[_events_vdi_port_base]);
_chunk_size = _chunk_port = 0;
read_pipe();
while (_running) {
int cont_read = _vdi_port->read();
int cont_write = _vdi_port->write();
bool cont = false;
if (cont_read >= 0 && cont_write >= 0) {
cont = cont_read || cont_write;
} else if (cont_read == VDI_PORT_ERROR || cont_write == VDI_PORT_ERROR) {
vd_printf("VDI Port error, read %d write %d", cont_read, cont_write);
_running = false;
} else if (cont_read == VDI_PORT_RESET || cont_write == VDI_PORT_RESET) {
vd_printf("VDI Port reset, read %d write %d", cont_read, cont_write);
_chunk_size = _chunk_port = 0;
write_agent_control(VD_AGENT_RESET, ++_connection_id);
_pending_reset = true;
}
if (cont) {
handle_port_data();
}
if (cont_write) {
handle_pipe_data(0);
}
if (_running && (!cont || _pending_read || _pending_write)) {
unsigned actual_events = fill_agent_event();
DWORD wait_ret = WaitForMultipleObjects(actual_events, _events, FALSE,
cont ? 0 : INFINITE);
switch (wait_ret) {
case WAIT_OBJECT_0 + VD_EVENT_PIPE_READ:
pipe_read_completion();
break;
case WAIT_OBJECT_0 + VD_EVENT_PIPE_WRITE:
pipe_write_completion();
break;
case WAIT_OBJECT_0 + VD_EVENT_CONTROL:
handle_control_event();
break;
case WAIT_TIMEOUT:
break;
default:
if (wait_ret == WAIT_OBJECT_0 + _events_count - 1) {
vd_printf("Agent killed");
if (_system_version == SYS_VER_WIN_XP_CLASS) {
restart_agent(false);
} else if (_system_version == SYS_VER_WIN_7_CLASS) {
kill_agent();
}
} else {
if (wait_ret >= WAIT_OBJECT_0 + _events_vdi_port_base &&
wait_ret < WAIT_OBJECT_0 +
_events_vdi_port_base + _vdi_port->get_num_events()) {
_vdi_port->handle_event(wait_ret - VD_STATIC_EVENTS_COUNT - WAIT_OBJECT_0);
} else {
vd_printf("WaitForMultipleObjects failed %u", GetLastError());
}
}
}
}
}
delete _vdi_port;
CloseHandle(pipe);
return true;
}
DWORD64 marshall_string(LPCWSTR str, DWORD max_size, LPBYTE* next_buf, DWORD* used_bytes)
{
DWORD offset = *used_bytes;
if (!str) {
return 0;
}
DWORD len = (DWORD)(wcslen(str) + 1) * sizeof(WCHAR);
if (*used_bytes + len > max_size) {
return 0;
}
memmove(*next_buf, str, len);
*used_bytes += len;
*next_buf += len;
return offset;
}
typedef struct CreateProcessParams {
DWORD size;
DWORD process_id;
BOOL use_default_token;
HANDLE token;
LPWSTR application_name;
LPWSTR command_line;
SECURITY_ATTRIBUTES process_attributes;
SECURITY_ATTRIBUTES thread_attributes;
BOOL inherit_handles;
DWORD creation_flags;
LPVOID environment;
LPWSTR current_directory;
STARTUPINFOW startup_info;
PROCESS_INFORMATION process_information;
BYTE data[0x2000];
} CreateProcessParams;
typedef struct CreateProcessRet {
DWORD size;
BOOL ret_value;
DWORD last_error;
PROCESS_INFORMATION process_information;
} CreateProcessRet;
BOOL create_session_process_as_user(IN DWORD session_id, IN BOOL use_default_token, IN HANDLE token,
IN LPCWSTR application_name, IN LPWSTR command_line,
IN LPSECURITY_ATTRIBUTES process_attributes,
IN LPSECURITY_ATTRIBUTES thread_attributes,
IN BOOL inherit_handles, IN DWORD creation_flags,
IN LPVOID environment, IN LPCWSTR current_directory,
IN LPSTARTUPINFOW startup_info,
OUT LPPROCESS_INFORMATION process_information)
{
WCHAR win_sta_path[MAX_PATH];
HINSTANCE win_sta_handle;
WCHAR pipe_name[MAX_PATH] = L"";
DWORD pipe_name_len;
BOOL got_pipe_name = FALSE;
HANDLE named_pipe;
CreateProcessRet proc_ret;
CreateProcessParams proc_params;
LPBYTE buffer = (LPBYTE)proc_params.data;
DWORD max_size = sizeof(proc_params);
DWORD bytes_used = offsetof(CreateProcessParams, data);
DWORD bytes_written;
DWORD bytes_read;
DWORD env_len = 0;
BOOL ret = FALSE;
GetSystemDirectoryW(win_sta_path, MAX_PATH);
lstrcatW(win_sta_path, L"\\winsta.dll");
win_sta_handle = LoadLibrary(win_sta_path);
if (win_sta_handle) {
PWINSTATIONQUERYINFORMATIONW win_sta_query_func =
(PWINSTATIONQUERYINFORMATIONW)GetProcAddress(win_sta_handle,
"WinStationQueryInformationW");
if (win_sta_query_func) {
got_pipe_name = win_sta_query_func(0, session_id, (WINSTATIONINFOCLASS)0x21,
pipe_name, sizeof(pipe_name), &pipe_name_len);
}
FreeLibrary(win_sta_handle);
}
if (!got_pipe_name || pipe_name[0] == '\0') {
swprintf_s(pipe_name, MAX_PATH, L"\\\\.\\Pipe\\TerminalServer\\SystemExecSrvr\\%d",
session_id);
}
do {
named_pipe = CreateFile(pipe_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
0, 0);
if (named_pipe == INVALID_HANDLE_VALUE) {
if (GetLastError() == ERROR_PIPE_BUSY) {
if (!WaitNamedPipe(pipe_name, 3000)) {
return FALSE;
}
} else {
return FALSE;
}
}
} while (named_pipe == INVALID_HANDLE_VALUE);
memset(&proc_params, 0, sizeof(proc_params));
proc_params.process_id = GetCurrentProcessId();
proc_params.use_default_token = use_default_token;
proc_params.token = token;
proc_params.application_name = (LPWSTR)marshall_string(application_name, max_size, &buffer,
&bytes_used);
proc_params.command_line = (LPWSTR)marshall_string(command_line, max_size, &buffer,
&bytes_used);
if (process_attributes) {
proc_params.process_attributes = *process_attributes;
}
if (thread_attributes) {
proc_params.thread_attributes = *thread_attributes;
}
proc_params.inherit_handles = inherit_handles;
proc_params.creation_flags = creation_flags;
proc_params.current_directory = (LPWSTR)marshall_string(current_directory, max_size,
&buffer, &bytes_used);
if (startup_info) {
proc_params.startup_info = *startup_info;
proc_params.startup_info.lpDesktop = (LPWSTR)marshall_string(startup_info->lpDesktop,
max_size, &buffer,
&bytes_used);
proc_params.startup_info.lpTitle = (LPWSTR)marshall_string(startup_info->lpTitle,
max_size, &buffer, &bytes_used);
}
if (environment) {
if (creation_flags & CREATE_UNICODE_ENVIRONMENT) {
while ((env_len + bytes_used <= max_size)) {
if (((LPWSTR)environment)[env_len / 2] == '\0' &&
((LPWSTR)environment)[env_len / 2 + 1] == '\0') {
env_len += 2 * sizeof(WCHAR);
break;
}
env_len += sizeof(WCHAR);
}
} else {
while (env_len + bytes_used <= max_size) {
if (((LPSTR)environment)[env_len] == '\0' &&
((LPSTR)environment)[env_len + 1] == '\0') {
env_len += 2;
break;
}
env_len++;
}
}
if (env_len + bytes_used <= max_size) {
memmove(buffer, environment, env_len);
proc_params.environment = (LPVOID)(UINT64)bytes_used;
buffer += env_len;
bytes_used += env_len;
} else {
proc_params.environment = NULL;
}
} else {
proc_params.environment = NULL;
}
proc_params.size = bytes_used;
if (WriteFile(named_pipe, &proc_params, proc_params.size, &bytes_written, NULL) &&
ReadFile(named_pipe, &proc_ret, sizeof(proc_ret), &bytes_read, NULL)) {
ret = proc_ret.ret_value;
if (ret) {
*process_information = proc_ret.process_information;
} else {
SetLastError(proc_ret.last_error);
}
} else {
ret = FALSE;
}
CloseHandle(named_pipe);
return ret;
}
BOOL create_process_as_user(IN DWORD session_id, IN LPCWSTR application_name,
IN LPWSTR command_line, IN LPSECURITY_ATTRIBUTES process_attributes,
IN LPSECURITY_ATTRIBUTES thread_attributes, IN BOOL inherit_handles,
IN DWORD creation_flags, IN LPVOID environment,
IN LPCWSTR current_directory, IN LPSTARTUPINFOW startup_info,
OUT LPPROCESS_INFORMATION process_information)
{
PROCESSENTRY32 proc_entry;
DWORD winlogon_pid = 0;
HANDLE winlogon_proc;
HANDLE token = NULL;
HANDLE token_dup;
BOOL ret = FALSE;
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap == INVALID_HANDLE_VALUE) {
vd_printf("CreateToolhelp32Snapshot() failed %u", GetLastError());
return false;
}
ZeroMemory(&proc_entry, sizeof(proc_entry));
proc_entry.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(snap, &proc_entry)) {
vd_printf("Process32First() failed %u", GetLastError());
CloseHandle(snap);
return false;
}
do {
if (_tcsicmp(proc_entry.szExeFile, WINLOGON_FILENAME) == 0) {
DWORD winlogon_session_id = 0;
if (ProcessIdToSessionId(proc_entry.th32ProcessID, &winlogon_session_id) &&
winlogon_session_id == session_id) {
winlogon_pid = proc_entry.th32ProcessID;
break;
}
}
} while (Process32Next(snap, &proc_entry));
CloseHandle(snap);
if (winlogon_pid == 0) {
vd_printf("Winlogon not found");
return false;
}
winlogon_proc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, winlogon_pid);
if (!winlogon_proc) {
vd_printf("OpenProcess() failed %u", GetLastError());
return false;
}
ret = OpenProcessToken(winlogon_proc, TOKEN_DUPLICATE, &token);
CloseHandle(winlogon_proc);
if (!ret) {
vd_printf("OpenProcessToken() failed %u", GetLastError());
return false;
}
ret = DuplicateTokenEx(token, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary,
&token_dup);
CloseHandle(token);
if (!ret) {
vd_printf("DuplicateTokenEx() failed %u", GetLastError());
return false;
}
ret = CreateProcessAsUser(token_dup, application_name, command_line, process_attributes,
thread_attributes, inherit_handles, creation_flags, environment,
current_directory, startup_info, process_information);
CloseHandle(token_dup);
return ret;
}
bool VDService::launch_agent()
{
STARTUPINFO startup_info;
OVERLAPPED overlap;
BOOL ret = FALSE;
ZeroMemory(&startup_info, sizeof(startup_info));
startup_info.cb = sizeof(startup_info);
startup_info.lpDesktop = TEXT("Winsta0\\winlogon");
ZeroMemory(&_agent_proc_info, sizeof(_agent_proc_info));
if (_system_version == SYS_VER_WIN_XP_CLASS) {
if (_session_id == 0) {
ret = CreateProcess(_agent_path, _agent_path, NULL, NULL, FALSE, 0, NULL, NULL,
&startup_info, &_agent_proc_info);
} else {
for (int i = 0; i < CREATE_PROC_MAX_RETRIES; i++) {
ret = create_session_process_as_user(_session_id, TRUE, NULL, NULL, _agent_path,
NULL, NULL, FALSE, 0, NULL, NULL,
&startup_info, &_agent_proc_info);
if (ret) {
vd_printf("create_session_process_as_user #%d", i);
break;
}
Sleep(CREATE_PROC_INTERVAL_MS);
}
}
} else if (_system_version == SYS_VER_WIN_7_CLASS) {
startup_info.lpDesktop = TEXT("Winsta0\\default");
ret = create_process_as_user(_session_id, _agent_path, _agent_path, NULL, NULL, FALSE, 0,
NULL, NULL, &startup_info, &_agent_proc_info);
} else {
vd_printf("Not supported in this system version");
return false;
}
if (!ret) {
vd_printf("CreateProcess() failed: %u", GetLastError());
return false;
}
_agent_alive = true;
if (_pipe_connected) {
vd_printf("Pipe already connected");
return false;
}
vd_printf("Wait for vdagent to connect");
ZeroMemory(&overlap, sizeof(overlap));
overlap.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
DWORD err = (ConnectNamedPipe(_pipe_state.pipe, &overlap) ? 0 : GetLastError());
if (err = ERROR_IO_PENDING) {
HANDLE wait_handles[2] = {overlap.hEvent, _agent_proc_info.hProcess};
DWORD wait_ret = WaitForMultipleObjects(2, wait_handles, FALSE, VD_AGENT_TIMEOUT);
if (wait_ret != WAIT_OBJECT_0) {
_agent_proc_info.hProcess = 0;
vd_printf("Failed waiting for vdagent connection: %u error: %u", wait_ret,
wait_ret == WAIT_FAILED ? GetLastError() : 0);
ret = FALSE;
}
} else if (err != 0 && err != ERROR_PIPE_CONNECTED) {
vd_printf("ConnectNamedPipe() failed: %u", err);
ret = FALSE;
}
if (ret) {
vd_printf("Pipe connected by vdagent");
_pipe_connected = true;
_pending_reset = false;
}
CloseHandle(overlap.hEvent);
return !!ret;
}
bool VDService::kill_agent()
{
DWORD exit_code = 0;
DWORD wait_ret;
HANDLE proc_handle;
bool ret = true;
if (!_agent_alive) {
return true;
}
_agent_alive = false;
proc_handle = _agent_proc_info.hProcess;
_agent_proc_info.hProcess = 0;
if (_pipe_connected) {
_pipe_connected = false;
DisconnectNamedPipe(_pipe_state.pipe);
}
if (GetProcessId(proc_handle)) {
wait_ret = WaitForSingleObject(proc_handle, VD_AGENT_TIMEOUT);
switch (wait_ret) {
case WAIT_OBJECT_0:
if (GetExitCodeProcess(proc_handle, &exit_code)) {
vd_printf("vdagent exit code %u", exit_code);
ret = (exit_code != STILL_ACTIVE);
} else {
vd_printf("GetExitCodeProcess() failed: %u", GetLastError());
}
break;
case WAIT_TIMEOUT:
vd_printf("Wait timeout");
ret = false;
break;
case WAIT_FAILED:
default:
vd_printf("WaitForSingleObject() failed: %u", GetLastError());
break;
}
}
CloseHandle(proc_handle);
CloseHandle(_agent_proc_info.hThread);
ZeroMemory(&_agent_proc_info, sizeof(_agent_proc_info));
return ret;
}
bool VDService::restart_agent(bool normal_restart)
{
DWORD time = GetTickCount();
bool ret = true;
MUTEX_LOCK(_agent_mutex);
if (!normal_restart && ++_agent_restarts > VD_AGENT_MAX_RESTARTS) {
vd_printf("Agent restarted too many times");
ret = false;
stop();
}
if (ret && kill_agent() && launch_agent()) {
if (time - _last_agent_restart_time > VD_AGENT_RESTART_COUNT_RESET_INTERVAL) {
_agent_restarts = 0;
}
_last_agent_restart_time = time;
ret = true;
read_pipe();
}
MUTEX_UNLOCK(_agent_mutex);
return ret;
}
void VDService::stop()
{
vd_printf("Service stopped");
set_control_event(VD_CONTROL_STOP);
}
void VDService::pipe_write_completion()
{
VDPipeState* ps = &this->_pipe_state;
DWORD bytes;
if (!_running) {
return;
}
if (_pending_write) {
if (GetOverlappedResult(_pipe_state.pipe, &_pipe_state.write.overlap, &bytes, FALSE)) {
ps->write.start += bytes;
if (ps->write.start == ps->write.end) {
ps->write.start = ps->write.end = 0;
}
} else if (GetLastError() == ERROR_IO_PENDING){
vd_printf("Overlapped write is pending");
return;
} else {
vd_printf("GetOverlappedResult() failed : %d", GetLastError());
}
_pending_write = false;
}
if (ps->write.start < ps->write.end) {
_pending_write = true;
if (!WriteFile(ps->pipe, ps->write.data + ps->write.start,
ps->write.end - ps->write.start, NULL, &_pipe_state.write.overlap)) {
vd_printf("vdagent disconnected (%u)", GetLastError());
_pending_write = false;
_pipe_connected = false;
DisconnectNamedPipe(_pipe_state.pipe);
}
} else {
_pending_write = false;
}
}
void VDService::pipe_read_completion()
{
DWORD bytes = 0;
DWORD err = ERROR_SUCCESS;
if (!_pipe_connected || !_pending_read) {
return;
}
_pending_read = false;
if (!GetOverlappedResult(_pipe_state.pipe, &_pipe_state.read.overlap, &bytes, FALSE)) {
err = GetLastError();
}
switch (err) {
case ERROR_SUCCESS:
case ERROR_MORE_DATA:
handle_pipe_data(bytes);
read_pipe();
break;
case ERROR_IO_INCOMPLETE:
break;
default:
vd_printf("vdagent disconnected (%u)", err);
_pipe_connected = false;
DisconnectNamedPipe(_pipe_state.pipe);
}
}
void VDService::read_pipe()
{
VDPipeState* ps = &_pipe_state;
DWORD bytes;
if (ps->read.end < sizeof(ps->read.data)) {
_pending_read = true;
if (ReadFile(ps->pipe, ps->read.data + ps->read.end, sizeof(ps->read.data) - ps->read.end,
&bytes, &ps->read.overlap) || GetLastError() == ERROR_MORE_DATA) {
_pending_read = false;
handle_pipe_data(bytes);
read_pipe();
} else if (GetLastError() != ERROR_IO_PENDING) {
vd_printf("vdagent disconnected (%u)", GetLastError());
_pending_read = false;
_pipe_connected = false;
DisconnectNamedPipe(_pipe_state.pipe);
}
} else {
_pending_read = false;
}
}
//FIXME: division to max size chunks should be here, not in the agent
void VDService::handle_pipe_data(DWORD bytes)
{
VDPipeState* ps = &_pipe_state;
DWORD read_size;
if (bytes) {
_pending_read = false;
}
if (!_running) {
return;
}
ps->read.end += bytes;
while (_running && (read_size = ps->read.end - ps->read.start) >= sizeof(VDPipeMessage)) {
VDPipeMessage* pipe_msg = (VDPipeMessage*)&ps->read.data[ps->read.start];
if (pipe_msg->type != VD_AGENT_COMMAND) {
handle_agent_control(pipe_msg);
ps->read.start += sizeof(VDPipeMessage);
continue;
}
if (read_size < sizeof(VDPipeMessage) + pipe_msg->size) {
break;
}
if (_vdi_port->write_ring_free_space() < sizeof(VDIChunkHeader) + pipe_msg->size) {
//vd_printf("DEBUG: no space in write ring %u", _vdi_port->write_ring_free_space());
break;
}
if (!_pending_reset) {
VDIChunkHeader chunk;
chunk.port = pipe_msg->opaque;
chunk.size = pipe_msg->size;
if (_vdi_port->ring_write(&chunk, sizeof(chunk)) != sizeof(chunk) ||
_vdi_port->ring_write(pipe_msg->data, chunk.size) != chunk.size) {
vd_printf("ring_write failed");
_running = false;
return;
}
}
ps->read.start += (sizeof(VDPipeMessage) + pipe_msg->size);
}
if (ps->read.start == ps->read.end && !_pending_read) {
DWORD prev_read_end = ps->read.end;
ps->read.start = ps->read.end = 0;
if (prev_read_end == sizeof(ps->read.data)) {
read_pipe();
}
}
}
void VDService::handle_port_data()
{
VDPipeMessage* pipe_msg;
VDIChunkHeader chunk;
int chunks_count = 0;
DWORD count = 0;
while (_running) {
if (!_chunk_size && _vdi_port->read_ring_size() >= sizeof(chunk)) {
if (_vdi_port->ring_read(&chunk, sizeof(chunk)) != sizeof(chunk)) {
vd_printf("ring_read of chunk header failed");
_running = false;
break;
}
count = sizeof(VDPipeMessage) + chunk.size;
if (_pipe_state.write.end + count > sizeof(_pipe_state.write.data)) {
vd_printf("chunk is too large, size %u port %u", chunk.size, chunk.port);
_running = false;
break;
}
_chunk_size = chunk.size;
_chunk_port = chunk.port;
}
if (_chunk_size && _vdi_port->read_ring_size() >= _chunk_size) {
count = sizeof(VDPipeMessage) + _chunk_size;
ASSERT(_pipe_state.write.end + count <= sizeof(_pipe_state.write.data));
pipe_msg = (VDPipeMessage*)&_pipe_state.write.data[_pipe_state.write.end];
if (_vdi_port->ring_read(pipe_msg->data, _chunk_size) != _chunk_size) {
vd_printf("ring_read of chunk data failed");
_running = false;
break;
}
if (_pipe_connected) {
pipe_msg->type = VD_AGENT_COMMAND;
pipe_msg->opaque = _chunk_port;
pipe_msg->size = _chunk_size;
_pipe_state.write.end += count;
chunks_count++;
} else {
_pipe_state.write.start = _pipe_state.write.end = 0;
}
_chunk_size = 0;
_chunk_port = 0;
} else {
break;
}
}
if (_pipe_connected && chunks_count && !_pending_write) {
pipe_write_completion();
}
}
bool VDService::handle_agent_control(VDPipeMessage* msg)
{
switch (msg->type) {
case VD_AGENT_RESET_ACK: {
if (msg->opaque != _connection_id) {
vd_printf("Agent reset ack mismatch %u %u", msg->opaque, _connection_id);
break;
}
vd_printf("Agent reset ack");
_pending_reset = false;
break;
}
default:
vd_printf("Unsupported control %u %u", msg->type, msg->opaque);
return false;
}
return true;
}
void VDService::write_agent_control(uint32_t type, uint32_t opaque)
{
if (!_pipe_connected) {
return;
}
if (_pipe_state.write.end + sizeof(VDPipeMessage) > sizeof(_pipe_state.write.data)) {
vd_printf("msg is too large");
_running = false;
return;
}
VDPipeMessage* msg = (VDPipeMessage*)&_pipe_state.write.data[_pipe_state.write.end];
msg->type = type;
msg->opaque = opaque;
_pipe_state.write.end += sizeof(VDPipeMessage);
if (!_pending_write) {
pipe_write_completion();
}
}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
bool success = false;
if (!supported_system_version()) {
printf("vdservice is not supported in this system version\n");
return -1;
}
VDService* vdservice = VDService::get();
if (argc > 1) {
if (lstrcmpi(argv[1], TEXT("install")) == 0) {
success = vdservice->install();
} else if (lstrcmpi(argv[1], TEXT("uninstall")) == 0) {
success = vdservice->uninstall();
} else {
printf("Use: vdservice install / uninstall\n");
}
} else {
success = vdservice->run();
}
delete vdservice;
return (success ? 0 : -1);
}