/*
Copyright (C) 2009 Red Hat, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include "common.h"
#include "controller.h"
#include
#include "cmd_line_parser.h"
#include "menu.h"
#include "utils.h"
#include "debug.h"
#include "platform.h"
#define PIPE_NAME_MAX_LEN 50
#ifdef WIN32
#define PIPE_NAME "SpiceController-%lu"
#endif
Controller::Controller(ControllerInterface *handler)
: _handler (handler)
, _exclusive (false)
, _refs (1)
{
char pipe_name[PIPE_NAME_MAX_LEN];
ASSERT(_handler);
#ifdef WIN32
snprintf(pipe_name, PIPE_NAME_MAX_LEN, PIPE_NAME, Platform::get_process_id());
#else
char *p_socket = getenv("SPICE_XPI_SOCKET");
if (!p_socket) {
LOG_ERROR("Failed to get a controller connection (SPICE_XPI_SOCKET)");
throw Exception("Failed to get a controller connection (SPICE_XPI_SOCKET)");
}
strncpy(pipe_name, p_socket, sizeof(pipe_name));
#endif
LOG_INFO("Creating a controller connection %s", pipe_name);
_pipe = NamedPipe::create(pipe_name, *this);
if (!_pipe) {
LOG_ERROR("Failed to create a controller connection");
}
}
Controller::~Controller()
{
std::map::const_iterator conn;
for (conn = _connections.begin(); conn != _connections.end(); ++conn) {
conn->second->reset_handler();
delete conn->second;
}
if (_pipe) {
NamedPipe::destroy(_pipe);
}
}
NamedPipe::ConnectionInterface& Controller::create()
{
ControllerConnection *conn = new ControllerConnection(_handler, *this);
if (conn == NULL) {
throw Exception("Error allocating a new controller connection");
}
return *conn;
}
bool Controller::set_exclusive(bool exclusive)
{
if (_exclusive) {
LOG_ERROR("Cannot init controller, an exclusive controller already connected");
return false;
}
if (exclusive && _connections.size() > 1) {
LOG_ERROR("Cannot init exclusive controller, other controllers already connected");
return false;
}
_exclusive = exclusive;
return true;
}
void Controller::add_connection(NamedPipe::ConnectionRef conn_ref, ControllerConnection *conn)
{
_connections[conn_ref] = conn;
conn->on_data();
}
void Controller::remove_connection(NamedPipe::ConnectionRef conn_ref)
{
ControllerConnection *conn = _connections[conn_ref];
_connections.erase(conn_ref);
_exclusive = false;
delete conn;
}
void Controller::on_command(NamedPipe::ConnectionRef conn_ref, int32_t id)
{
ControllerConnection *conn = _connections[conn_ref];
ControllerValue msg;
ASSERT(conn);
msg.base.id = CONTROLLER_MENU_ITEM_CLICK;
msg.base.size = sizeof(msg);
msg.value = id;
conn->write_msg(&msg.base, msg.base.size);
}
ControllerConnection::ControllerConnection(ControllerInterface *handler, Controller& parent)
: _handler (handler)
, _parent (parent)
, _initialized (false)
, _write_pending (0)
, _write_pos (_write_buf)
, _read_pos (_read_buf)
, _port (-1)
, _sport (-1)
, _full_screen (false)
, _auto_display_res (false)
{
}
ControllerConnection::~ControllerConnection()
{
if (_opaque != NamedPipe::INVALID_CONNECTION) {
NamedPipe::destroy_connection(_opaque);
}
if (_handler) {
_handler->clear_menu_items(_opaque);
_handler->delete_menu();
}
}
void ControllerConnection::bind(NamedPipe::ConnectionRef conn_ref)
{
_opaque = conn_ref;
_parent.add_connection(conn_ref, this);
}
void ControllerConnection::on_data()
{
if (_write_pending) {
LOG_INFO("Resume pending write %d", _write_pending);
if (!write_msg(_write_pos, _write_pending)) {
return;
}
}
while (read_msgs());
}
bool ControllerConnection::read_msgs()
{
uint8_t *pos = _read_buf;
size_t nread = _read_pos - _read_buf;
int32_t size;
ASSERT(_handler);
ASSERT(_opaque != NamedPipe::INVALID_CONNECTION);
size = NamedPipe::read(_opaque, (uint8_t*)_read_pos, sizeof(_read_buf) - nread);
if (size == 0) {
return false;
} else if (size < 0) {
LOG_ERROR("Error reading from named pipe %d", size);
_parent.remove_connection(_opaque);
return false;
}
nread += size;
while (nread > 0) {
if (!_initialized && nread >= sizeof(ControllerInitHeader)) {
ControllerInitHeader *init = (ControllerInitHeader *)pos;
if (init->magic != CONTROLLER_MAGIC || init->version != CONTROLLER_VERSION) {
LOG_ERROR("Bad controller init, magic=0x%x version=%u", init->magic,
init->version);
_parent.remove_connection(_opaque);
return false;
}
if (nread < init->size) {
break;
}
if (!handle_init((ControllerInit*)init)) {
_parent.remove_connection(_opaque);
return false;
}
nread -= init->size;
pos += init->size;
_initialized = true;
}
if (!_initialized || nread < sizeof(ControllerMsg)) {
break;
}
ControllerMsg *hdr = (ControllerMsg *)pos;
if (hdr->size < sizeof(ControllerMsg)) {
LOG_ERROR("Bad controller message, size=%u", hdr->size);
_parent.remove_connection(_opaque);
return false;
}
if (nread < hdr->size) {
break;
}
handle_message(hdr);
nread -= hdr->size;
pos += hdr->size;
}
if (nread > 0 && pos != _read_buf) {
memcpy(_read_buf, pos, nread);
}
_read_pos = _read_buf + nread;
return true;
}
bool ControllerConnection::write_msg(const void *buf, int len)
{
RecurciveLock lock(_write_lock);
uint8_t *pos;
int32_t written = 0;
ASSERT(_opaque != NamedPipe::INVALID_CONNECTION);
if (_write_pending && buf != _write_pos) {
if ((_write_pos + _write_pending + len > _write_buf + sizeof(_write_buf)) &&
!write_msg(_write_pos, _write_pending)) {
return false;
}
if (_write_pending) {
if (_write_pos + _write_pending + len > _write_buf + sizeof(_write_buf)) {
DBG(0, "Dropping message, due to insufficient space in write buffer");
return true;
}
memcpy(_write_pos + _write_pending, buf, len);
_write_pending += len;
}
}
pos = (uint8_t*)buf;
while (len && (written = NamedPipe::write(_opaque, pos, len)) > 0) {
pos += written;
len -= written;
}
if (len && written == 0) {
if (_write_pending) {
_write_pos = pos;
} else {
_write_pos = _write_buf;
memcpy(_write_buf, pos, len);
}
_write_pending = len;
} else if (written < 0) {
LOG_ERROR("Error writing to named pipe %d", written);
_parent.remove_connection(_opaque);
return false;
} else {
_write_pending = 0;
}
return true;
}
bool ControllerConnection::handle_init(ControllerInit *init)
{
ASSERT(_handler);
if (init->credentials != 0) {
LOG_ERROR("Controller menu has wrong credentials 0x%x", init->credentials);
return false;
}
if (!_parent.set_exclusive(init->flags & CONTROLLER_FLAG_EXCLUSIVE)) {
return false;
}
return true;
}
bool ControllerConnection::handle_message(ControllerMsg *hdr)
{
uint32_t value = ((ControllerValue*)hdr)->value;
char *data = (char*)((ControllerData*)hdr)->data;
ASSERT(_handler);
switch (hdr->id) {
case CONTROLLER_HOST:
_host.assign(data);
break;
case CONTROLLER_PORT:
_port = value;
break;
case CONTROLLER_SPORT:
_sport = value;
break;
case CONTROLLER_PASSWORD:
_password.assign(data);
break;
case CONTROLLER_SECURE_CHANNELS:
case CONTROLLER_DISABLE_CHANNELS:
return set_multi_val(hdr->id, data);
case CONTROLLER_TLS_CIPHERS:
return _handler->set_connection_ciphers(data, "Controller");
case CONTROLLER_CA_FILE:
return _handler->set_ca_file(data, "Controller");
case CONTROLLER_HOST_SUBJECT:
return _handler->set_host_cert_subject(data, "Controller");
case CONTROLLER_FULL_SCREEN:
_full_screen = !!(value & CONTROLLER_SET_FULL_SCREEN);
_handler->set_auto_display_res(!!(value & CONTROLLER_AUTO_DISPLAY_RES));
break;
case CONTROLLER_SET_TITLE: {
std::string str;
string_printf(str, "%s", data);
_handler->set_title(str);
break;
}
case CONTROLLER_HOTKEYS:
_handler->set_hotkeys(data);
break;
case CONTROLLER_CONNECT:
_handler->connect(_host, _port, _sport, _password);
break;
case CONTROLLER_SHOW:
_handler->show_me(_full_screen);
break;
case CONTROLLER_HIDE:
_handler->hide_me();
break;
case CONTROLLER_CREATE_MENU:
return create_menu((char*)data);
case CONTROLLER_DELETE_MENU:
_handler->delete_menu();
break;
case CONTROLLER_SEND_CAD:
default:
LOG_ERROR("Ignoring an unknown controller message %u", hdr->id);
return false;
}
return true;
}
#ifdef WIN32
#define next_tok(str, delim, state) strtok(str, delim)
#else
#define next_tok(str, delim, state) strtok_r(str, delim, state)
#endif
bool ControllerConnection::create_menu(char* resource)
{
bool ret = true;
#ifndef WIN32
char* item_state = 0;
#endif
char* next_item;
const char* param;
const char* text;
int parent_id;
int flags;
int state;
int id;
ASSERT(_handler);
AutoRef