354 lines
7.1 KiB
C
354 lines
7.1 KiB
C
/*
|
|
* Copyright (C) 2007 Martin Willi
|
|
* Hochschule fuer Technik Rapperswil
|
|
* Copyright (C) 2001-2004 Jeff Dike
|
|
*
|
|
* Based on the "uml_mconsole" utility from Jeff Dike.
|
|
*
|
|
* 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. See <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <sys/socket.h>
|
|
#include <errno.h>
|
|
#include <sys/un.h>
|
|
|
|
#include <utils/debug.h>
|
|
|
|
#include "mconsole.h"
|
|
|
|
#define MCONSOLE_MAGIC 0xcafebabe
|
|
#define MCONSOLE_VERSION 2
|
|
#define MCONSOLE_MAX_DATA 512
|
|
|
|
typedef struct private_mconsole_t private_mconsole_t;
|
|
|
|
struct private_mconsole_t {
|
|
/** public interface */
|
|
mconsole_t public;
|
|
/** mconsole socket */
|
|
int console;
|
|
/** notify socket */
|
|
int notify;
|
|
/** address of uml socket */
|
|
struct sockaddr_un uml;
|
|
/** idle function */
|
|
void (*idle)(void);
|
|
};
|
|
|
|
/**
|
|
* mconsole message format from "arch/um/include/mconsole.h"
|
|
*/
|
|
typedef struct mconsole_request mconsole_request;
|
|
/** mconsole request message */
|
|
struct mconsole_request {
|
|
uint32_t magic;
|
|
uint32_t version;
|
|
uint32_t len;
|
|
char data[MCONSOLE_MAX_DATA];
|
|
};
|
|
|
|
|
|
typedef struct mconsole_reply mconsole_reply;
|
|
/** mconsole reply message */
|
|
struct mconsole_reply {
|
|
uint32_t err;
|
|
uint32_t more;
|
|
uint32_t len;
|
|
char data[MCONSOLE_MAX_DATA];
|
|
};
|
|
|
|
typedef struct mconsole_notify mconsole_notify;
|
|
/** mconsole notify message */
|
|
struct mconsole_notify {
|
|
uint32_t magic;
|
|
uint32_t version;
|
|
enum {
|
|
MCONSOLE_SOCKET,
|
|
MCONSOLE_PANIC,
|
|
MCONSOLE_HANG,
|
|
MCONSOLE_USER_NOTIFY,
|
|
} type;
|
|
uint32_t len;
|
|
char data[MCONSOLE_MAX_DATA];
|
|
};
|
|
|
|
/**
|
|
* send a request to UML using mconsole
|
|
*/
|
|
static int request(private_mconsole_t *this, void(*cb)(void*,char*,size_t),
|
|
void *data, char *command, ...)
|
|
{
|
|
mconsole_request request;
|
|
mconsole_reply reply;
|
|
int len, flags = 0;
|
|
va_list args;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
request.magic = MCONSOLE_MAGIC;
|
|
request.version = MCONSOLE_VERSION;
|
|
va_start(args, command);
|
|
request.len = vsnprintf(request.data, sizeof(request.data), command, args);
|
|
va_end(args);
|
|
|
|
if (this->idle)
|
|
{
|
|
flags = MSG_DONTWAIT;
|
|
}
|
|
do
|
|
{
|
|
if (this->idle)
|
|
{
|
|
this->idle();
|
|
}
|
|
len = sendto(this->console, &request, sizeof(request), flags,
|
|
(struct sockaddr*)&this->uml, sizeof(this->uml));
|
|
}
|
|
while (len < 0 && (errno == EINTR || errno == EAGAIN));
|
|
|
|
if (len < 0)
|
|
{
|
|
DBG1(DBG_LIB, "sending mconsole command to UML failed: %m");
|
|
return -1;
|
|
}
|
|
do
|
|
{
|
|
len = recv(this->console, &reply, sizeof(reply), flags);
|
|
if (len < 0 && (errno == EINTR || errno == EAGAIN))
|
|
{
|
|
if (this->idle)
|
|
{
|
|
this->idle();
|
|
}
|
|
continue;
|
|
}
|
|
if (len < 0)
|
|
{
|
|
DBG1(DBG_LIB, "receiving from mconsole failed: %m");
|
|
return -1;
|
|
}
|
|
if (len > 0)
|
|
{
|
|
if (cb)
|
|
{
|
|
cb(data, reply.data, reply.len);
|
|
}
|
|
else if (reply.err)
|
|
{
|
|
if (reply.len && *reply.data)
|
|
{
|
|
DBG1(DBG_LIB, "received mconsole error %d: %.*s",
|
|
reply.err, (int)reply.len, reply.data);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
while (reply.more);
|
|
|
|
return reply.err;
|
|
}
|
|
|
|
/**
|
|
* ignore error message
|
|
*/
|
|
static void ignore(void *data, char *buf, size_t len)
|
|
{
|
|
}
|
|
|
|
METHOD(mconsole_t, add_iface, bool,
|
|
private_mconsole_t *this, char *guest, char *host)
|
|
{
|
|
int tries = 0;
|
|
|
|
while (tries++ < 5)
|
|
{
|
|
if (request(this, ignore, NULL, "config %s=tuntap,%s", guest, host) == 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
usleep(10000 * tries * tries);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
METHOD(mconsole_t, del_iface, bool,
|
|
private_mconsole_t *this, char *guest)
|
|
{
|
|
if (request(this, NULL, NULL, "remove %s", guest) != 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
METHOD(mconsole_t, exec, int,
|
|
private_mconsole_t *this, void(*cb)(void*,char*,size_t), void *data,
|
|
char *cmd)
|
|
{
|
|
return request(this, cb, data, "%s", cmd);
|
|
}
|
|
|
|
/**
|
|
* Poll until guest is ready
|
|
*/
|
|
static void wait_bootup(private_mconsole_t *this)
|
|
{
|
|
/* wait for init process to appear */
|
|
while (request(this, ignore, NULL, "exec ps -p 1 > /dev/null"))
|
|
{
|
|
if (this->idle)
|
|
{
|
|
this->idle();
|
|
}
|
|
usleep(100000);
|
|
}
|
|
}
|
|
|
|
METHOD(mconsole_t, destroy, void,
|
|
private_mconsole_t *this)
|
|
{
|
|
close(this->console);
|
|
close(this->notify);
|
|
free(this);
|
|
}
|
|
|
|
/**
|
|
* setup the mconsole notify connection and wait for its readiness
|
|
*/
|
|
static bool wait_for_notify(private_mconsole_t *this, char *nsock)
|
|
{
|
|
struct sockaddr_un addr;
|
|
mconsole_notify notify;
|
|
int len, flags = 0;
|
|
|
|
this->notify = socket(AF_UNIX, SOCK_DGRAM, 0);
|
|
if (this->notify < 0)
|
|
{
|
|
DBG1(DBG_LIB, "opening mconsole notify socket failed: %m");
|
|
return FALSE;
|
|
}
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sun_family = AF_UNIX;
|
|
strncpy(addr.sun_path, nsock, sizeof(addr.sun_path));
|
|
if (bind(this->notify, (struct sockaddr*)&addr, sizeof(addr)) < 0)
|
|
{
|
|
DBG1(DBG_LIB, "binding mconsole notify socket to '%s' failed: %m",
|
|
nsock);
|
|
close(this->notify);
|
|
return FALSE;
|
|
}
|
|
if (this->idle)
|
|
{
|
|
flags = MSG_DONTWAIT;
|
|
}
|
|
do
|
|
{
|
|
if (this->idle)
|
|
{
|
|
this->idle();
|
|
}
|
|
len = recvfrom(this->notify, ¬ify, sizeof(notify), flags, NULL, 0);
|
|
}
|
|
while (len < 0 && (errno == EINTR || errno == EAGAIN));
|
|
|
|
if (len < 0 || len >= sizeof(notify))
|
|
{
|
|
DBG1(DBG_LIB, "reading from mconsole notify socket failed: %m");
|
|
close(this->notify);
|
|
unlink(nsock);
|
|
return FALSE;
|
|
}
|
|
if (notify.magic != MCONSOLE_MAGIC ||
|
|
notify.version != MCONSOLE_VERSION ||
|
|
notify.type != MCONSOLE_SOCKET)
|
|
{
|
|
DBG1(DBG_LIB, "received unexpected message from mconsole notify"
|
|
" socket: %b", ¬ify, sizeof(notify));
|
|
close(this->notify);
|
|
unlink(nsock);
|
|
return FALSE;
|
|
}
|
|
memset(&this->uml, 0, sizeof(this->uml));
|
|
this->uml.sun_family = AF_UNIX;
|
|
strncpy(this->uml.sun_path, (char*)¬ify.data, sizeof(this->uml.sun_path));
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* setup the mconsole console connection
|
|
*/
|
|
static bool setup_console(private_mconsole_t *this)
|
|
{
|
|
struct sockaddr_un addr;
|
|
|
|
this->console = socket(AF_UNIX, SOCK_DGRAM, 0);
|
|
if (this->console < 0)
|
|
{
|
|
DBG1(DBG_LIB, "opening mconsole socket failed: %m");
|
|
return FALSE;
|
|
}
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sun_family = AF_UNIX;
|
|
snprintf(&addr.sun_path[1], sizeof(addr.sun_path)-1, "%5d-%d",
|
|
getpid(), this->console);
|
|
if (bind(this->console, (struct sockaddr*)&addr, sizeof(addr)) < 0)
|
|
{
|
|
DBG1(DBG_LIB, "binding mconsole socket to '%s' failed: %m",
|
|
&addr.sun_path[1]);
|
|
close(this->console);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* create the mconsole instance
|
|
*/
|
|
mconsole_t *mconsole_create(char *notify, void(*idle)(void))
|
|
{
|
|
private_mconsole_t *this;
|
|
|
|
INIT(this,
|
|
.public = {
|
|
.add_iface = _add_iface,
|
|
.del_iface = _del_iface,
|
|
.exec = _exec,
|
|
.destroy = _destroy,
|
|
},
|
|
.idle = idle,
|
|
);
|
|
|
|
if (!wait_for_notify(this, notify))
|
|
{
|
|
free(this);
|
|
return NULL;
|
|
}
|
|
|
|
if (!setup_console(this))
|
|
{
|
|
close(this->notify);
|
|
unlink(notify);
|
|
free(this);
|
|
return NULL;
|
|
}
|
|
unlink(notify);
|
|
|
|
wait_bootup(this);
|
|
|
|
return &this->public;
|
|
}
|
|
|