/*
* remote TCP control connection
*
* Copyright (c) 2006 Daniel Veillard
*
* 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 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "vl.h"
#ifdef CONFIG_TCPCTRL
#include
#include
#include
#include
/* #define DEBUG_TCPCTRL */
static int tcpctrl_fd = -1;
static char tcpctrl_socket[150] = "";
typedef struct _tcp_client {
int fd;
CharDriverState drv;
} tcp_client;
static int
chrctr_write(struct CharDriverState *s, const uint8_t *buf, int len) {
tcp_client *info;
int written, sent = 0;
if ((s == NULL) || (buf == NULL) || (len < 0) || (s->opaque == NULL))
return(-1);
if (tcpctrl_fd == -1)
return(-1);
info = s->opaque;
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "chrctr_write(fd = %d, len = %d)\n", info->fd, len);
#endif
retry_write:
written = write(info->fd, buf + sent, len - sent);
if (written < 0) {
if ((errno == EINTR) || (errno == EAGAIN))
goto retry_write;
close(info->fd);
qemu_set_fd_handler(info->fd, NULL, NULL, NULL);
info->fd = -1;
return(-1);
}
if (written + sent < len) {
sent += written;
goto retry_write;
}
return(written);
}
static void
tcpctrl_disconnect(tcp_client *info)
{
if ((info == NULL) || (info->fd < 0))
return;
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "tcpctrl_disconnect(fd = %d)\n", info->fd);
#endif
/*
* this seems the best way to unregister this should work
* even in the loop over iov in the main loop
*/
if (info->fd > 0) {
qemu_set_fd_handler(info->fd, NULL, NULL, NULL);
close(info->fd);
}
qemu_free(info);
}
static int tcpctrl_process(tcp_client *info, const char *command) {
if (tcpctrl_fd == -1)
return(-1);
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "tcpctrl_process(command = '%s')\n", command);
#endif
monitor_process_command(&info->drv, command);
/* writing back to tcp connection may have failed */
if (info->fd < 0)
tcpctrl_disconnect(info);
}
static void tcpctrl_read(void *opaque)
{
char buffer[4096 + 1];
int ret;
tcp_client *info = opaque;
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "tcpctrl_read(fd = %d)\n", info->fd);
#endif
if (tcpctrl_fd == -1) {
tcpctrl_disconnect(info);
return;
}
retry_read:
ret = read(info->fd, &buffer[0], sizeof(buffer) - 1);
if (ret < 0) {
if ((errno == EINTR) || (errno == EAGAIN))
goto retry_read;
tcpctrl_disconnect(info);
return;
}
if (ret == 0) {
tcpctrl_disconnect(info);
return;
}
buffer[ret] = 0;
tcpctrl_process(info, &buffer[0]);
}
static void
tcpctrl_accept(void *opaque)
{
struct sockaddr_in sockaddr;
socklen_t len;
int val, fd;
tcp_client *info;
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "tcpctrl_accept()\n");
#endif
for(;;) {
len = sizeof(sockaddr);
fd = accept(tcpctrl_fd, (struct sockaddr *)&sockaddr, &len);
if (fd < 0 && errno != EINTR) {
perror("accept");
return;
} else if (fd >= 0) {
break;
}
}
/* set short latency */
val = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
info = qemu_mallocz(sizeof(tcp_client));
if (!info) {
close(fd);
return;
}
info->fd = fd;
info->drv.chr_write = chrctr_write;
info->drv.opaque = info;
fcntl(fd, F_SETFL, O_NONBLOCK);
qemu_set_fd_handler(fd, tcpctrl_read, NULL, info);
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "tcpctrl_accept(): fd = %d\n", fd);
#endif
}
static int tcpctrl_open()
{
int fd = -1;
int pid = getpid();
mode_t oldmask;
struct sockaddr_un addr;
snprintf(tcpctrl_socket, sizeof(tcpctrl_socket) - 1,
"%s/qemu-%d-socket", "/tmp", pid);
tcpctrl_socket[sizeof(tcpctrl_socket) - 1] = 0;
unlink(tcpctrl_socket);
fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
fprintf(stderr, "Failed to create unix socket");
return(-1);
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(&addr.sun_path[0], tcpctrl_socket, (sizeof(addr) - 4) - 1);
oldmask = umask(0077);
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
fprintf(stderr, "Failed to bind to socket %s\n", tcpctrl_socket);
close(fd);
umask(oldmask);
return(-1);
}
if (listen(fd, 3) < 0) {
fprintf(stderr, "Failed to listen to socket %s\n", tcpctrl_socket);
close(fd);
umask(oldmask);
return(-1);
}
umask(oldmask);
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "Listening on TCP control socket %s\n", tcpctrl_socket);
#endif
return fd;
}
int tcpctrl_start()
{
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "tcpctrl_start()\n");
#endif
tcpctrl_fd = tcpctrl_open();
if (tcpctrl_fd < 0)
return -1;
qemu_set_fd_handler(tcpctrl_fd, tcpctrl_accept, NULL, NULL);
return 0;
}
void tcpctrl_stop()
{
if (tcpctrl_fd < 0)
return;
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "tcpctrl_stop()\n");
#endif
unlink(tcpctrl_socket);
qemu_set_fd_handler(tcpctrl_fd, NULL, NULL, NULL);
close(tcpctrl_fd);
tcpctrl_fd = -1;
return;
}
#endif /* CONFIG_TCPCTRL */