Import QEMU upstream snapshot d2e570c
Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
@@ -0,0 +1,318 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include <termios.h>
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/sockets.h"
|
||||
#include "channel.h"
|
||||
#include "cutils.h"
|
||||
|
||||
#ifdef CONFIG_SOLARIS
|
||||
#include <stropts.h>
|
||||
#endif
|
||||
|
||||
#define GA_CHANNEL_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
|
||||
|
||||
struct GAChannel {
|
||||
GIOChannel *listen_channel;
|
||||
GIOChannel *client_channel;
|
||||
GAChannelMethod method;
|
||||
GAChannelCallback event_cb;
|
||||
gpointer user_data;
|
||||
};
|
||||
|
||||
static int ga_channel_client_add(GAChannel *c, int fd);
|
||||
|
||||
static gboolean ga_channel_listen_accept(GIOChannel *channel,
|
||||
GIOCondition condition, gpointer data)
|
||||
{
|
||||
GAChannel *c = data;
|
||||
int ret, client_fd;
|
||||
bool accepted = false;
|
||||
Error *err = NULL;
|
||||
|
||||
g_assert(channel != NULL);
|
||||
|
||||
client_fd = qemu_accept(g_io_channel_unix_get_fd(channel), NULL, NULL);
|
||||
if (client_fd == -1) {
|
||||
g_warning("error converting fd to gsocket: %s", strerror(errno));
|
||||
goto out;
|
||||
}
|
||||
if (!qemu_set_blocking(client_fd, false, &err)) {
|
||||
g_warning("%s", error_get_pretty(err));
|
||||
error_free(err);
|
||||
goto out;
|
||||
}
|
||||
ret = ga_channel_client_add(c, client_fd);
|
||||
if (ret) {
|
||||
g_warning("error setting up connection");
|
||||
close(client_fd);
|
||||
goto out;
|
||||
}
|
||||
accepted = true;
|
||||
|
||||
out:
|
||||
/* only accept 1 connection at a time */
|
||||
return !accepted;
|
||||
}
|
||||
|
||||
/* start polling for readable events on listen fd, new==true
|
||||
* indicates we should use the existing s->listen_channel
|
||||
*/
|
||||
static void ga_channel_listen_add(GAChannel *c, int listen_fd, bool create)
|
||||
{
|
||||
if (create) {
|
||||
c->listen_channel = g_io_channel_unix_new(listen_fd);
|
||||
}
|
||||
g_io_add_watch(c->listen_channel, G_IO_IN, ga_channel_listen_accept, c);
|
||||
}
|
||||
|
||||
static void ga_channel_listen_close(GAChannel *c)
|
||||
{
|
||||
g_assert(c->listen_channel);
|
||||
g_io_channel_shutdown(c->listen_channel, true, NULL);
|
||||
g_io_channel_unref(c->listen_channel);
|
||||
c->listen_channel = NULL;
|
||||
}
|
||||
|
||||
/* cleanup state for closed connection/session, start accepting new
|
||||
* connections if we're in listening mode
|
||||
*/
|
||||
static void ga_channel_client_close(GAChannel *c)
|
||||
{
|
||||
g_assert(c->client_channel);
|
||||
g_io_channel_shutdown(c->client_channel, true, NULL);
|
||||
g_io_channel_unref(c->client_channel);
|
||||
c->client_channel = NULL;
|
||||
if (c->listen_channel) {
|
||||
ga_channel_listen_add(c, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean ga_channel_client_event(GIOChannel *channel,
|
||||
GIOCondition condition, gpointer data)
|
||||
{
|
||||
GAChannel *c = data;
|
||||
gboolean client_cont;
|
||||
|
||||
g_assert(c);
|
||||
if (c->event_cb) {
|
||||
client_cont = c->event_cb(condition, c->user_data);
|
||||
if (!client_cont) {
|
||||
ga_channel_client_close(c);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int ga_channel_client_add(GAChannel *c, int fd)
|
||||
{
|
||||
GIOChannel *client_channel;
|
||||
GError *err = NULL;
|
||||
|
||||
g_assert(c && !c->client_channel);
|
||||
client_channel = g_io_channel_unix_new(fd);
|
||||
g_assert(client_channel);
|
||||
g_io_channel_set_encoding(client_channel, NULL, &err);
|
||||
if (err != NULL) {
|
||||
g_warning("error setting channel encoding to binary");
|
||||
g_error_free(err);
|
||||
return -1;
|
||||
}
|
||||
g_io_add_watch(client_channel, G_IO_IN | G_IO_HUP,
|
||||
ga_channel_client_event, c);
|
||||
c->client_channel = client_channel;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static gboolean ga_channel_open(GAChannel *c, const gchar *path,
|
||||
GAChannelMethod method, int fd, Error **errp)
|
||||
{
|
||||
int ret;
|
||||
c->method = method;
|
||||
|
||||
switch (c->method) {
|
||||
case GA_CHANNEL_VIRTIO_SERIAL: {
|
||||
assert(fd < 0);
|
||||
fd = qga_open_cloexec(
|
||||
path,
|
||||
#ifndef CONFIG_SOLARIS
|
||||
O_ASYNC |
|
||||
#endif
|
||||
O_RDWR | O_NONBLOCK,
|
||||
0
|
||||
);
|
||||
if (fd == -1) {
|
||||
error_setg_errno(errp, errno, "error opening channel '%s'", path);
|
||||
return false;
|
||||
}
|
||||
#ifdef CONFIG_SOLARIS
|
||||
ret = ioctl(fd, I_SETSIG, S_OUTPUT | S_INPUT | S_HIPRI);
|
||||
if (ret == -1) {
|
||||
error_setg_errno(errp, errno, "error setting event mask for channel");
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
#ifdef __FreeBSD__
|
||||
/*
|
||||
* In the default state channel sends echo of every command to a
|
||||
* client. The client program doesn't expect this and raises an
|
||||
* error. Suppress echo by resetting ECHO terminal flag.
|
||||
*/
|
||||
struct termios tio;
|
||||
if (tcgetattr(fd, &tio) < 0) {
|
||||
error_setg_errno(errp, errno, "error getting channel termios attrs");
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
tio.c_lflag &= ~ECHO;
|
||||
if (tcsetattr(fd, TCSAFLUSH, &tio) < 0) {
|
||||
error_setg_errno(errp, errno, "error setting channel termios attrs");
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
#endif /* __FreeBSD__ */
|
||||
ret = ga_channel_client_add(c, fd);
|
||||
if (ret) {
|
||||
error_setg(errp, "error adding channel to main loop");
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GA_CHANNEL_ISA_SERIAL: {
|
||||
struct termios tio;
|
||||
|
||||
assert(fd < 0);
|
||||
fd = qga_open_cloexec(path, O_RDWR | O_NOCTTY | O_NONBLOCK, 0);
|
||||
if (fd == -1) {
|
||||
error_setg_errno(errp, errno, "error opening channel '%s'", path);
|
||||
return false;
|
||||
}
|
||||
tcgetattr(fd, &tio);
|
||||
/* set up serial port for non-canonical, dumb byte streaming */
|
||||
tio.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP |
|
||||
INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY |
|
||||
IMAXBEL);
|
||||
tio.c_oflag = 0;
|
||||
tio.c_lflag = 0;
|
||||
tio.c_cflag |= GA_CHANNEL_BAUDRATE_DEFAULT;
|
||||
/* 1 available byte min or reads will block (we'll set non-blocking
|
||||
* elsewhere, else we have to deal with read()=0 instead)
|
||||
*/
|
||||
tio.c_cc[VMIN] = 1;
|
||||
tio.c_cc[VTIME] = 0;
|
||||
/* flush everything waiting for read/xmit, it's garbage at this point */
|
||||
tcflush(fd, TCIFLUSH);
|
||||
tcsetattr(fd, TCSANOW, &tio);
|
||||
ret = ga_channel_client_add(c, fd);
|
||||
if (ret) {
|
||||
error_setg(errp, "error adding channel to main loop");
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GA_CHANNEL_UNIX_LISTEN: {
|
||||
if (fd < 0) {
|
||||
fd = unix_listen(path, errp);
|
||||
if (fd < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ga_channel_listen_add(c, fd, true);
|
||||
break;
|
||||
}
|
||||
case GA_CHANNEL_VSOCK_LISTEN: {
|
||||
if (fd < 0) {
|
||||
SocketAddress *addr;
|
||||
char *addr_str;
|
||||
|
||||
addr_str = g_strdup_printf("vsock:%s", path);
|
||||
addr = socket_parse(addr_str, errp);
|
||||
g_free(addr_str);
|
||||
if (!addr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fd = socket_listen(addr, 1, errp);
|
||||
qapi_free_SocketAddress(addr);
|
||||
if (fd < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ga_channel_listen_add(c, fd, true);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
error_setg(errp, "error binding/listening to specified socket");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
GIOStatus ga_channel_write_all(GAChannel *c, const gchar *buf, gsize size)
|
||||
{
|
||||
GError *err = NULL;
|
||||
gsize written = 0;
|
||||
GIOStatus status = G_IO_STATUS_NORMAL;
|
||||
|
||||
while (size) {
|
||||
g_debug("sending data, count: %d", (int)size);
|
||||
status = g_io_channel_write_chars(c->client_channel, buf, size,
|
||||
&written, &err);
|
||||
if (status == G_IO_STATUS_NORMAL) {
|
||||
size -= written;
|
||||
buf += written;
|
||||
} else if (status != G_IO_STATUS_AGAIN) {
|
||||
g_warning("error writing to channel: %s", err->message);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
status = g_io_channel_flush(c->client_channel, &err);
|
||||
} while (status == G_IO_STATUS_AGAIN);
|
||||
|
||||
if (status != G_IO_STATUS_NORMAL) {
|
||||
g_warning("error flushing channel: %s", err->message);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
GIOStatus ga_channel_read(GAChannel *c, gchar *buf, gsize size, gsize *count)
|
||||
{
|
||||
return g_io_channel_read_chars(c->client_channel, buf, size, count, NULL);
|
||||
}
|
||||
|
||||
GAChannel *ga_channel_new(GAChannelMethod method, const gchar *path,
|
||||
int listen_fd, GAChannelCallback cb, gpointer opaque)
|
||||
{
|
||||
Error *err = NULL;
|
||||
GAChannel *c = g_new0(GAChannel, 1);
|
||||
c->event_cb = cb;
|
||||
c->user_data = opaque;
|
||||
|
||||
if (!ga_channel_open(c, path, method, listen_fd, &err)) {
|
||||
g_critical("%s", error_get_pretty(err));
|
||||
error_free(err);
|
||||
ga_channel_free(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void ga_channel_free(GAChannel *c)
|
||||
{
|
||||
if (c->listen_channel) {
|
||||
ga_channel_listen_close(c);
|
||||
}
|
||||
if (c->client_channel) {
|
||||
ga_channel_client_close(c);
|
||||
}
|
||||
g_free(c);
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#include "guest-agent-core.h"
|
||||
#include "channel.h"
|
||||
|
||||
typedef struct GAChannelReadState {
|
||||
guint thread_id;
|
||||
uint8_t *buf;
|
||||
size_t buf_size;
|
||||
size_t cur; /* current buffer start */
|
||||
size_t pending; /* pending buffered bytes to read */
|
||||
OVERLAPPED ov;
|
||||
bool ov_pending; /* whether on async read is outstanding */
|
||||
} GAChannelReadState;
|
||||
|
||||
struct GAChannel {
|
||||
HANDLE handle;
|
||||
GAChannelCallback cb;
|
||||
gpointer user_data;
|
||||
GAChannelReadState rstate;
|
||||
GIOCondition pending_events; /* TODO: use GAWatch.pollfd.revents */
|
||||
GSource *source;
|
||||
};
|
||||
|
||||
typedef struct GAWatch {
|
||||
GSource source;
|
||||
GPollFD pollfd;
|
||||
GAChannel *channel;
|
||||
GIOCondition events_mask;
|
||||
} GAWatch;
|
||||
|
||||
/*
|
||||
* Called by glib prior to polling to set up poll events if polling is needed.
|
||||
*
|
||||
*/
|
||||
static gboolean ga_channel_prepare(GSource *source, gint *timeout_ms)
|
||||
{
|
||||
GAWatch *watch = (GAWatch *)source;
|
||||
GAChannel *c = (GAChannel *)watch->channel;
|
||||
GAChannelReadState *rs = &c->rstate;
|
||||
DWORD count_read, count_to_read = 0;
|
||||
bool success;
|
||||
GIOCondition new_events = 0;
|
||||
|
||||
g_debug("prepare");
|
||||
/* go ahead and submit another read if there's room in the buffer
|
||||
* and no previous reads are outstanding
|
||||
*/
|
||||
if (!rs->ov_pending) {
|
||||
if (rs->cur + rs->pending >= rs->buf_size) {
|
||||
if (rs->cur) {
|
||||
memmove(rs->buf, rs->buf + rs->cur, rs->pending);
|
||||
rs->cur = 0;
|
||||
}
|
||||
}
|
||||
count_to_read = rs->buf_size - rs->cur - rs->pending;
|
||||
}
|
||||
|
||||
if (rs->ov_pending || count_to_read <= 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* submit the read */
|
||||
success = ReadFile(c->handle, rs->buf + rs->cur + rs->pending,
|
||||
count_to_read, &count_read, &rs->ov);
|
||||
if (success) {
|
||||
rs->pending += count_read;
|
||||
rs->ov_pending = false;
|
||||
} else {
|
||||
if (GetLastError() == ERROR_IO_PENDING) {
|
||||
rs->ov_pending = true;
|
||||
} else {
|
||||
new_events |= G_IO_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
/* don't block forever, iterate the main loop every once in a while */
|
||||
*timeout_ms = 500;
|
||||
/* if there's data in the read buffer, or another event is pending,
|
||||
* skip polling and issue user cb.
|
||||
*/
|
||||
if (rs->pending) {
|
||||
new_events |= G_IO_IN;
|
||||
}
|
||||
c->pending_events |= new_events;
|
||||
return !!c->pending_events;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by glib after an outstanding read request is completed.
|
||||
*/
|
||||
static gboolean ga_channel_check(GSource *source)
|
||||
{
|
||||
GAWatch *watch = (GAWatch *)source;
|
||||
GAChannel *c = (GAChannel *)watch->channel;
|
||||
GAChannelReadState *rs = &c->rstate;
|
||||
DWORD count_read, error;
|
||||
BOOL success;
|
||||
|
||||
GIOCondition new_events = 0;
|
||||
|
||||
g_debug("check");
|
||||
|
||||
/* failing this implies we issued a read that completed immediately,
|
||||
* yet no data was placed into the buffer (and thus we did not skip
|
||||
* polling). but since EOF is not obtainable until we retrieve an
|
||||
* overlapped result, it must be the case that there was data placed
|
||||
* into the buffer, or an error was generated by Readfile(). in either
|
||||
* case, we should've skipped the polling for this round.
|
||||
*/
|
||||
g_assert(rs->ov_pending);
|
||||
|
||||
success = GetOverlappedResult(c->handle, &rs->ov, &count_read, FALSE);
|
||||
if (success) {
|
||||
g_debug("thread: overlapped result, count_read: %d", (int)count_read);
|
||||
rs->pending += count_read;
|
||||
new_events |= G_IO_IN;
|
||||
} else {
|
||||
error = GetLastError();
|
||||
if (error == 0 || error == ERROR_HANDLE_EOF ||
|
||||
error == ERROR_NO_SYSTEM_RESOURCES ||
|
||||
error == ERROR_OPERATION_ABORTED) {
|
||||
/* note: On WinXP SP3 with rhel6ga virtio-win-1.1.16 vioser drivers,
|
||||
* ENSR seems to be synonymous with when we'd normally expect
|
||||
* ERROR_HANDLE_EOF. So treat it as such. Microsoft's
|
||||
* recommendation for ERROR_NO_SYSTEM_RESOURCES is to
|
||||
* retry the read, so this happens to work out anyway. On newer
|
||||
* virtio-win driver, this seems to be replaced with EOA, so
|
||||
* handle that in the same fashion.
|
||||
*/
|
||||
new_events |= G_IO_HUP;
|
||||
} else if (error != ERROR_IO_INCOMPLETE) {
|
||||
g_critical("error retrieving overlapped result: %d", (int)error);
|
||||
new_events |= G_IO_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_events) {
|
||||
rs->ov_pending = 0;
|
||||
}
|
||||
c->pending_events |= new_events;
|
||||
|
||||
return !!c->pending_events;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by glib after either prepare or check routines signal readiness
|
||||
*/
|
||||
static gboolean ga_channel_dispatch(GSource *source, GSourceFunc unused,
|
||||
gpointer user_data)
|
||||
{
|
||||
GAWatch *watch = (GAWatch *)source;
|
||||
GAChannel *c = (GAChannel *)watch->channel;
|
||||
GAChannelReadState *rs = &c->rstate;
|
||||
gboolean success;
|
||||
|
||||
g_debug("dispatch");
|
||||
success = c->cb(watch->pollfd.revents, c->user_data);
|
||||
|
||||
if (c->pending_events & G_IO_ERR) {
|
||||
g_critical("channel error, removing source");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* TODO: replace rs->pending with watch->revents */
|
||||
c->pending_events &= ~G_IO_HUP;
|
||||
if (!rs->pending) {
|
||||
c->pending_events &= ~G_IO_IN;
|
||||
} else {
|
||||
c->pending_events = 0;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
static void ga_channel_finalize(GSource *source)
|
||||
{
|
||||
g_debug("finalize");
|
||||
}
|
||||
|
||||
GSourceFuncs ga_channel_watch_funcs = {
|
||||
ga_channel_prepare,
|
||||
ga_channel_check,
|
||||
ga_channel_dispatch,
|
||||
ga_channel_finalize
|
||||
};
|
||||
|
||||
static GSource *ga_channel_create_watch(GAChannel *c)
|
||||
{
|
||||
GSource *source = g_source_new(&ga_channel_watch_funcs, sizeof(GAWatch));
|
||||
GAWatch *watch = (GAWatch *)source;
|
||||
|
||||
watch->channel = c;
|
||||
watch->pollfd.fd = (gintptr) c->rstate.ov.hEvent;
|
||||
g_source_add_poll(source, &watch->pollfd);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
GIOStatus ga_channel_read(GAChannel *c, char *buf, size_t size, gsize *count)
|
||||
{
|
||||
GAChannelReadState *rs = &c->rstate;
|
||||
GIOStatus status;
|
||||
size_t to_read = 0;
|
||||
|
||||
if (c->pending_events & G_IO_ERR) {
|
||||
return G_IO_STATUS_ERROR;
|
||||
}
|
||||
|
||||
*count = to_read = MIN(size, rs->pending);
|
||||
if (to_read) {
|
||||
memcpy(buf, rs->buf + rs->cur, to_read);
|
||||
rs->cur += to_read;
|
||||
rs->pending -= to_read;
|
||||
status = G_IO_STATUS_NORMAL;
|
||||
} else {
|
||||
status = G_IO_STATUS_AGAIN;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static GIOStatus ga_channel_write(GAChannel *c, const char *buf, size_t size,
|
||||
size_t *count)
|
||||
{
|
||||
GIOStatus status;
|
||||
OVERLAPPED ov = {0};
|
||||
BOOL ret;
|
||||
DWORD written;
|
||||
|
||||
ov.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
ret = WriteFile(c->handle, buf, size, &written, &ov);
|
||||
if (!ret) {
|
||||
if (GetLastError() == ERROR_IO_PENDING) {
|
||||
/* write is pending */
|
||||
ret = GetOverlappedResult(c->handle, &ov, &written, TRUE);
|
||||
if (!ret) {
|
||||
if (!GetLastError()) {
|
||||
status = G_IO_STATUS_AGAIN;
|
||||
} else {
|
||||
status = G_IO_STATUS_ERROR;
|
||||
}
|
||||
} else {
|
||||
/* write is complete */
|
||||
status = G_IO_STATUS_NORMAL;
|
||||
*count = written;
|
||||
}
|
||||
} else {
|
||||
status = G_IO_STATUS_ERROR;
|
||||
}
|
||||
} else {
|
||||
/* write returned immediately */
|
||||
status = G_IO_STATUS_NORMAL;
|
||||
*count = written;
|
||||
}
|
||||
|
||||
if (ov.hEvent) {
|
||||
CloseHandle(ov.hEvent);
|
||||
ov.hEvent = NULL;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
GIOStatus ga_channel_write_all(GAChannel *c, const char *buf, size_t size)
|
||||
{
|
||||
GIOStatus status = G_IO_STATUS_NORMAL;
|
||||
size_t count = 0;
|
||||
|
||||
while (size) {
|
||||
status = ga_channel_write(c, buf, size, &count);
|
||||
if (status == G_IO_STATUS_NORMAL) {
|
||||
size -= count;
|
||||
buf += count;
|
||||
} else if (status != G_IO_STATUS_AGAIN) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static gboolean ga_channel_open(GAChannel *c, GAChannelMethod method,
|
||||
const gchar *path)
|
||||
{
|
||||
COMMTIMEOUTS comTimeOut = {0};
|
||||
gchar newpath[MAXPATHLEN] = {0};
|
||||
comTimeOut.ReadIntervalTimeout = 1;
|
||||
|
||||
if (method != GA_CHANNEL_VIRTIO_SERIAL && method != GA_CHANNEL_ISA_SERIAL) {
|
||||
g_critical("unsupported communication method");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (method == GA_CHANNEL_ISA_SERIAL) {
|
||||
snprintf(newpath, sizeof(newpath), "\\\\.\\%s", path);
|
||||
} else {
|
||||
g_strlcpy(newpath, path, sizeof(newpath));
|
||||
}
|
||||
|
||||
c->handle = CreateFile(newpath, GENERIC_READ | GENERIC_WRITE, 0, NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, NULL);
|
||||
if (c->handle == INVALID_HANDLE_VALUE) {
|
||||
g_autofree gchar *emsg = g_win32_error_message(GetLastError());
|
||||
g_critical("error opening path %s: %s", newpath, emsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (method == GA_CHANNEL_ISA_SERIAL
|
||||
&& !SetCommTimeouts(c->handle, &comTimeOut)) {
|
||||
g_autofree gchar *emsg = g_win32_error_message(GetLastError());
|
||||
g_critical("error setting timeout for com port: %s", emsg);
|
||||
CloseHandle(c->handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
GAChannel *ga_channel_new(GAChannelMethod method, const gchar *path,
|
||||
int listen_fd, GAChannelCallback cb, gpointer opaque)
|
||||
{
|
||||
GAChannel *c = g_new0(GAChannel, 1);
|
||||
SECURITY_ATTRIBUTES sec_attrs;
|
||||
|
||||
if (!ga_channel_open(c, method, path)) {
|
||||
g_critical("error opening channel");
|
||||
g_free(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c->cb = cb;
|
||||
c->user_data = opaque;
|
||||
|
||||
sec_attrs.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sec_attrs.lpSecurityDescriptor = NULL;
|
||||
sec_attrs.bInheritHandle = false;
|
||||
|
||||
c->rstate.buf_size = QGA_READ_COUNT_DEFAULT;
|
||||
c->rstate.buf = g_malloc(QGA_READ_COUNT_DEFAULT);
|
||||
c->rstate.ov.hEvent = CreateEvent(&sec_attrs, FALSE, FALSE, NULL);
|
||||
|
||||
c->source = ga_channel_create_watch(c);
|
||||
g_source_attach(c->source, NULL);
|
||||
return c;
|
||||
}
|
||||
|
||||
void ga_channel_free(GAChannel *c)
|
||||
{
|
||||
if (c->source) {
|
||||
g_source_destroy(c->source);
|
||||
}
|
||||
if (c->rstate.ov.hEvent) {
|
||||
CloseHandle(c->rstate.ov.hEvent);
|
||||
}
|
||||
g_free(c->rstate.buf);
|
||||
g_free(c);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* QEMU Guest Agent channel declarations
|
||||
*
|
||||
* Copyright IBM Corp. 2012
|
||||
*
|
||||
* Authors:
|
||||
* Michael Roth <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
#ifndef QGA_CHANNEL_H
|
||||
#define QGA_CHANNEL_H
|
||||
|
||||
|
||||
typedef struct GAChannel GAChannel;
|
||||
|
||||
typedef enum {
|
||||
GA_CHANNEL_VIRTIO_SERIAL,
|
||||
GA_CHANNEL_ISA_SERIAL,
|
||||
GA_CHANNEL_UNIX_LISTEN,
|
||||
GA_CHANNEL_VSOCK_LISTEN,
|
||||
} GAChannelMethod;
|
||||
|
||||
typedef gboolean (*GAChannelCallback)(GIOCondition condition, gpointer opaque);
|
||||
|
||||
GAChannel *ga_channel_new(GAChannelMethod method, const gchar *path,
|
||||
int listen_fd, GAChannelCallback cb,
|
||||
gpointer opaque);
|
||||
void ga_channel_free(GAChannel *c);
|
||||
GIOStatus ga_channel_read(GAChannel *c, gchar *buf, gsize size, gsize *count);
|
||||
GIOStatus ga_channel_write_all(GAChannel *c, const gchar *buf, gsize size);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* QEMU Guest Agent BSD-specific command implementations
|
||||
*
|
||||
* Copyright (c) Virtuozzo International GmbH.
|
||||
*
|
||||
* Authors:
|
||||
* Alexander Ivanov <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qga-qapi-commands.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/queue.h"
|
||||
#include "commands-common.h"
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/ucred.h>
|
||||
#include <sys/mount.h>
|
||||
#include <net/if_dl.h>
|
||||
#if defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
#include <net/if_arp.h>
|
||||
#include <netinet/if_ether.h>
|
||||
#else
|
||||
#include <net/ethernet.h>
|
||||
#endif
|
||||
#include <paths.h>
|
||||
|
||||
#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
|
||||
bool build_fs_mount_list(FsMountList *mounts, Error **errp)
|
||||
{
|
||||
FsMount *mount;
|
||||
struct statfs *mntbuf, *mntp;
|
||||
struct stat statbuf;
|
||||
int i, count, ret;
|
||||
|
||||
count = getmntinfo(&mntbuf, MNT_NOWAIT);
|
||||
if (count == 0) {
|
||||
error_setg_errno(errp, errno, "getmntinfo failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
mntp = &mntbuf[i];
|
||||
ret = stat(mntp->f_mntonname, &statbuf);
|
||||
if (ret != 0) {
|
||||
error_setg_errno(errp, errno, "stat failed on %s",
|
||||
mntp->f_mntonname);
|
||||
return false;
|
||||
}
|
||||
|
||||
mount = g_new0(FsMount, 1);
|
||||
|
||||
mount->dirname = g_strdup(mntp->f_mntonname);
|
||||
mount->devtype = g_strdup(mntp->f_fstypename);
|
||||
mount->devmajor = major(mount->dev);
|
||||
mount->devminor = minor(mount->dev);
|
||||
mount->fsid = mntp->f_fsid;
|
||||
mount->dev = statbuf.st_dev;
|
||||
|
||||
QTAILQ_INSERT_TAIL(mounts, mount, next);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif /* CONFIG_FSFREEZE || CONFIG_FSTRIM */
|
||||
|
||||
#if defined(CONFIG_FSFREEZE)
|
||||
static int ufssuspend_fd = -1;
|
||||
static int ufssuspend_cnt;
|
||||
|
||||
int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints,
|
||||
strList *mountpoints,
|
||||
FsMountList mounts,
|
||||
Error **errp)
|
||||
{
|
||||
int ret;
|
||||
strList *list;
|
||||
struct FsMount *mount;
|
||||
|
||||
if (ufssuspend_fd != -1) {
|
||||
error_setg(errp, "filesystems have already frozen");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ufssuspend_cnt = 0;
|
||||
ufssuspend_fd = qemu_open(_PATH_UFSSUSPEND, O_RDWR, errp);
|
||||
if (ufssuspend_fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
QTAILQ_FOREACH_REVERSE(mount, &mounts, next) {
|
||||
/*
|
||||
* To issue fsfreeze in the reverse order of mounts, check if the
|
||||
* mount is listed in the list here
|
||||
*/
|
||||
if (has_mountpoints) {
|
||||
for (list = mountpoints; list; list = list->next) {
|
||||
if (g_str_equal(list->value, mount->dirname)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!list) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Only UFS supports suspend */
|
||||
if (!g_str_equal(mount->devtype, "ufs")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = ioctl(ufssuspend_fd, UFSSUSPEND, &mount->fsid);
|
||||
if (ret == -1) {
|
||||
/*
|
||||
* ioctl returns EBUSY for all the FS except the first one
|
||||
* that was suspended
|
||||
*/
|
||||
if (errno == EBUSY) {
|
||||
continue;
|
||||
}
|
||||
error_setg_errno(errp, errno, "failed to freeze %s",
|
||||
mount->dirname);
|
||||
goto error;
|
||||
}
|
||||
ufssuspend_cnt++;
|
||||
}
|
||||
return ufssuspend_cnt;
|
||||
error:
|
||||
close(ufssuspend_fd);
|
||||
ufssuspend_fd = -1;
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't need to call UFSRESUME ioctl because all the frozen FS
|
||||
* are thawed on /dev/ufssuspend closing.
|
||||
*/
|
||||
int qmp_guest_fsfreeze_do_thaw(Error **errp)
|
||||
{
|
||||
int ret = ufssuspend_cnt;
|
||||
ufssuspend_cnt = 0;
|
||||
if (ufssuspend_fd != -1) {
|
||||
close(ufssuspend_fd);
|
||||
ufssuspend_fd = -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_FSFREEZE */
|
||||
|
||||
#ifdef HAVE_GETIFADDRS
|
||||
/*
|
||||
* Fill "buf" with MAC address by ifaddrs. Pointer buf must point to a
|
||||
* buffer with ETHER_ADDR_LEN length at least.
|
||||
*
|
||||
* Returns false in case of an error, otherwise true. "obtained" arguument
|
||||
* is true if a MAC address was obtained successful, otherwise false.
|
||||
*/
|
||||
bool guest_get_hw_addr(struct ifaddrs *ifa, unsigned char *buf,
|
||||
bool *obtained, Error **errp)
|
||||
{
|
||||
struct sockaddr_dl *sdp;
|
||||
|
||||
*obtained = false;
|
||||
|
||||
if (ifa->ifa_addr->sa_family != AF_LINK) {
|
||||
/* We can get HW address only for AF_LINK family. */
|
||||
g_debug("failed to get MAC address of %s", ifa->ifa_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
sdp = (struct sockaddr_dl *)ifa->ifa_addr;
|
||||
memcpy(buf, sdp->sdl_data + sdp->sdl_nlen, ETHER_ADDR_LEN);
|
||||
*obtained = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif /* HAVE_GETIFADDRS */
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "commands-common-ssh.h"
|
||||
|
||||
GStrv read_authkeys(const char *path, Error **errp)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autofree char *contents = NULL;
|
||||
|
||||
if (!g_file_get_contents(path, &contents, NULL, &err)) {
|
||||
error_setg(errp, "failed to read '%s': %s", path, err->message);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_strsplit(contents, "\n", -1);
|
||||
}
|
||||
|
||||
bool check_openssh_pub_keys(strList *keys, size_t *nkeys, Error **errp)
|
||||
{
|
||||
size_t n = 0;
|
||||
strList *k;
|
||||
|
||||
for (k = keys; k != NULL; k = k->next) {
|
||||
if (!check_openssh_pub_key(k->value, errp)) {
|
||||
return false;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
|
||||
if (nkeys) {
|
||||
*nkeys = n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_openssh_pub_key(const char *key, Error **errp)
|
||||
{
|
||||
/* simple sanity-check, we may want more? */
|
||||
if (!key || key[0] == '#' || strchr(key, '\n')) {
|
||||
error_setg(errp, "invalid OpenSSH public key: '%s'", key);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qapi/qapi-builtin-types.h"
|
||||
|
||||
GStrv read_authkeys(const char *path, Error **errp);
|
||||
bool check_openssh_pub_keys(strList *keys, size_t *nkeys, Error **errp);
|
||||
bool check_openssh_pub_key(const char *key, Error **errp);
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* QEMU Guest Agent common/cross-platform common commands
|
||||
*
|
||||
* Copyright (c) 2020 Red Hat, Inc.
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
#ifndef QGA_COMMANDS_COMMON_H
|
||||
#define QGA_COMMANDS_COMMON_H
|
||||
|
||||
#include "qga-qapi-types.h"
|
||||
#include "guest-agent-core.h"
|
||||
#include "qemu/queue.h"
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <linux/fs.h>
|
||||
#endif /* __linux__ */
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#include <ufs/ffs/fs.h>
|
||||
#endif /* __FreeBSD__ */
|
||||
|
||||
#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
|
||||
typedef struct FsMount {
|
||||
char *dirname;
|
||||
char *devtype;
|
||||
unsigned int devmajor, devminor;
|
||||
#if defined(__FreeBSD__)
|
||||
dev_t dev;
|
||||
fsid_t fsid;
|
||||
#endif
|
||||
QTAILQ_ENTRY(FsMount) next;
|
||||
} FsMount;
|
||||
|
||||
typedef QTAILQ_HEAD(FsMountList, FsMount) FsMountList;
|
||||
|
||||
bool build_fs_mount_list(FsMountList *mounts, Error **errp);
|
||||
void free_fs_mount_list(FsMountList *mounts);
|
||||
#endif /* CONFIG_FSFREEZE || CONFIG_FSTRIM */
|
||||
|
||||
#if defined(CONFIG_FSFREEZE)
|
||||
int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints,
|
||||
strList *mountpoints,
|
||||
FsMountList mounts,
|
||||
Error **errp);
|
||||
int qmp_guest_fsfreeze_do_thaw(Error **errp);
|
||||
#endif /* CONFIG_FSFREEZE */
|
||||
|
||||
#ifdef HAVE_GETIFADDRS
|
||||
#include <ifaddrs.h>
|
||||
bool guest_get_hw_addr(struct ifaddrs *ifa, unsigned char *buf,
|
||||
bool *obtained, Error **errp);
|
||||
#endif
|
||||
|
||||
typedef struct GuestFileHandle GuestFileHandle;
|
||||
|
||||
GuestFileHandle *guest_file_handle_find(int64_t id, Error **errp);
|
||||
|
||||
GuestFileRead *guest_file_read_unsafe(GuestFileHandle *gfh,
|
||||
int64_t count, Error **errp);
|
||||
|
||||
/**
|
||||
* qga_get_host_name:
|
||||
* @errp: Error object
|
||||
*
|
||||
* Operating system agnostic way of querying host name.
|
||||
* Compared to g_get_host_name(), it doesn't cache the result.
|
||||
*
|
||||
* Returns allocated hostname (caller should free), NULL on failure.
|
||||
*/
|
||||
char *qga_get_host_name(Error **errp);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,444 @@
|
||||
/*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include <glib-unix.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <locale.h>
|
||||
#include <pwd.h>
|
||||
|
||||
#include "commands-common-ssh.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qga-qapi-commands.h"
|
||||
|
||||
#ifdef QGA_BUILD_UNIT_TEST
|
||||
static struct passwd *
|
||||
test_get_passwd_entry(const gchar *user_name, GError **error)
|
||||
{
|
||||
struct passwd *p;
|
||||
int ret;
|
||||
|
||||
if (!user_name || g_strcmp0(user_name, g_get_user_name())) {
|
||||
g_set_error(error, G_UNIX_ERROR, 0, "Invalid user name");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
p = g_new0(struct passwd, 1);
|
||||
p->pw_dir = (char *)g_get_home_dir();
|
||||
p->pw_uid = geteuid();
|
||||
p->pw_gid = getegid();
|
||||
|
||||
ret = g_mkdir_with_parents(p->pw_dir, 0700);
|
||||
g_assert(ret == 0);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
#define g_unix_get_passwd_entry(username, err) \
|
||||
test_get_passwd_entry(username, err)
|
||||
#endif
|
||||
|
||||
static struct passwd *
|
||||
get_passwd_entry(const char *username, Error **errp)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
struct passwd *p;
|
||||
|
||||
p = g_unix_get_passwd_entry(username, &err);
|
||||
if (p == NULL) {
|
||||
error_setg(errp, "failed to lookup user '%s': %s",
|
||||
username, err->message);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static bool
|
||||
mkdir_for_user(const char *path, const struct passwd *p,
|
||||
mode_t mode, Error **errp)
|
||||
{
|
||||
if (g_mkdir(path, mode) == -1) {
|
||||
error_setg_errno(errp, errno, "failed to create directory '%s'",
|
||||
path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chown(path, p->pw_uid, p->pw_gid) == -1) {
|
||||
error_setg_errno(errp, errno,
|
||||
"failed to set ownership of directory '%s'",
|
||||
path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chmod(path, mode) == -1) {
|
||||
error_setg_errno(errp, errno,
|
||||
"failed to set permissions of directory '%s'",
|
||||
path);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
write_authkeys(const char *path, const GStrv keys,
|
||||
const struct passwd *p, Error **errp)
|
||||
{
|
||||
g_autofree char *contents = NULL;
|
||||
g_autoptr(GError) err = NULL;
|
||||
|
||||
contents = g_strjoinv("\n", keys);
|
||||
if (!g_file_set_contents(path, contents, -1, &err)) {
|
||||
error_setg(errp, "failed to write to '%s': %s", path, err->message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chown(path, p->pw_uid, p->pw_gid) == -1) {
|
||||
error_setg_errno(errp, errno,
|
||||
"failed to set ownership of directory '%s'",
|
||||
path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chmod(path, 0600) == -1) {
|
||||
error_setg_errno(errp, errno, "failed to set permissions of '%s'",
|
||||
path);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
qmp_guest_ssh_add_authorized_keys(const char *username, strList *keys,
|
||||
bool has_reset, bool reset,
|
||||
Error **errp)
|
||||
{
|
||||
g_autofree struct passwd *p = NULL;
|
||||
g_autofree char *ssh_path = NULL;
|
||||
g_autofree char *authkeys_path = NULL;
|
||||
g_auto(GStrv) authkeys = NULL;
|
||||
strList *k;
|
||||
size_t nkeys, nauthkeys;
|
||||
|
||||
reset = has_reset && reset;
|
||||
|
||||
if (!check_openssh_pub_keys(keys, &nkeys, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
p = get_passwd_entry(username, errp);
|
||||
if (p == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ssh_path = g_build_filename(p->pw_dir, ".ssh", NULL);
|
||||
authkeys_path = g_build_filename(ssh_path, "authorized_keys", NULL);
|
||||
|
||||
if (!reset) {
|
||||
authkeys = read_authkeys(authkeys_path, NULL);
|
||||
}
|
||||
if (authkeys == NULL) {
|
||||
if (!g_file_test(ssh_path, G_FILE_TEST_IS_DIR) &&
|
||||
!mkdir_for_user(ssh_path, p, 0700, errp)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nauthkeys = authkeys ? g_strv_length(authkeys) : 0;
|
||||
authkeys = g_realloc_n(authkeys, nauthkeys + nkeys + 1, sizeof(char *));
|
||||
memset(authkeys + nauthkeys, 0, (nkeys + 1) * sizeof(char *));
|
||||
|
||||
for (k = keys; k != NULL; k = k->next) {
|
||||
if (g_strv_contains((const gchar * const *)authkeys, k->value)) {
|
||||
continue;
|
||||
}
|
||||
authkeys[nauthkeys++] = g_strdup(k->value);
|
||||
}
|
||||
|
||||
write_authkeys(authkeys_path, authkeys, p, errp);
|
||||
}
|
||||
|
||||
void
|
||||
qmp_guest_ssh_remove_authorized_keys(const char *username, strList *keys,
|
||||
Error **errp)
|
||||
{
|
||||
g_autofree struct passwd *p = NULL;
|
||||
g_autofree char *authkeys_path = NULL;
|
||||
g_autofree GStrv new_keys = NULL; /* do not own the strings */
|
||||
g_auto(GStrv) authkeys = NULL;
|
||||
GStrv a;
|
||||
size_t nkeys = 0;
|
||||
|
||||
if (!check_openssh_pub_keys(keys, NULL, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
p = get_passwd_entry(username, errp);
|
||||
if (p == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
authkeys_path = g_build_filename(p->pw_dir, ".ssh",
|
||||
"authorized_keys", NULL);
|
||||
if (!g_file_test(authkeys_path, G_FILE_TEST_EXISTS)) {
|
||||
return;
|
||||
}
|
||||
authkeys = read_authkeys(authkeys_path, errp);
|
||||
if (authkeys == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
new_keys = g_new0(char *, g_strv_length(authkeys) + 1);
|
||||
for (a = authkeys; *a != NULL; a++) {
|
||||
strList *k;
|
||||
|
||||
for (k = keys; k != NULL; k = k->next) {
|
||||
if (g_str_equal(k->value, *a)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (k != NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
new_keys[nkeys++] = *a;
|
||||
}
|
||||
|
||||
write_authkeys(authkeys_path, new_keys, p, errp);
|
||||
}
|
||||
|
||||
GuestAuthorizedKeys *
|
||||
qmp_guest_ssh_get_authorized_keys(const char *username, Error **errp)
|
||||
{
|
||||
g_autofree struct passwd *p = NULL;
|
||||
g_autofree char *authkeys_path = NULL;
|
||||
g_auto(GStrv) authkeys = NULL;
|
||||
g_autoptr(GuestAuthorizedKeys) ret = NULL;
|
||||
int i;
|
||||
|
||||
p = get_passwd_entry(username, errp);
|
||||
if (p == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
authkeys_path = g_build_filename(p->pw_dir, ".ssh",
|
||||
"authorized_keys", NULL);
|
||||
authkeys = read_authkeys(authkeys_path, errp);
|
||||
if (authkeys == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = g_new0(GuestAuthorizedKeys, 1);
|
||||
for (i = 0; authkeys[i] != NULL; i++) {
|
||||
g_strstrip(authkeys[i]);
|
||||
if (!authkeys[i][0] || authkeys[i][0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
QAPI_LIST_PREPEND(ret->keys, g_strdup(authkeys[i]));
|
||||
}
|
||||
|
||||
return g_steal_pointer(&ret);
|
||||
}
|
||||
|
||||
#ifdef QGA_BUILD_UNIT_TEST
|
||||
static const strList test_key2 = {
|
||||
.value = (char *)"algo key2 comments"
|
||||
};
|
||||
|
||||
static const strList test_key1_2 = {
|
||||
.value = (char *)"algo key1 comments",
|
||||
.next = (strList *)&test_key2,
|
||||
};
|
||||
|
||||
static char *
|
||||
test_get_authorized_keys_path(void)
|
||||
{
|
||||
return g_build_filename(g_get_home_dir(), ".ssh", "authorized_keys", NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_authorized_keys_set(const char *contents)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autofree char *path = NULL;
|
||||
int ret;
|
||||
|
||||
path = g_build_filename(g_get_home_dir(), ".ssh", NULL);
|
||||
ret = g_mkdir_with_parents(path, 0700);
|
||||
g_assert(ret == 0);
|
||||
g_free(path);
|
||||
|
||||
path = test_get_authorized_keys_path();
|
||||
g_file_set_contents(path, contents, -1, &err);
|
||||
g_assert(err == NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_authorized_keys_equal(const char *expected)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autofree char *path = NULL;
|
||||
g_autofree char *contents = NULL;
|
||||
|
||||
path = test_get_authorized_keys_path();
|
||||
g_file_get_contents(path, &contents, NULL, &err);
|
||||
g_assert(err == NULL);
|
||||
|
||||
g_assert(g_strcmp0(contents, expected) == 0);
|
||||
}
|
||||
|
||||
static void
|
||||
test_invalid_user(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
|
||||
qmp_guest_ssh_add_authorized_keys("", NULL, FALSE, FALSE, &err);
|
||||
error_free_or_abort(&err);
|
||||
|
||||
qmp_guest_ssh_remove_authorized_keys("", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
}
|
||||
|
||||
static void
|
||||
test_invalid_key(void)
|
||||
{
|
||||
strList key = {
|
||||
.value = (char *)"not a valid\nkey"
|
||||
};
|
||||
Error *err = NULL;
|
||||
|
||||
qmp_guest_ssh_add_authorized_keys(g_get_user_name(), &key,
|
||||
FALSE, FALSE, &err);
|
||||
error_free_or_abort(&err);
|
||||
|
||||
qmp_guest_ssh_remove_authorized_keys(g_get_user_name(), &key, &err);
|
||||
error_free_or_abort(&err);
|
||||
}
|
||||
|
||||
static void
|
||||
test_add_keys(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
|
||||
qmp_guest_ssh_add_authorized_keys(g_get_user_name(),
|
||||
(strList *)&test_key2,
|
||||
FALSE, FALSE,
|
||||
&err);
|
||||
g_assert(err == NULL);
|
||||
|
||||
test_authorized_keys_equal("algo key2 comments");
|
||||
|
||||
qmp_guest_ssh_add_authorized_keys(g_get_user_name(),
|
||||
(strList *)&test_key1_2,
|
||||
FALSE, FALSE,
|
||||
&err);
|
||||
g_assert(err == NULL);
|
||||
|
||||
/* key2 came first, and shouldn't be duplicated */
|
||||
test_authorized_keys_equal("algo key2 comments\n"
|
||||
"algo key1 comments");
|
||||
}
|
||||
|
||||
static void
|
||||
test_add_reset_keys(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
|
||||
qmp_guest_ssh_add_authorized_keys(g_get_user_name(),
|
||||
(strList *)&test_key1_2,
|
||||
FALSE, FALSE,
|
||||
&err);
|
||||
g_assert(err == NULL);
|
||||
|
||||
/* reset with key2 only */
|
||||
test_authorized_keys_equal("algo key1 comments\n"
|
||||
"algo key2 comments");
|
||||
|
||||
qmp_guest_ssh_add_authorized_keys(g_get_user_name(),
|
||||
(strList *)&test_key2,
|
||||
TRUE, TRUE,
|
||||
&err);
|
||||
g_assert(err == NULL);
|
||||
|
||||
test_authorized_keys_equal("algo key2 comments");
|
||||
|
||||
/* empty should clear file */
|
||||
qmp_guest_ssh_add_authorized_keys(g_get_user_name(),
|
||||
(strList *)NULL,
|
||||
TRUE, TRUE,
|
||||
&err);
|
||||
g_assert(err == NULL);
|
||||
|
||||
test_authorized_keys_equal("");
|
||||
}
|
||||
|
||||
static void
|
||||
test_remove_keys(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
static const char *authkeys =
|
||||
"algo key1 comments\n"
|
||||
/* originally duplicated */
|
||||
"algo key1 comments\n"
|
||||
"# a commented line\n"
|
||||
"algo some-key another\n";
|
||||
|
||||
test_authorized_keys_set(authkeys);
|
||||
qmp_guest_ssh_remove_authorized_keys(g_get_user_name(),
|
||||
(strList *)&test_key2, &err);
|
||||
g_assert(err == NULL);
|
||||
test_authorized_keys_equal(authkeys);
|
||||
|
||||
qmp_guest_ssh_remove_authorized_keys(g_get_user_name(),
|
||||
(strList *)&test_key1_2, &err);
|
||||
g_assert(err == NULL);
|
||||
test_authorized_keys_equal("# a commented line\n"
|
||||
"algo some-key another\n");
|
||||
}
|
||||
|
||||
static void
|
||||
test_get_keys(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
static const char *authkeys =
|
||||
"algo key1 comments\n"
|
||||
"# a commented line\n"
|
||||
"algo some-key another\n";
|
||||
g_autoptr(GuestAuthorizedKeys) ret = NULL;
|
||||
strList *k;
|
||||
size_t len = 0;
|
||||
|
||||
test_authorized_keys_set(authkeys);
|
||||
|
||||
ret = qmp_guest_ssh_get_authorized_keys(g_get_user_name(), &err);
|
||||
g_assert(err == NULL);
|
||||
|
||||
for (len = 0, k = ret->keys; k != NULL; k = k->next) {
|
||||
g_assert(g_str_has_prefix(k->value, "algo "));
|
||||
len++;
|
||||
}
|
||||
|
||||
g_assert(len == 2);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
setlocale(LC_ALL, "");
|
||||
|
||||
g_test_init(&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
|
||||
|
||||
g_test_add_func("/qga/ssh/invalid_user", test_invalid_user);
|
||||
g_test_add_func("/qga/ssh/invalid_key", test_invalid_key);
|
||||
g_test_add_func("/qga/ssh/add_keys", test_add_keys);
|
||||
g_test_add_func("/qga/ssh/add_reset_keys", test_add_reset_keys);
|
||||
g_test_add_func("/qga/ssh/remove_keys", test_remove_keys);
|
||||
g_test_add_func("/qga/ssh/get_keys", test_get_keys);
|
||||
|
||||
return g_test_run();
|
||||
}
|
||||
#endif /* BUILD_UNIT_TEST */
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,712 @@
|
||||
/*
|
||||
* QEMU Guest Agent win32-specific command implementations for SSH keys.
|
||||
* The implementation is opinionated and expects the SSH implementation to
|
||||
* be OpenSSH.
|
||||
*
|
||||
* Copyright Schweitzer Engineering Laboratories. 2024
|
||||
*
|
||||
* Authors:
|
||||
* Aidan Leuck <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include <aclapi.h>
|
||||
#include <qga-qapi-types.h>
|
||||
|
||||
#include "commands-common-ssh.h"
|
||||
#include "commands-windows-ssh.h"
|
||||
#include "guest-agent-core.h"
|
||||
#include "limits.h"
|
||||
#include "lmaccess.h"
|
||||
#include "lmapibuf.h"
|
||||
#include "lmerr.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
#include "qga-qapi-commands.h"
|
||||
#include "sddl.h"
|
||||
#include "shlobj.h"
|
||||
#include "userenv.h"
|
||||
|
||||
#define AUTHORIZED_KEY_FILE "authorized_keys"
|
||||
#define AUTHORIZED_KEY_FILE_ADMIN "administrators_authorized_keys"
|
||||
#define LOCAL_SYSTEM_SID "S-1-5-18"
|
||||
#define ADMIN_SID "S-1-5-32-544"
|
||||
|
||||
/*
|
||||
* Frees userInfo structure. This implements the g_auto cleanup
|
||||
* for the structure.
|
||||
*/
|
||||
void free_userInfo(PWindowsUserInfo info)
|
||||
{
|
||||
g_free(info->sshDirectory);
|
||||
g_free(info->authorizedKeyFile);
|
||||
LocalFree(info->SSID);
|
||||
g_free(info->username);
|
||||
g_free(info);
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the admin SSH folder for OpenSSH. OpenSSH does not store
|
||||
* the authorized_key file in the users home directory for security reasons and
|
||||
* instead stores it at %PROGRAMDATA%/ssh. This function returns the path to
|
||||
* that directory on the users machine
|
||||
*
|
||||
* parameters:
|
||||
* errp -> error structure to set when an error occurs
|
||||
* returns: The path to the ssh folder in %PROGRAMDATA% or NULL if an error
|
||||
* occurred.
|
||||
*/
|
||||
static char *get_admin_ssh_folder(Error **errp)
|
||||
{
|
||||
/* Allocate memory for the program data path */
|
||||
g_autofree char *programDataPath = NULL;
|
||||
char *authkeys_path = NULL;
|
||||
PWSTR pgDataW = NULL;
|
||||
g_autoptr(GError) gerr = NULL;
|
||||
|
||||
/* Get the KnownFolderPath on the machine. */
|
||||
HRESULT folderResult =
|
||||
SHGetKnownFolderPath(&FOLDERID_ProgramData, 0, NULL, &pgDataW);
|
||||
if (folderResult != S_OK) {
|
||||
error_setg(errp, "Failed to retrieve ProgramData folder");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Convert from a wide string back to a standard character string. */
|
||||
programDataPath = g_utf16_to_utf8(pgDataW, -1, NULL, NULL, &gerr);
|
||||
CoTaskMemFree(pgDataW);
|
||||
if (!programDataPath) {
|
||||
error_setg(errp,
|
||||
"Failed converting ProgramData folder path to UTF-16 %s",
|
||||
gerr->message);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Build the path to the file. */
|
||||
authkeys_path = g_build_filename(programDataPath, "ssh", NULL);
|
||||
return authkeys_path;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the path to the SSH folder for the specified user. If the user is an
|
||||
* admin it returns the ssh folder located at %PROGRAMDATA%/ssh. If the user is
|
||||
* not an admin it returns %USERPROFILE%/.ssh
|
||||
*
|
||||
* parameters:
|
||||
* username -> Username to get the SSH folder for
|
||||
* isAdmin -> Whether the user is an admin or not
|
||||
* errp -> Error structure to set any errors that occur.
|
||||
* returns: path to the ssh folder as a string.
|
||||
*/
|
||||
static char *get_ssh_folder(const char *username, const bool isAdmin,
|
||||
Error **errp)
|
||||
{
|
||||
DWORD maxSize = MAX_PATH;
|
||||
g_autofree char *profilesDir = g_new0(char, maxSize);
|
||||
|
||||
if (isAdmin) {
|
||||
return get_admin_ssh_folder(errp);
|
||||
}
|
||||
|
||||
/* If not an Admin the SSH key is in the user directory. */
|
||||
/* Get the user profile directory on the machine. */
|
||||
BOOL ret = GetProfilesDirectory(profilesDir, &maxSize);
|
||||
if (!ret) {
|
||||
error_setg_win32(errp, GetLastError(),
|
||||
"failed to retrieve profiles directory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Builds the filename */
|
||||
return g_build_filename(profilesDir, username, ".ssh", NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates an entry for the user so they can access the ssh folder in their
|
||||
* userprofile.
|
||||
*
|
||||
* parameters:
|
||||
* userInfo -> Information about the current user
|
||||
* pACL -> Pointer to an ACL structure
|
||||
* errp -> Error structure to set any errors that occur
|
||||
* returns -> 1 on success, 0 otherwise
|
||||
*/
|
||||
static bool create_acl_user(PWindowsUserInfo userInfo, PACL *pACL, Error **errp)
|
||||
{
|
||||
const int aclSize = 1;
|
||||
PACL newACL = NULL;
|
||||
EXPLICIT_ACCESS eAccess[1];
|
||||
PSID userPSID = NULL;
|
||||
|
||||
/* Get a pointer to the internal SID object in Windows */
|
||||
bool converted = ConvertStringSidToSid(userInfo->SSID, &userPSID);
|
||||
if (!converted) {
|
||||
error_setg_win32(errp, GetLastError(), "failed to retrieve user %s SID",
|
||||
userInfo->username);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Set the permissions for the user. */
|
||||
eAccess[0].grfAccessPermissions = GENERIC_ALL;
|
||||
eAccess[0].grfAccessMode = SET_ACCESS;
|
||||
eAccess[0].grfInheritance = NO_INHERITANCE;
|
||||
eAccess[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
eAccess[0].Trustee.TrusteeType = TRUSTEE_IS_USER;
|
||||
eAccess[0].Trustee.ptstrName = (LPTSTR)userPSID;
|
||||
|
||||
/* Set the ACL entries */
|
||||
DWORD setResult;
|
||||
|
||||
/*
|
||||
* If we are given a pointer that is already initialized, then we can merge
|
||||
* the existing entries instead of overwriting them.
|
||||
*/
|
||||
if (*pACL) {
|
||||
setResult = SetEntriesInAcl(aclSize, eAccess, *pACL, &newACL);
|
||||
} else {
|
||||
setResult = SetEntriesInAcl(aclSize, eAccess, NULL, &newACL);
|
||||
}
|
||||
|
||||
if (setResult != ERROR_SUCCESS) {
|
||||
error_setg_win32(errp, GetLastError(),
|
||||
"failed to set ACL entries for user %s %lu",
|
||||
userInfo->username, setResult);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Free any old memory since we are going to overwrite the users pointer. */
|
||||
LocalFree(*pACL);
|
||||
*pACL = newACL;
|
||||
|
||||
LocalFree(userPSID);
|
||||
return true;
|
||||
error:
|
||||
LocalFree(userPSID);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a base ACL for both normal users and admins to share
|
||||
* pACL -> Pointer to an ACL structure
|
||||
* errp -> Error structure to set any errors that occur
|
||||
* returns: 1 on success, 0 otherwise
|
||||
*/
|
||||
static bool create_acl_base(PACL *pACL, Error **errp)
|
||||
{
|
||||
PSID adminGroupPSID = NULL;
|
||||
PSID systemPSID = NULL;
|
||||
|
||||
const int aclSize = 2;
|
||||
EXPLICIT_ACCESS eAccess[2];
|
||||
|
||||
/* Create an entry for the system user. */
|
||||
const char *systemSID = LOCAL_SYSTEM_SID;
|
||||
bool converted = ConvertStringSidToSid(systemSID, &systemPSID);
|
||||
if (!converted) {
|
||||
error_setg_win32(errp, GetLastError(), "failed to retrieve system SID");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* set permissions for system user */
|
||||
eAccess[0].grfAccessPermissions = GENERIC_ALL;
|
||||
eAccess[0].grfAccessMode = SET_ACCESS;
|
||||
eAccess[0].grfInheritance = NO_INHERITANCE;
|
||||
eAccess[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
eAccess[0].Trustee.TrusteeType = TRUSTEE_IS_USER;
|
||||
eAccess[0].Trustee.ptstrName = (LPTSTR)systemPSID;
|
||||
|
||||
/* Create an entry for the admin user. */
|
||||
const char *adminSID = ADMIN_SID;
|
||||
converted = ConvertStringSidToSid(adminSID, &adminGroupPSID);
|
||||
if (!converted) {
|
||||
error_setg_win32(errp, GetLastError(), "failed to retrieve Admin SID");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Set permissions for admin group. */
|
||||
eAccess[1].grfAccessPermissions = GENERIC_ALL;
|
||||
eAccess[1].grfAccessMode = SET_ACCESS;
|
||||
eAccess[1].grfInheritance = NO_INHERITANCE;
|
||||
eAccess[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
eAccess[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
|
||||
eAccess[1].Trustee.ptstrName = (LPTSTR)adminGroupPSID;
|
||||
|
||||
/* Put the entries in an ACL object. */
|
||||
PACL pNewACL = NULL;
|
||||
DWORD setResult;
|
||||
|
||||
/*
|
||||
*If we are given a pointer that is already initialized, then we can merge
|
||||
*the existing entries instead of overwriting them.
|
||||
*/
|
||||
if (*pACL) {
|
||||
setResult = SetEntriesInAcl(aclSize, eAccess, *pACL, &pNewACL);
|
||||
} else {
|
||||
setResult = SetEntriesInAcl(aclSize, eAccess, NULL, &pNewACL);
|
||||
}
|
||||
|
||||
if (setResult != ERROR_SUCCESS) {
|
||||
error_setg_win32(errp, GetLastError(),
|
||||
"failed to set base ACL entries for system user and "
|
||||
"admin group %lu",
|
||||
setResult);
|
||||
goto error;
|
||||
}
|
||||
|
||||
LocalFree(adminGroupPSID);
|
||||
LocalFree(systemPSID);
|
||||
|
||||
/* Free any old memory since we are going to overwrite the users pointer. */
|
||||
LocalFree(*pACL);
|
||||
|
||||
*pACL = pNewACL;
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
LocalFree(adminGroupPSID);
|
||||
LocalFree(systemPSID);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the access control on the authorized_keys file and any ssh folders that
|
||||
* need to be created. For administrators the required permissions on the
|
||||
* file/folders are that only administrators and the LocalSystem account can
|
||||
* access the folders. For normal user accounts only the specified user,
|
||||
* LocalSystem and Administrators can have access to the key.
|
||||
*
|
||||
* parameters:
|
||||
* userInfo -> pointer to structure that contains information about the user
|
||||
* PACL -> pointer to an access control structure that will be set upon
|
||||
* successful completion of the function.
|
||||
* errp -> error structure that will be set upon error.
|
||||
* returns: 1 upon success 0 upon failure.
|
||||
*/
|
||||
static bool create_acl(PWindowsUserInfo userInfo, PACL *pACL, Error **errp)
|
||||
{
|
||||
/*
|
||||
* Creates a base ACL that both admins and users will share
|
||||
* This adds the Administrators group and the SYSTEM group
|
||||
*/
|
||||
if (!create_acl_base(pACL, errp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the user is not an admin give the user creating the key permission to
|
||||
* access the file.
|
||||
*/
|
||||
if (!userInfo->isAdmin) {
|
||||
if (!create_acl_user(userInfo, pACL, errp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
* Create the SSH directory for the user and d sets appropriate permissions.
|
||||
* In general the directory will be %PROGRAMDATA%/ssh if the user is an admin.
|
||||
* %USERPOFILE%/.ssh if not an admin
|
||||
*
|
||||
* parameters:
|
||||
* userInfo -> Contains information about the user
|
||||
* errp -> Structure that will contain errors if the function fails.
|
||||
* returns: zero upon failure, 1 upon success
|
||||
*/
|
||||
static bool create_ssh_directory(WindowsUserInfo *userInfo, Error **errp)
|
||||
{
|
||||
PACL pNewACL = NULL;
|
||||
g_autofree PSECURITY_DESCRIPTOR pSD = NULL;
|
||||
|
||||
/* Gets the appropriate ACL for the user */
|
||||
if (!create_acl(userInfo, &pNewACL, errp)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Allocate memory for a security descriptor */
|
||||
pSD = g_malloc(SECURITY_DESCRIPTOR_MIN_LENGTH);
|
||||
if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) {
|
||||
error_setg_win32(errp, GetLastError(),
|
||||
"Failed to initialize security descriptor");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Associate the security descriptor with the ACL permissions. */
|
||||
if (!SetSecurityDescriptorDacl(pSD, TRUE, pNewACL, FALSE)) {
|
||||
error_setg_win32(errp, GetLastError(),
|
||||
"Failed to set security descriptor ACL");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Set the security attributes on the folder */
|
||||
SECURITY_ATTRIBUTES sAttr;
|
||||
sAttr.bInheritHandle = FALSE;
|
||||
sAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sAttr.lpSecurityDescriptor = pSD;
|
||||
|
||||
/* Create the directory with the created permissions */
|
||||
BOOL created = CreateDirectory(userInfo->sshDirectory, &sAttr);
|
||||
if (!created) {
|
||||
error_setg_win32(errp, GetLastError(), "failed to create directory %s",
|
||||
userInfo->sshDirectory);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Free memory */
|
||||
LocalFree(pNewACL);
|
||||
return true;
|
||||
error:
|
||||
LocalFree(pNewACL);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets permissions on the authorized_key_file that is created.
|
||||
*
|
||||
* parameters: userInfo -> Information about the user
|
||||
* errp -> error structure that will contain errors upon failure
|
||||
* returns: 1 upon success, zero upon failure.
|
||||
*/
|
||||
static bool set_file_permissions(PWindowsUserInfo userInfo, Error **errp)
|
||||
{
|
||||
PACL pACL = NULL;
|
||||
PSID userPSID = NULL;
|
||||
|
||||
/* Creates the access control structure */
|
||||
if (!create_acl(userInfo, &pACL, errp)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Get the PSID structure for the user based off the string SID. */
|
||||
bool converted = ConvertStringSidToSid(userInfo->SSID, &userPSID);
|
||||
if (!converted) {
|
||||
error_setg_win32(errp, GetLastError(), "failed to retrieve user %s SID",
|
||||
userInfo->username);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Prevents permissions from being inherited and use the DACL provided. */
|
||||
const SE_OBJECT_TYPE securityBitFlags =
|
||||
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION;
|
||||
|
||||
/* Set the ACL on the file. */
|
||||
if (SetNamedSecurityInfo(userInfo->authorizedKeyFile, SE_FILE_OBJECT,
|
||||
securityBitFlags, userPSID, NULL, pACL,
|
||||
NULL) != ERROR_SUCCESS) {
|
||||
error_setg_win32(errp, GetLastError(),
|
||||
"failed to set file security for file %s",
|
||||
userInfo->authorizedKeyFile);
|
||||
goto error;
|
||||
}
|
||||
|
||||
LocalFree(pACL);
|
||||
LocalFree(userPSID);
|
||||
return true;
|
||||
|
||||
error:
|
||||
LocalFree(pACL);
|
||||
LocalFree(userPSID);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Writes the specified keys to the authenticated keys file.
|
||||
* parameters:
|
||||
* userInfo: Information about the user we are writing the authkeys file to.
|
||||
* authkeys: Array of keys to write to disk
|
||||
* errp: Error structure that will contain any errors if they occur.
|
||||
* returns: 1 if successful, 0 otherwise.
|
||||
*/
|
||||
static bool write_authkeys(WindowsUserInfo *userInfo, GStrv authkeys,
|
||||
Error **errp)
|
||||
{
|
||||
g_autofree char *contents = NULL;
|
||||
g_autoptr(GError) err = NULL;
|
||||
|
||||
contents = g_strjoinv("\n", authkeys);
|
||||
|
||||
if (!g_file_set_contents(userInfo->authorizedKeyFile, contents, -1, &err)) {
|
||||
error_setg(errp, "failed to write to '%s': %s",
|
||||
userInfo->authorizedKeyFile, err->message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!set_file_permissions(userInfo, errp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieves information about a Windows user by their username
|
||||
*
|
||||
* parameters:
|
||||
* userInfo -> Double pointer to a WindowsUserInfo structure. Upon success, it
|
||||
* will be allocated with information about the user and need to be freed.
|
||||
* username -> Name of the user to lookup.
|
||||
* errp -> Contains any errors that occur.
|
||||
* returns: 1 upon success, 0 upon failure.
|
||||
*/
|
||||
static bool get_user_info(PWindowsUserInfo *userInfo, const char *username,
|
||||
Error **errp)
|
||||
{
|
||||
DWORD infoLevel = 4;
|
||||
LPUSER_INFO_4 uBuf = NULL;
|
||||
g_autofree wchar_t *wideUserName = NULL;
|
||||
g_autoptr(GError) gerr = NULL;
|
||||
PSID psid = NULL;
|
||||
|
||||
/*
|
||||
* Converts a string to a Windows wide string since the GetNetUserInfo
|
||||
* function requires it.
|
||||
*/
|
||||
wideUserName = g_utf8_to_utf16(username, -1, NULL, NULL, &gerr);
|
||||
if (!wideUserName) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* allocate data */
|
||||
PWindowsUserInfo uData = g_new0(WindowsUserInfo, 1);
|
||||
|
||||
/* Set pointer so it can be cleaned up by the callee, even upon error. */
|
||||
*userInfo = uData;
|
||||
|
||||
/* Find the information */
|
||||
NET_API_STATUS result =
|
||||
NetUserGetInfo(NULL, wideUserName, infoLevel, (LPBYTE *)&uBuf);
|
||||
if (result != NERR_Success) {
|
||||
/* Give a friendlier error message if the user was not found. */
|
||||
if (result == NERR_UserNotFound) {
|
||||
error_setg(errp, "User %s was not found", username);
|
||||
goto error;
|
||||
}
|
||||
|
||||
error_setg(errp,
|
||||
"Received unexpected error when asking for user info: Error "
|
||||
"Code %lu",
|
||||
result);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Get information from the buffer returned by NetUserGetInfo. */
|
||||
uData->username = g_strdup(username);
|
||||
uData->isAdmin = uBuf->usri4_priv == USER_PRIV_ADMIN;
|
||||
psid = uBuf->usri4_user_sid;
|
||||
|
||||
char *sidStr = NULL;
|
||||
|
||||
/*
|
||||
* We store the string representation of the SID not SID structure in
|
||||
* memory. Callees wanting to use the SID structure should call
|
||||
* ConvertStringSidToSID.
|
||||
*/
|
||||
if (!ConvertSidToStringSid(psid, &sidStr)) {
|
||||
error_setg_win32(errp, GetLastError(),
|
||||
"failed to get SID string for user %s", username);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Store the SSID */
|
||||
uData->SSID = sidStr;
|
||||
|
||||
/* Get the SSH folder for the user. */
|
||||
char *sshFolder = get_ssh_folder(username, uData->isAdmin, errp);
|
||||
if (sshFolder == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Get the authorized key file path */
|
||||
const char *authorizedKeyFile =
|
||||
uData->isAdmin ? AUTHORIZED_KEY_FILE_ADMIN : AUTHORIZED_KEY_FILE;
|
||||
char *authorizedKeyPath =
|
||||
g_build_filename(sshFolder, authorizedKeyFile, NULL);
|
||||
uData->sshDirectory = sshFolder;
|
||||
uData->authorizedKeyFile = authorizedKeyPath;
|
||||
|
||||
/* Free */
|
||||
NetApiBufferFree(uBuf);
|
||||
return true;
|
||||
error:
|
||||
if (uBuf) {
|
||||
NetApiBufferFree(uBuf);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the list of authorized keys for a user.
|
||||
*
|
||||
* parameters:
|
||||
* username -> Username to retrieve the keys for.
|
||||
* errp -> Error structure that will display any errors through QMP.
|
||||
* returns: List of keys associated with the user.
|
||||
*/
|
||||
GuestAuthorizedKeys *qmp_guest_ssh_get_authorized_keys(const char *username,
|
||||
Error **errp)
|
||||
{
|
||||
GuestAuthorizedKeys *keys = NULL;
|
||||
g_auto(GStrv) authKeys = NULL;
|
||||
g_autoptr(GuestAuthorizedKeys) ret = NULL;
|
||||
g_auto(PWindowsUserInfo) userInfo = NULL;
|
||||
|
||||
/* Gets user information */
|
||||
if (!get_user_info(&userInfo, username, errp)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Reads authkeys for the user */
|
||||
authKeys = read_authkeys(userInfo->authorizedKeyFile, errp);
|
||||
if (authKeys == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Set the GuestAuthorizedKey struct with keys from the file */
|
||||
ret = g_new0(GuestAuthorizedKeys, 1);
|
||||
for (int i = 0; authKeys[i] != NULL; i++) {
|
||||
g_strstrip(authKeys[i]);
|
||||
if (!authKeys[i][0] || authKeys[i][0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
QAPI_LIST_PREPEND(ret->keys, g_strdup(authKeys[i]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Steal the pointer because it is up for the callee to deallocate the
|
||||
* memory.
|
||||
*/
|
||||
keys = g_steal_pointer(&ret);
|
||||
return keys;
|
||||
}
|
||||
|
||||
/*
|
||||
* Adds an ssh key for a user.
|
||||
*
|
||||
* parameters:
|
||||
* username -> User to add the SSH key to
|
||||
* strList -> Array of keys to add to the list
|
||||
* has_reset -> Whether the keys have been reset
|
||||
* reset -> Boolean to reset the keys (If this is set the existing list will be
|
||||
* cleared) and the other key reset. errp -> Pointer to an error structure that
|
||||
* will get returned over QMP if anything goes wrong.
|
||||
*/
|
||||
void qmp_guest_ssh_add_authorized_keys(const char *username, strList *keys,
|
||||
bool has_reset, bool reset, Error **errp)
|
||||
{
|
||||
g_auto(PWindowsUserInfo) userInfo = NULL;
|
||||
g_auto(GStrv) authkeys = NULL;
|
||||
strList *k;
|
||||
size_t nkeys, nauthkeys;
|
||||
|
||||
/* Make sure the keys given are valid */
|
||||
if (!check_openssh_pub_keys(keys, &nkeys, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Gets user information */
|
||||
if (!get_user_info(&userInfo, username, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Determine whether we should reset the keys */
|
||||
reset = has_reset && reset;
|
||||
if (!reset) {
|
||||
/* Read existing keys into memory */
|
||||
authkeys = read_authkeys(userInfo->authorizedKeyFile, NULL);
|
||||
}
|
||||
|
||||
/* Check that the SSH key directory exists for the user. */
|
||||
if (!g_file_test(userInfo->sshDirectory, G_FILE_TEST_IS_DIR)) {
|
||||
BOOL success = create_ssh_directory(userInfo, errp);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reallocates the buffer to fit the new keys. */
|
||||
nauthkeys = authkeys ? g_strv_length(authkeys) : 0;
|
||||
authkeys = g_realloc_n(authkeys, nauthkeys + nkeys + 1, sizeof(char *));
|
||||
|
||||
/* zero out the memory for the reallocated buffer */
|
||||
memset(authkeys + nauthkeys, 0, (nkeys + 1) * sizeof(char *));
|
||||
|
||||
/* Adds the keys */
|
||||
for (k = keys; k != NULL; k = k->next) {
|
||||
/* Check that the key doesn't already exist */
|
||||
if (g_strv_contains((const gchar *const *)authkeys, k->value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
authkeys[nauthkeys++] = g_strdup(k->value);
|
||||
}
|
||||
|
||||
/* Write the authkeys to the file. */
|
||||
write_authkeys(userInfo, authkeys, errp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes an SSH key for a user
|
||||
*
|
||||
* parameters:
|
||||
* username -> Username to remove the key from
|
||||
* strList -> List of strings to remove
|
||||
* errp -> Contains any errors that occur.
|
||||
*/
|
||||
void qmp_guest_ssh_remove_authorized_keys(const char *username, strList *keys,
|
||||
Error **errp)
|
||||
{
|
||||
g_auto(PWindowsUserInfo) userInfo = NULL;
|
||||
g_autofree struct passwd *p = NULL;
|
||||
g_autofree GStrv new_keys = NULL; /* do not own the strings */
|
||||
g_auto(GStrv) authkeys = NULL;
|
||||
GStrv a;
|
||||
size_t nkeys = 0;
|
||||
|
||||
/* Validates the keys passed in by the user */
|
||||
if (!check_openssh_pub_keys(keys, NULL, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Gets user information */
|
||||
if (!get_user_info(&userInfo, username, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Reads the authkeys for the user */
|
||||
authkeys = read_authkeys(userInfo->authorizedKeyFile, errp);
|
||||
if (authkeys == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create a new buffer to hold the keys */
|
||||
new_keys = g_new0(char *, g_strv_length(authkeys) + 1);
|
||||
for (a = authkeys; *a != NULL; a++) {
|
||||
strList *k;
|
||||
|
||||
/* Filters out keys that are equal to ones the user specified. */
|
||||
for (k = keys; k != NULL; k = k->next) {
|
||||
if (g_str_equal(k->value, *a)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (k != NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
new_keys[nkeys++] = *a;
|
||||
}
|
||||
|
||||
/* Write the new authkeys to the file. */
|
||||
write_authkeys(userInfo, new_keys, errp);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Header file for commands-windows-ssh.c
|
||||
*
|
||||
* Copyright Schweitzer Engineering Laboratories. 2024
|
||||
*
|
||||
* Authors:
|
||||
* Aidan Leuck <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include <glib/gstrfuncs.h>
|
||||
typedef struct WindowsUserInfo {
|
||||
char *sshDirectory;
|
||||
char *authorizedKeyFile;
|
||||
char *username;
|
||||
char *SSID;
|
||||
bool isAdmin;
|
||||
} WindowsUserInfo;
|
||||
|
||||
typedef WindowsUserInfo *PWindowsUserInfo;
|
||||
|
||||
void free_userInfo(PWindowsUserInfo info);
|
||||
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(PWindowsUserInfo, free_userInfo, NULL);
|
||||
+638
@@ -0,0 +1,638 @@
|
||||
/*
|
||||
* QEMU Guest Agent common/cross-platform command implementations
|
||||
*
|
||||
* Copyright IBM Corp. 2012
|
||||
*
|
||||
* Authors:
|
||||
* Michael Roth <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/units.h"
|
||||
#include "guest-agent-core.h"
|
||||
#include "qga-qapi-commands.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/base64.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "commands-common.h"
|
||||
|
||||
/* Maximum captured guest-exec out_data/err_data - 16MB */
|
||||
#define GUEST_EXEC_MAX_OUTPUT (16 * 1024 * 1024)
|
||||
/* Allocation and I/O buffer for reading guest-exec out_data/err_data - 4KB */
|
||||
#define GUEST_EXEC_IO_SIZE (4 * 1024)
|
||||
/*
|
||||
* Maximum file size to read - 48MB
|
||||
*
|
||||
* (48MB + Base64 3:4 overhead = JSON parser 64 MB limit)
|
||||
*/
|
||||
#define GUEST_FILE_READ_COUNT_MAX (48 * MiB)
|
||||
|
||||
/* Note: in some situations, like with the fsfreeze, logging may be
|
||||
* temporarily disabled. if it is necessary that a command be able
|
||||
* to log for accounting purposes, check ga_logging_enabled() beforehand.
|
||||
*/
|
||||
void slog(const gchar *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
int64_t qmp_guest_sync_delimited(int64_t id, Error **errp)
|
||||
{
|
||||
ga_set_response_delimited(ga_state);
|
||||
return id;
|
||||
}
|
||||
|
||||
int64_t qmp_guest_sync(int64_t id, Error **errp)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
void qmp_guest_ping(Error **errp)
|
||||
{
|
||||
slog("guest-ping called");
|
||||
}
|
||||
|
||||
static void qmp_command_info(const QmpCommand *cmd, void *opaque)
|
||||
{
|
||||
GuestAgentInfo *info = opaque;
|
||||
GuestAgentCommandInfo *cmd_info;
|
||||
|
||||
cmd_info = g_new0(GuestAgentCommandInfo, 1);
|
||||
cmd_info->name = g_strdup(qmp_command_name(cmd));
|
||||
cmd_info->enabled = qmp_command_is_enabled(cmd);
|
||||
cmd_info->success_response = qmp_has_success_response(cmd);
|
||||
|
||||
QAPI_LIST_PREPEND(info->supported_commands, cmd_info);
|
||||
}
|
||||
|
||||
struct GuestAgentInfo *qmp_guest_info(Error **errp)
|
||||
{
|
||||
GuestAgentInfo *info = g_new0(GuestAgentInfo, 1);
|
||||
|
||||
info->version = g_strdup(QEMU_VERSION);
|
||||
qmp_for_each_command(&ga_commands, qmp_command_info, info);
|
||||
return info;
|
||||
}
|
||||
|
||||
struct GuestExecIOData {
|
||||
guchar *data;
|
||||
gsize size;
|
||||
gsize length;
|
||||
bool closed;
|
||||
bool truncated;
|
||||
const char *name;
|
||||
};
|
||||
typedef struct GuestExecIOData GuestExecIOData;
|
||||
|
||||
struct GuestExecInfo {
|
||||
GPid pid;
|
||||
int64_t pid_numeric;
|
||||
gint status;
|
||||
bool has_output;
|
||||
bool finished;
|
||||
GuestExecIOData in;
|
||||
GuestExecIOData out;
|
||||
GuestExecIOData err;
|
||||
QTAILQ_ENTRY(GuestExecInfo) next;
|
||||
};
|
||||
typedef struct GuestExecInfo GuestExecInfo;
|
||||
|
||||
static struct {
|
||||
QTAILQ_HEAD(, GuestExecInfo) processes;
|
||||
} guest_exec_state = {
|
||||
.processes = QTAILQ_HEAD_INITIALIZER(guest_exec_state.processes),
|
||||
};
|
||||
|
||||
static int64_t gpid_to_int64(GPid pid)
|
||||
{
|
||||
#ifdef G_OS_WIN32
|
||||
return GetProcessId(pid);
|
||||
#else
|
||||
return (int64_t)pid;
|
||||
#endif
|
||||
}
|
||||
|
||||
static GuestExecInfo *guest_exec_info_add(GPid pid)
|
||||
{
|
||||
GuestExecInfo *gei;
|
||||
|
||||
gei = g_new0(GuestExecInfo, 1);
|
||||
gei->pid = pid;
|
||||
gei->pid_numeric = gpid_to_int64(pid);
|
||||
QTAILQ_INSERT_TAIL(&guest_exec_state.processes, gei, next);
|
||||
|
||||
return gei;
|
||||
}
|
||||
|
||||
static GuestExecInfo *guest_exec_info_find(int64_t pid_numeric)
|
||||
{
|
||||
GuestExecInfo *gei;
|
||||
|
||||
QTAILQ_FOREACH(gei, &guest_exec_state.processes, next) {
|
||||
if (gei->pid_numeric == pid_numeric) {
|
||||
return gei;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GuestExecStatus *qmp_guest_exec_status(int64_t pid, Error **errp)
|
||||
{
|
||||
GuestExecInfo *gei;
|
||||
GuestExecStatus *ges;
|
||||
|
||||
slog("guest-exec-status called, pid: %u", (uint32_t)pid);
|
||||
|
||||
gei = guest_exec_info_find(pid);
|
||||
if (gei == NULL) {
|
||||
error_setg(errp, "PID " PRId64 " does not exist");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ges = g_new0(GuestExecStatus, 1);
|
||||
|
||||
bool finished = gei->finished;
|
||||
|
||||
/* need to wait till output channels are closed
|
||||
* to be sure we captured all output at this point */
|
||||
if (gei->has_output) {
|
||||
finished &= gei->out.closed && gei->err.closed;
|
||||
}
|
||||
|
||||
ges->exited = finished;
|
||||
if (finished) {
|
||||
/* Glib has no portable way to parse exit status.
|
||||
* On UNIX, we can get either exit code from normal termination
|
||||
* or signal number.
|
||||
* On Windows, it is either the same exit code or the exception
|
||||
* value for an unhandled exception that caused the process
|
||||
* to terminate.
|
||||
* See MSDN for GetExitCodeProcess() and ntstatus.h for possible
|
||||
* well-known codes, e.g. C0000005 ACCESS_DENIED - analog of SIGSEGV
|
||||
* References:
|
||||
* https://msdn.microsoft.com/en-us/library/windows/desktop/ms683189(v=vs.85).aspx
|
||||
* https://msdn.microsoft.com/en-us/library/aa260331(v=vs.60).aspx
|
||||
*/
|
||||
#ifdef G_OS_WIN32
|
||||
/* Additionally WIN32 does not provide any additional information
|
||||
* on whether the child exited or terminated via signal.
|
||||
* We use this simple range check to distinguish application exit code
|
||||
* (usually value less then 256) and unhandled exception code with
|
||||
* ntstatus (always value greater then 0xC0000005). */
|
||||
if ((uint32_t)gei->status < 0xC0000000U) {
|
||||
ges->has_exitcode = true;
|
||||
ges->exitcode = gei->status;
|
||||
} else {
|
||||
ges->has_signal = true;
|
||||
ges->signal = gei->status;
|
||||
}
|
||||
#else
|
||||
if (WIFEXITED(gei->status)) {
|
||||
ges->has_exitcode = true;
|
||||
ges->exitcode = WEXITSTATUS(gei->status);
|
||||
} else if (WIFSIGNALED(gei->status)) {
|
||||
ges->has_signal = true;
|
||||
ges->signal = WTERMSIG(gei->status);
|
||||
}
|
||||
#endif
|
||||
if (gei->out.length > 0) {
|
||||
ges->out_data = g_base64_encode(gei->out.data, gei->out.length);
|
||||
ges->has_out_truncated = true;
|
||||
ges->out_truncated = gei->out.truncated;
|
||||
}
|
||||
g_free(gei->out.data);
|
||||
|
||||
if (gei->err.length > 0) {
|
||||
ges->err_data = g_base64_encode(gei->err.data, gei->err.length);
|
||||
ges->has_err_truncated = true;
|
||||
ges->err_truncated = gei->err.truncated;
|
||||
}
|
||||
g_free(gei->err.data);
|
||||
|
||||
QTAILQ_REMOVE(&guest_exec_state.processes, gei, next);
|
||||
g_free(gei);
|
||||
}
|
||||
|
||||
return ges;
|
||||
}
|
||||
|
||||
/* Get environment variables or arguments array for execve(). */
|
||||
static char **guest_exec_get_args(const strList *entry, bool log)
|
||||
{
|
||||
const strList *it;
|
||||
int count = 1, i = 0; /* reserve for NULL terminator */
|
||||
char **args;
|
||||
char *str; /* for logging array of arguments */
|
||||
size_t str_size = 1;
|
||||
|
||||
for (it = entry; it != NULL; it = it->next) {
|
||||
count++;
|
||||
str_size += 1 + strlen(it->value);
|
||||
}
|
||||
|
||||
str = g_malloc(str_size);
|
||||
*str = 0;
|
||||
args = g_new(char *, count);
|
||||
for (it = entry; it != NULL; it = it->next) {
|
||||
args[i++] = it->value;
|
||||
pstrcat(str, str_size, it->value);
|
||||
if (it->next) {
|
||||
pstrcat(str, str_size, " ");
|
||||
}
|
||||
}
|
||||
args[i] = NULL;
|
||||
|
||||
if (log) {
|
||||
slog("guest-exec called: \"%s\"", str);
|
||||
}
|
||||
g_free(str);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
static void guest_exec_child_watch(GPid pid, gint status, gpointer data)
|
||||
{
|
||||
GuestExecInfo *gei = (GuestExecInfo *)data;
|
||||
|
||||
g_debug("guest_exec_child_watch called, pid: %d, status: %u",
|
||||
(int32_t)gpid_to_int64(pid), (uint32_t)status);
|
||||
|
||||
gei->status = status;
|
||||
gei->finished = true;
|
||||
|
||||
g_spawn_close_pid(pid);
|
||||
}
|
||||
|
||||
static void guest_exec_task_setup(gpointer data)
|
||||
{
|
||||
#if !defined(G_OS_WIN32)
|
||||
bool has_merge = *(bool *)data;
|
||||
struct sigaction sigact;
|
||||
|
||||
if (has_merge) {
|
||||
/*
|
||||
* FIXME: When `GLIB_VERSION_MIN_REQUIRED` is bumped to 2.58+, use
|
||||
* g_spawn_async_with_fds() to be portable on windows. The current
|
||||
* logic does not work on windows b/c `GSpawnChildSetupFunc` is run
|
||||
* inside the parent, not the child.
|
||||
*/
|
||||
if (dup2(STDOUT_FILENO, STDERR_FILENO) != 0) {
|
||||
slog("dup2() failed to merge stderr into stdout: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset ignored signals back to default. */
|
||||
memset(&sigact, 0, sizeof(struct sigaction));
|
||||
sigact.sa_handler = SIG_DFL;
|
||||
|
||||
if (sigaction(SIGPIPE, &sigact, NULL) != 0) {
|
||||
slog("sigaction() failed to reset child process's SIGPIPE: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static gboolean guest_exec_input_watch(GIOChannel *ch,
|
||||
GIOCondition cond, gpointer p_)
|
||||
{
|
||||
GuestExecIOData *p = (GuestExecIOData *)p_;
|
||||
gsize bytes_written = 0;
|
||||
GIOStatus status;
|
||||
GError *gerr = NULL;
|
||||
|
||||
/* nothing left to write */
|
||||
if (p->size == p->length) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
status = g_io_channel_write_chars(ch, (gchar *)p->data + p->length,
|
||||
p->size - p->length, &bytes_written, &gerr);
|
||||
|
||||
/* can be not 0 even if not G_IO_STATUS_NORMAL */
|
||||
if (bytes_written != 0) {
|
||||
p->length += bytes_written;
|
||||
}
|
||||
|
||||
/* continue write, our callback will be called again */
|
||||
if (status == G_IO_STATUS_NORMAL || status == G_IO_STATUS_AGAIN) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (gerr) {
|
||||
g_warning("qga: i/o error writing to input_data channel: %s",
|
||||
gerr->message);
|
||||
g_error_free(gerr);
|
||||
}
|
||||
|
||||
done:
|
||||
g_io_channel_shutdown(ch, true, NULL);
|
||||
g_io_channel_unref(ch);
|
||||
p->closed = true;
|
||||
g_free(p->data);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static gboolean guest_exec_output_watch(GIOChannel *ch,
|
||||
GIOCondition cond, gpointer p_)
|
||||
{
|
||||
GuestExecIOData *p = (GuestExecIOData *)p_;
|
||||
gsize bytes_read;
|
||||
GIOStatus gstatus;
|
||||
|
||||
if (cond == G_IO_HUP || cond == G_IO_ERR) {
|
||||
goto close;
|
||||
}
|
||||
|
||||
if (p->size == p->length) {
|
||||
gpointer t = NULL;
|
||||
if (!p->truncated && p->size < GUEST_EXEC_MAX_OUTPUT) {
|
||||
t = g_try_realloc(p->data, p->size + GUEST_EXEC_IO_SIZE);
|
||||
}
|
||||
if (t == NULL) {
|
||||
/* ignore truncated output */
|
||||
gchar buf[GUEST_EXEC_IO_SIZE];
|
||||
|
||||
p->truncated = true;
|
||||
gstatus = g_io_channel_read_chars(ch, buf, sizeof(buf),
|
||||
&bytes_read, NULL);
|
||||
if (gstatus == G_IO_STATUS_EOF || gstatus == G_IO_STATUS_ERROR) {
|
||||
goto close;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
p->size += GUEST_EXEC_IO_SIZE;
|
||||
p->data = t;
|
||||
}
|
||||
|
||||
/* Calling read API once.
|
||||
* On next available data our callback will be called again */
|
||||
gstatus = g_io_channel_read_chars(ch, (gchar *)p->data + p->length,
|
||||
p->size - p->length, &bytes_read, NULL);
|
||||
if (gstatus == G_IO_STATUS_EOF || gstatus == G_IO_STATUS_ERROR) {
|
||||
goto close;
|
||||
}
|
||||
|
||||
p->length += bytes_read;
|
||||
|
||||
return true;
|
||||
|
||||
close:
|
||||
g_io_channel_shutdown(ch, true, NULL);
|
||||
g_io_channel_unref(ch);
|
||||
p->closed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static GuestExecCaptureOutputMode ga_parse_capture_output(
|
||||
GuestExecCaptureOutput *capture_output)
|
||||
{
|
||||
if (!capture_output)
|
||||
return GUEST_EXEC_CAPTURE_OUTPUT_MODE_NONE;
|
||||
else if (capture_output->type == QTYPE_QBOOL)
|
||||
return capture_output->u.flag ? GUEST_EXEC_CAPTURE_OUTPUT_MODE_SEPARATED
|
||||
: GUEST_EXEC_CAPTURE_OUTPUT_MODE_NONE;
|
||||
else
|
||||
return capture_output->u.mode;
|
||||
}
|
||||
|
||||
GuestExec *qmp_guest_exec(const char *path,
|
||||
bool has_arg, strList *arg,
|
||||
bool has_env, strList *env,
|
||||
const char *input_data,
|
||||
GuestExecCaptureOutput *capture_output,
|
||||
Error **errp)
|
||||
{
|
||||
GPid pid;
|
||||
GuestExec *ge = NULL;
|
||||
GuestExecInfo *gei;
|
||||
char **argv, **envp;
|
||||
strList arglist;
|
||||
gboolean ret;
|
||||
GError *gerr = NULL;
|
||||
gint in_fd, out_fd, err_fd;
|
||||
GIOChannel *in_ch, *out_ch, *err_ch;
|
||||
GSpawnFlags flags;
|
||||
bool has_output = false;
|
||||
bool has_merge = false;
|
||||
GuestExecCaptureOutputMode output_mode;
|
||||
g_autofree uint8_t *input = NULL;
|
||||
size_t ninput = 0;
|
||||
|
||||
arglist.value = (char *)path;
|
||||
arglist.next = has_arg ? arg : NULL;
|
||||
|
||||
if (input_data) {
|
||||
input = qbase64_decode(input_data, -1, &ninput, errp);
|
||||
if (!input) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
argv = guest_exec_get_args(&arglist, true);
|
||||
envp = has_env ? guest_exec_get_args(env, false) : NULL;
|
||||
|
||||
flags = G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD |
|
||||
G_SPAWN_SEARCH_PATH_FROM_ENVP;
|
||||
|
||||
output_mode = ga_parse_capture_output(capture_output);
|
||||
switch (output_mode) {
|
||||
case GUEST_EXEC_CAPTURE_OUTPUT_MODE_NONE:
|
||||
flags |= G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL;
|
||||
break;
|
||||
case GUEST_EXEC_CAPTURE_OUTPUT_MODE_STDOUT:
|
||||
has_output = true;
|
||||
flags |= G_SPAWN_STDERR_TO_DEV_NULL;
|
||||
break;
|
||||
case GUEST_EXEC_CAPTURE_OUTPUT_MODE_STDERR:
|
||||
has_output = true;
|
||||
flags |= G_SPAWN_STDOUT_TO_DEV_NULL;
|
||||
break;
|
||||
case GUEST_EXEC_CAPTURE_OUTPUT_MODE_SEPARATED:
|
||||
has_output = true;
|
||||
break;
|
||||
#if !defined(G_OS_WIN32)
|
||||
case GUEST_EXEC_CAPTURE_OUTPUT_MODE_MERGED:
|
||||
has_output = true;
|
||||
has_merge = true;
|
||||
break;
|
||||
#endif
|
||||
case GUEST_EXEC_CAPTURE_OUTPUT_MODE__MAX:
|
||||
/* Silence warning; impossible branch */
|
||||
break;
|
||||
}
|
||||
|
||||
ret = g_spawn_async_with_pipes(NULL, argv, envp, flags,
|
||||
guest_exec_task_setup, &has_merge, &pid, input_data ? &in_fd : NULL,
|
||||
has_output ? &out_fd : NULL, has_output ? &err_fd : NULL, &gerr);
|
||||
if (!ret) {
|
||||
error_setg(errp, "%s", gerr->message);
|
||||
g_error_free(gerr);
|
||||
goto done;
|
||||
}
|
||||
|
||||
ge = g_new0(GuestExec, 1);
|
||||
ge->pid = gpid_to_int64(pid);
|
||||
|
||||
gei = guest_exec_info_add(pid);
|
||||
gei->has_output = has_output;
|
||||
g_child_watch_add(pid, guest_exec_child_watch, gei);
|
||||
|
||||
if (input_data) {
|
||||
gei->in.data = g_steal_pointer(&input);
|
||||
gei->in.size = ninput;
|
||||
#ifdef G_OS_WIN32
|
||||
in_ch = g_io_channel_win32_new_fd(in_fd);
|
||||
#else
|
||||
in_ch = g_io_channel_unix_new(in_fd);
|
||||
#endif
|
||||
g_io_channel_set_encoding(in_ch, NULL, NULL);
|
||||
g_io_channel_set_buffered(in_ch, false);
|
||||
g_io_channel_set_flags(in_ch, G_IO_FLAG_NONBLOCK, NULL);
|
||||
g_io_channel_set_close_on_unref(in_ch, true);
|
||||
g_io_add_watch(in_ch, G_IO_OUT, guest_exec_input_watch, &gei->in);
|
||||
}
|
||||
|
||||
if (has_output) {
|
||||
#ifdef G_OS_WIN32
|
||||
out_ch = g_io_channel_win32_new_fd(out_fd);
|
||||
err_ch = g_io_channel_win32_new_fd(err_fd);
|
||||
#else
|
||||
out_ch = g_io_channel_unix_new(out_fd);
|
||||
err_ch = g_io_channel_unix_new(err_fd);
|
||||
#endif
|
||||
g_io_channel_set_encoding(out_ch, NULL, NULL);
|
||||
g_io_channel_set_encoding(err_ch, NULL, NULL);
|
||||
g_io_channel_set_buffered(out_ch, false);
|
||||
g_io_channel_set_buffered(err_ch, false);
|
||||
g_io_channel_set_close_on_unref(out_ch, true);
|
||||
g_io_channel_set_close_on_unref(err_ch, true);
|
||||
g_io_add_watch(out_ch, G_IO_IN | G_IO_HUP,
|
||||
guest_exec_output_watch, &gei->out);
|
||||
g_io_add_watch(err_ch, G_IO_IN | G_IO_HUP,
|
||||
guest_exec_output_watch, &gei->err);
|
||||
}
|
||||
|
||||
done:
|
||||
g_free(argv);
|
||||
g_free(envp);
|
||||
|
||||
return ge;
|
||||
}
|
||||
|
||||
/* Convert GuestFileWhence (either a raw integer or an enum value) into
|
||||
* the guest's SEEK_ constants. */
|
||||
int ga_parse_whence(GuestFileWhence *whence, Error **errp)
|
||||
{
|
||||
/*
|
||||
* Exploit the fact that we picked values to match QGA_SEEK_*;
|
||||
* however, we have to use a temporary variable since the union
|
||||
* members may have different size.
|
||||
*/
|
||||
if (whence->type == QTYPE_QSTRING) {
|
||||
int value = whence->u.name;
|
||||
whence->type = QTYPE_QNUM;
|
||||
whence->u.value = value;
|
||||
}
|
||||
switch (whence->u.value) {
|
||||
case QGA_SEEK_SET:
|
||||
return SEEK_SET;
|
||||
case QGA_SEEK_CUR:
|
||||
return SEEK_CUR;
|
||||
case QGA_SEEK_END:
|
||||
return SEEK_END;
|
||||
}
|
||||
error_setg(errp, "invalid whence code %"PRId64, whence->u.value);
|
||||
return -1;
|
||||
}
|
||||
|
||||
GuestHostName *qmp_guest_get_host_name(Error **errp)
|
||||
{
|
||||
GuestHostName *result = NULL;
|
||||
g_autofree char *hostname = qga_get_host_name(errp);
|
||||
|
||||
/*
|
||||
* We want to avoid using g_get_host_name() because that
|
||||
* caches the result and we wouldn't reflect changes in the
|
||||
* host name.
|
||||
*/
|
||||
|
||||
if (!hostname) {
|
||||
hostname = g_strdup("localhost");
|
||||
}
|
||||
|
||||
result = g_new0(GuestHostName, 1);
|
||||
result->host_name = g_steal_pointer(&hostname);
|
||||
return result;
|
||||
}
|
||||
|
||||
GuestTimezone *qmp_guest_get_timezone(Error **errp)
|
||||
{
|
||||
GuestTimezone *info = NULL;
|
||||
GTimeZone *tz = NULL;
|
||||
gint64 now = 0;
|
||||
gint32 intv = 0;
|
||||
gchar const *name = NULL;
|
||||
|
||||
info = g_new0(GuestTimezone, 1);
|
||||
tz = g_time_zone_new_local();
|
||||
if (tz == NULL) {
|
||||
error_setg(errp, "Couldn't retrieve local timezone");
|
||||
goto error;
|
||||
}
|
||||
|
||||
now = g_get_real_time() / G_USEC_PER_SEC;
|
||||
intv = g_time_zone_find_interval(tz, G_TIME_TYPE_UNIVERSAL, now);
|
||||
info->offset = g_time_zone_get_offset(tz, intv);
|
||||
name = g_time_zone_get_abbreviation(tz, intv);
|
||||
if (name != NULL) {
|
||||
info->zone = g_strdup(name);
|
||||
}
|
||||
g_time_zone_unref(tz);
|
||||
|
||||
return info;
|
||||
|
||||
error:
|
||||
g_free(info);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count,
|
||||
int64_t count, Error **errp)
|
||||
{
|
||||
GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
|
||||
GuestFileRead *read_data;
|
||||
|
||||
if (!gfh) {
|
||||
return NULL;
|
||||
}
|
||||
if (!has_count) {
|
||||
count = QGA_READ_COUNT_DEFAULT;
|
||||
} else if (count < 0 || count > GUEST_FILE_READ_COUNT_MAX) {
|
||||
error_setg(errp, "value '%" PRId64 "' is invalid for argument count",
|
||||
count);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
read_data = guest_file_read_unsafe(gfh, count, errp);
|
||||
if (!read_data) {
|
||||
slog("guest-file-write failed, handle: %" PRId64, handle);
|
||||
}
|
||||
|
||||
return read_data;
|
||||
}
|
||||
|
||||
int64_t qmp_guest_get_time(Error **errp)
|
||||
{
|
||||
return g_get_real_time() * 1000;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "cutils.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
/**
|
||||
* qga_open_cloexec:
|
||||
* @name: the pathname to open
|
||||
* @flags: as in open()
|
||||
* @mode: as in open()
|
||||
*
|
||||
* A wrapper for open() function which sets O_CLOEXEC.
|
||||
*
|
||||
* On error, -1 is returned.
|
||||
*/
|
||||
int qga_open_cloexec(const char *name, int flags, mode_t mode)
|
||||
{
|
||||
int ret;
|
||||
|
||||
#ifdef O_CLOEXEC
|
||||
ret = open(name, flags | O_CLOEXEC, mode);
|
||||
#else
|
||||
ret = open(name, flags, mode);
|
||||
if (ret >= 0) {
|
||||
qemu_set_cloexec(ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef CUTILS_H_
|
||||
#define CUTILS_H_
|
||||
|
||||
int qga_open_cloexec(const char *name, int flags, mode_t mode);
|
||||
|
||||
#endif /* CUTILS_H_ */
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* QEMU Guest Agent command state interfaces
|
||||
*
|
||||
* Copyright IBM Corp. 2011
|
||||
*
|
||||
* Authors:
|
||||
* Michael Roth <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "guest-agent-core.h"
|
||||
|
||||
struct GACommandState {
|
||||
GSList *groups;
|
||||
};
|
||||
|
||||
typedef struct GACommandGroup {
|
||||
void (*init)(void);
|
||||
void (*cleanup)(void);
|
||||
} GACommandGroup;
|
||||
|
||||
/* handle init/cleanup for stateful guest commands */
|
||||
|
||||
void ga_command_state_add(GACommandState *cs,
|
||||
void (*init)(void),
|
||||
void (*cleanup)(void))
|
||||
{
|
||||
GACommandGroup *cg = g_new0(GACommandGroup, 1);
|
||||
cg->init = init;
|
||||
cg->cleanup = cleanup;
|
||||
cs->groups = g_slist_append(cs->groups, cg);
|
||||
}
|
||||
|
||||
static void ga_command_group_init(gpointer opaque, gpointer unused)
|
||||
{
|
||||
GACommandGroup *cg = opaque;
|
||||
|
||||
g_assert(cg);
|
||||
if (cg->init) {
|
||||
cg->init();
|
||||
}
|
||||
}
|
||||
|
||||
void ga_command_state_init_all(GACommandState *cs)
|
||||
{
|
||||
g_assert(cs);
|
||||
g_slist_foreach(cs->groups, ga_command_group_init, NULL);
|
||||
}
|
||||
|
||||
static void ga_command_group_cleanup(gpointer opaque, gpointer unused)
|
||||
{
|
||||
GACommandGroup *cg = opaque;
|
||||
|
||||
g_assert(cg);
|
||||
if (cg->cleanup) {
|
||||
cg->cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
void ga_command_state_cleanup_all(GACommandState *cs)
|
||||
{
|
||||
g_assert(cs);
|
||||
g_slist_foreach(cs->groups, ga_command_group_cleanup, NULL);
|
||||
}
|
||||
|
||||
GACommandState *ga_command_state_new(void)
|
||||
{
|
||||
GACommandState *cs = g_new0(GACommandState, 1);
|
||||
cs->groups = NULL;
|
||||
return cs;
|
||||
}
|
||||
|
||||
void ga_command_state_free(GACommandState *cs)
|
||||
{
|
||||
g_slist_free_full(cs->groups, g_free);
|
||||
g_free(cs);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* QEMU Guest Agent core declarations
|
||||
*
|
||||
* Copyright IBM Corp. 2011
|
||||
*
|
||||
* Authors:
|
||||
* Adam Litke <[email protected]>
|
||||
* Michael Roth <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
#ifndef GUEST_AGENT_CORE_H
|
||||
#define GUEST_AGENT_CORE_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <pdh.h>
|
||||
#endif
|
||||
|
||||
#include "qapi/qmp-registry.h"
|
||||
#include "qga-qapi-types.h"
|
||||
|
||||
#define QGA_READ_COUNT_DEFAULT 4096
|
||||
|
||||
typedef struct GAState GAState;
|
||||
typedef struct GACommandState GACommandState;
|
||||
|
||||
extern GAState *ga_state;
|
||||
extern QmpCommandList ga_commands;
|
||||
|
||||
GList *ga_command_init_blockedrpcs(GList *blockedrpcs);
|
||||
void ga_command_state_init(GAState *s, GACommandState *cs);
|
||||
void ga_command_state_add(GACommandState *cs,
|
||||
void (*init)(void),
|
||||
void (*cleanup)(void));
|
||||
void ga_command_state_init_all(GACommandState *cs);
|
||||
void ga_command_state_cleanup_all(GACommandState *cs);
|
||||
GACommandState *ga_command_state_new(void);
|
||||
void ga_command_state_free(GACommandState *cs);
|
||||
bool ga_logging_enabled(GAState *s);
|
||||
void ga_disable_logging(GAState *s);
|
||||
void ga_enable_logging(GAState *s);
|
||||
void G_GNUC_PRINTF(1, 2) slog(const gchar *fmt, ...);
|
||||
void ga_set_response_delimited(GAState *s);
|
||||
bool ga_is_frozen(GAState *s);
|
||||
void ga_set_frozen(GAState *s);
|
||||
void ga_unset_frozen(GAState *s);
|
||||
#ifdef _WIN32
|
||||
void ga_set_load_avg_event(GAState *s, HANDLE event);
|
||||
void ga_set_load_avg_wait_handle(GAState *s, HANDLE wait_handle);
|
||||
void ga_set_load_avg_pdh_query(GAState *s, HQUERY query);
|
||||
HQUERY ga_get_load_avg_pdh_query(GAState *s);
|
||||
#endif
|
||||
const char *ga_fsfreeze_hook(GAState *s);
|
||||
int64_t ga_get_fd_handle(GAState *s, Error **errp);
|
||||
int ga_parse_whence(GuestFileWhence *whence, Error **errp);
|
||||
|
||||
#ifndef _WIN32
|
||||
void reopen_fd_to_null(int fd);
|
||||
#endif
|
||||
|
||||
#endif /* GUEST_AGENT_CORE_H */
|
||||
@@ -0,0 +1,201 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<?if $(var.Arch) = "64"?>
|
||||
<?define ArchLib=libgcc_s_seh-1.dll?>
|
||||
<?define GaProgramFilesFolder="ProgramFiles64Folder" ?>
|
||||
<?else?>
|
||||
<?if $(var.Arch) = "32"?>
|
||||
<?define ArchLib=libgcc_s_dw2-1.dll?>
|
||||
<?define GaProgramFilesFolder="ProgramFilesFolder" ?>
|
||||
<?else?>
|
||||
<?error Unexpected Arch value $(var.Arch)?>
|
||||
<?endif?>
|
||||
<?endif?>
|
||||
|
||||
<Product
|
||||
Name="QEMU guest agent"
|
||||
Id="*"
|
||||
UpgradeCode="{EB6B8302-C06E-4BEC-ADAC-932C68A3A98D}"
|
||||
Manufacturer="$(var.QEMU_GA_MANUFACTURER)"
|
||||
Version="$(var.QEMU_GA_VERSION)"
|
||||
Language="1033">
|
||||
<?if $(var.Arch) = 32 ?>
|
||||
<Condition Message="Error: 32-bit version of Qemu GA can not be installed on 64-bit Windows.">NOT VersionNT64</Condition>
|
||||
<?endif?>
|
||||
<Package
|
||||
Manufacturer="$(var.QEMU_GA_MANUFACTURER)"
|
||||
InstallerVersion="200"
|
||||
Languages="1033"
|
||||
Compressed="yes"
|
||||
InstallScope="perMachine"
|
||||
/>
|
||||
<Media Id="1" Cabinet="qemu_ga.$(var.QEMU_GA_VERSION).cab" EmbedCab="yes" />
|
||||
<Property Id="WHSLogo">1</Property>
|
||||
<Property Id="ARPNOMODIFY" Value="yes" Secure="yes" />
|
||||
<MajorUpgrade
|
||||
DowngradeErrorMessage="Error: A newer version of QEMU guest agent is already installed."
|
||||
/>
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="$(var.GaProgramFilesFolder)" Name="QEMU Guest Agent">
|
||||
<Directory Id="qemu_ga_directory" Name="Qemu-ga">
|
||||
<Component Id="qemu_ga" Guid="{908B7199-DE2A-4DC6-A8D0-27A5AE444FEA}">
|
||||
<File Id="qemu_ga.exe" Name="qemu-ga.exe" Source="$(var.BUILD_DIR)/qga/qemu-ga.exe" KeyPath="yes" DiskId="1"/>
|
||||
<ServiceInstall
|
||||
Id="ServiceInstaller"
|
||||
Type="ownProcess"
|
||||
Vital="yes"
|
||||
Name="QEMU-GA"
|
||||
DisplayName="QEMU Guest Agent"
|
||||
Description="QEMU Guest Agent"
|
||||
Start="auto"
|
||||
Account="LocalSystem"
|
||||
ErrorControl="ignore"
|
||||
Interactive="no"
|
||||
Arguments="-d --retry-path"
|
||||
>
|
||||
</ServiceInstall>
|
||||
<ServiceControl Id="StartService" Start="install" Stop="both" Remove="uninstall" Name="QEMU-GA" Wait="yes" />
|
||||
</Component>
|
||||
<?ifdef var.InstallVss?>
|
||||
<Component Id="libstdc++_6_lib" Guid="{55E737B5-9127-4A11-9FC3-A29367714574}">
|
||||
<File Id="libstdc++-6.lib" Name="libstdc++-6.dll" Source="$(var.BIN_DIR)/libstdc++-6.dll" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<Component Id="qga_vss_dll" Guid="{CB19C453-FABB-4BB1-ABAB-6B74F687BFBB}">
|
||||
<File Id="qga_vss.dll" Name="qga-vss.dll" Source="$(var.BUILD_DIR)/qga/vss-win32/qga-vss.dll" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<Component Id="qga_vss_tlb" Guid="{D8D584B1-59C2-4FB7-A91F-636FF7BFA66E}">
|
||||
<File Id="qga_vss.tlb" Name="qga-vss.tlb" Source="$(var.BUILD_DIR)/qga/vss-win32/qga-vss.tlb" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<?endif?>
|
||||
<?if $(var.Arch) = "32"?>
|
||||
<Component Id="gspawn-helper-console" Guid="{446185B3-87BE-43D2-96B8-0FEFD9E8696D}">
|
||||
<File Id="gspawn-win32-helper-console.exe" Name="gspawn-win32-helper-console.exe" Source="$(var.BIN_DIR)/gspawn-win32-helper-console.exe" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<Component Id="gspawn-helper" Guid="{CD67A5A3-2DB1-4DA1-A67A-8D71E797B466}">
|
||||
<File Id="gspawn-win32-helper.exe" Name="gspawn-win32-helper.exe" Source="$(var.BIN_DIR)/gspawn-win32-helper-console.exe" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<?endif?>
|
||||
<?if $(var.Arch) = "64"?>
|
||||
<Component Id="gspawn-helper-console" Guid="{9E615A9F-349A-4992-A5C2-C10BAD173660}">
|
||||
<File Id="gspawn-win64-helper-console.exe" Name="gspawn-win64-helper-console.exe" Source="$(var.BIN_DIR)/gspawn-win64-helper-console.exe" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<Component Id="gspawn-helper" Guid="{D201AD22-1846-4E4F-B6E1-C7A908ED2457}">
|
||||
<File Id="gspawn-win64-helper.exe" Name="gspawn-win64-helper.exe" Source="$(var.BIN_DIR)/gspawn-win64-helper-console.exe" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<?endif?>
|
||||
<Component Id="iconv" Guid="{35EE3558-D34B-4F0A-B8BD-430FF0775246}">
|
||||
<File Id="iconv.dll" Name="iconv.dll" Source="$(var.BIN_DIR)/iconv.dll" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<Component Id="libgcc_arch_lib" Guid="{ADD4D07D-4515-4AB6-AF3E-C904961B4BB0}">
|
||||
<File Id="libgcc_arch_lib" Name="$(var.ArchLib)" Source="$(var.BIN_DIR)/$(var.ArchLib)" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<Component Id="libglib" Guid="{D31BFD83-2773-4B65-B45A-E0D2ADA58679}">
|
||||
<File Id="libglib_2.0_0.dll" Name="libglib-2.0-0.dll" Source="$(var.BIN_DIR)/libglib-2.0-0.dll" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<Component Id="libintl" Guid="{A641BC2D-A907-4A94-9149-F30ED430878F}">
|
||||
<File Id="libintl_8.dll" Name="libintl-8.dll" Source="$(var.BIN_DIR)/libintl-8.dll" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<Component Id="libssp" Guid="{7880087B-02B4-4EF6-A5D3-D18F8E3D90E1}">
|
||||
<File Id="libssp_0.dll" Name="libssp-0.dll" Source="$(var.BIN_DIR)/libssp-0.dll" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<Component Id="libwinpthread" Guid="{6C117C78-0F47-4B07-8F34-6BEE11643829}">
|
||||
<File Id="libwinpthread_1.dll" Name="libwinpthread-1.dll" Source="$(var.BIN_DIR)/libwinpthread-1.dll" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<?if $(var.LIBPCRE) = "libpcre1"?>
|
||||
<Component Id="libpcre" Guid="{7A86B45E-A009-489A-A849-CE3BACF03CD0}">
|
||||
<File Id="libpcre_1.dll" Name="libpcre-1.dll" Source="$(var.BIN_DIR)/libpcre-1.dll" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<?else?>
|
||||
<Component Id="libpcre" Guid="{F92A3804-B59C-419D-8F29-99A30352C156}">
|
||||
<File Id="libpcre2_8_0.dll" Name="libpcre2-8-0.dll" Source="$(var.BIN_DIR)/libpcre2-8-0.dll" KeyPath="yes" DiskId="1"/>
|
||||
</Component>
|
||||
<?endif?>
|
||||
<Component Id="registry_entries" Guid="{D075D109-51CA-11E3-9F8B-000C29858960}">
|
||||
<RegistryKey Root="HKLM"
|
||||
Key="Software\$(var.QEMU_GA_MANUFACTURER)\$(var.QEMU_GA_DISTRO)\Tools\QemuGA">
|
||||
<RegistryValue Type="string" Name="ProductID" Value="fb0a0d66-c7fb-4e2e-a16b-c4a3bfe8d13b" />
|
||||
<RegistryValue Type="string" Name="Version" Value="$(var.QEMU_GA_VERSION)" />
|
||||
</RegistryKey>
|
||||
<RegistryKey Root="HKLM"
|
||||
Key="System\CurrentControlSet\Services\EventLog\Application\qemu-ga">
|
||||
<RegistryValue Type="integer" Name="TypesSupported" Value="7" />
|
||||
<RegistryValue Type="string" Name="EventMessageFile" Value="[qemu_ga_directory]qemu-ga.exe" />
|
||||
</RegistryKey>
|
||||
<RegistryKey Root="HKLM"
|
||||
Key="System\CurrentControlSet\Services\QEMU Guest Agent VSS Provider">
|
||||
<RegistryValue Type="integer" Name="VssOption" Value="1" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Property Id="rundll" Value="rundll32.exe"/>
|
||||
<Property Id="REINSTALLMODE" Value="amus"/>
|
||||
|
||||
<?ifdef var.InstallVss?>
|
||||
<CustomAction Id="RegisterCom"
|
||||
ExeCommand='"[qemu_ga_directory]qga-vss.dll",DLLCOMRegister'
|
||||
Execute="deferred"
|
||||
Property="rundll"
|
||||
Impersonate="no"
|
||||
Return="check"
|
||||
>
|
||||
</CustomAction>
|
||||
<CustomAction Id="UnRegisterCom"
|
||||
ExeCommand='"[qemu_ga_directory]qga-vss.dll",DLLCOMUnregister'
|
||||
Execute="deferred"
|
||||
Property="rundll"
|
||||
Impersonate="no"
|
||||
Return="check"
|
||||
>
|
||||
</CustomAction>
|
||||
<CustomAction Id="UnRegisterCom_Rollback"
|
||||
ExeCommand='"[qemu_ga_directory]qga-vss.dll",DLLCOMUnregister'
|
||||
Execute="rollback"
|
||||
Property="rundll"
|
||||
Impersonate="no"
|
||||
Return="check"
|
||||
>
|
||||
</CustomAction>
|
||||
<?endif?>
|
||||
|
||||
<Feature Id="QEMUFeature" Title="QEMU Guest Agent" Level="1">
|
||||
<ComponentRef Id="qemu_ga" />
|
||||
<?ifdef var.InstallVss?>
|
||||
<ComponentRef Id="libstdc++_6_lib" />
|
||||
<ComponentRef Id="qga_vss_dll" />
|
||||
<ComponentRef Id="qga_vss_tlb" />
|
||||
<?endif?>
|
||||
<ComponentRef Id="gspawn-helper-console" />
|
||||
<ComponentRef Id="gspawn-helper" />
|
||||
<ComponentRef Id="iconv" />
|
||||
<ComponentRef Id="libgcc_arch_lib" />
|
||||
<ComponentRef Id="libglib" />
|
||||
<ComponentRef Id="libintl" />
|
||||
<ComponentRef Id="libssp" />
|
||||
<ComponentRef Id="libwinpthread" />
|
||||
<ComponentRef Id="registry_entries" />
|
||||
<ComponentRef Id="libpcre" />
|
||||
</Feature>
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<?ifdef var.InstallVss?>
|
||||
<!-- Use explicit Sequence number to provide an absolute position in the sequence-->
|
||||
<!-- This is needed to set "UnRegisterCom_Rollback" before "RegisterCom" and after "InstallFiles"-->
|
||||
<!-- but, Wix detect this double condition incorrectly -->
|
||||
|
||||
<!-- UnRegisterCom_Rollback (for install rollback): at 5849, right before RegisterCom (5850)-->
|
||||
<!-- Runs only if the installation fails and rolls back-->
|
||||
<Custom Action="UnRegisterCom_Rollback" Sequence="5849">NOT REMOVE</Custom>
|
||||
|
||||
<!-- RegisterCom (for install): at 5850, right after InstallFiles (5849) (old: After="InstallServices")-->
|
||||
<Custom Action="RegisterCom" Sequence="5850">NOT REMOVE</Custom>
|
||||
|
||||
<!-- UnRegisterCom (for uninstall): at 1901, right after StopServices (1900) (old: After="StopServices")-->
|
||||
<Custom Action="UnRegisterCom" Sequence="1901">Installed</Custom>
|
||||
<?endif?>
|
||||
</InstallExecuteSequence>
|
||||
</Product>
|
||||
</Wix>
|
||||
+1780
File diff suppressed because it is too large
Load Diff
+205
@@ -0,0 +1,205 @@
|
||||
if not have_ga
|
||||
if get_option('guest_agent_msi').enabled()
|
||||
error('Guest agent MSI requested, but the guest agent is not being built')
|
||||
endif
|
||||
have_qga_vss = false
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
have_qga_vss = get_option('qga_vss') \
|
||||
.require(host_os == 'windows',
|
||||
error_message: 'VSS support requires Windows') \
|
||||
.require('cpp' in all_languages,
|
||||
error_message: 'VSS support requires a C++ compiler') \
|
||||
.require(have_vss, error_message: '''VSS support requires VSS headers.
|
||||
If your Visual Studio installation doesn't have the VSS headers,
|
||||
Please download and install Microsoft VSS SDK:
|
||||
http://www.microsoft.com/en-us/download/details.aspx?id=23490
|
||||
On POSIX-systems, MinGW should provide headers in >=10.0 releases.
|
||||
you can extract the SDK headers by:
|
||||
$ scripts/extract-vsssdk-headers setup.exe
|
||||
The headers are extracted in the directory 'inc/win2003'.
|
||||
Then run configure with: --extra-cxxflags="-isystem /path/to/vss/inc/win2003"''') \
|
||||
.require(midl.found() or widl.found(),
|
||||
error_message: 'VSS support requires midl or widl') \
|
||||
.require(not get_option('prefer_static'),
|
||||
error_message: 'VSS support requires dynamic linking with GLib') \
|
||||
.allowed()
|
||||
|
||||
all_qga = []
|
||||
|
||||
qga_qapi_outputs = [
|
||||
'qga-qapi-commands.c',
|
||||
'qga-qapi-commands.h',
|
||||
'qga-qapi-emit-events.c',
|
||||
'qga-qapi-emit-events.h',
|
||||
'qga-qapi-events.c',
|
||||
'qga-qapi-events.h',
|
||||
'qga-qapi-init-commands.c',
|
||||
'qga-qapi-init-commands.h',
|
||||
'qga-qapi-introspect.c',
|
||||
'qga-qapi-introspect.h',
|
||||
'qga-qapi-types.c',
|
||||
'qga-qapi-types.h',
|
||||
'qga-qapi-visit.c',
|
||||
'qga-qapi-visit.h',
|
||||
]
|
||||
|
||||
# Problem: to generate trace events, we'd have to add the .trace-events
|
||||
# file to qapi_trace_events like we do in qapi/meson.build. Since
|
||||
# qapi_trace_events is used by trace/meson.build, we'd have to move
|
||||
# subdir('qga') above subdir('trace') in the top-level meson.build.
|
||||
# Can't, because it would break the dependency of qga on qemuutil (which
|
||||
# depends on trace_ss). Not worth solving now; simply suppress trace
|
||||
# event generation instead.
|
||||
qga_qapi_files = custom_target('QGA QAPI files',
|
||||
output: qga_qapi_outputs,
|
||||
input: 'qapi-schema.json',
|
||||
command: [ qapi_gen, '-o', 'qga', '-p', 'qga-', '@INPUT0@',
|
||||
'--suppress-tracing' ],
|
||||
depend_files: qapi_gen_depends)
|
||||
|
||||
qga_ss = ss.source_set()
|
||||
qga_ss.add(qga_qapi_files.to_list())
|
||||
qga_ss.add(files(
|
||||
'commands.c',
|
||||
'guest-agent-command-state.c',
|
||||
'main.c',
|
||||
'cutils.c',
|
||||
'commands-common-ssh.c'
|
||||
))
|
||||
if host_os == 'windows'
|
||||
qga_ss.add(files(
|
||||
'channel-win32.c',
|
||||
'commands-win32.c',
|
||||
'service-win32.c',
|
||||
'vss-win32.c',
|
||||
'commands-windows-ssh.c'
|
||||
))
|
||||
else
|
||||
qga_ss.add(files(
|
||||
'channel-posix.c',
|
||||
'commands-posix.c',
|
||||
'commands-posix-ssh.c',
|
||||
))
|
||||
if host_os == 'linux'
|
||||
qga_ss.add(files('commands-linux.c'))
|
||||
elif host_os in bsd_oses
|
||||
qga_ss.add(files('commands-bsd.c'))
|
||||
endif
|
||||
endif
|
||||
|
||||
qga_ss = qga_ss.apply({})
|
||||
|
||||
gen_tlb = []
|
||||
qga_libs = []
|
||||
if host_os == 'windows'
|
||||
qga_libs += ['-lws2_32', '-lwinmm', '-lpowrprof', '-lwtsapi32', '-lwininet', '-liphlpapi', '-lnetapi32',
|
||||
'-lsetupapi', '-lcfgmgr32', '-luserenv', '-lpdh' ]
|
||||
if have_qga_vss
|
||||
qga_libs += ['-lole32', '-loleaut32', '-lshlwapi', '-Wl,--enable-stdcall-fixup']
|
||||
subdir('vss-win32')
|
||||
endif
|
||||
endif
|
||||
|
||||
qga_objs = []
|
||||
if host_os == 'windows'
|
||||
windmc = find_program('windmc', 'mc', required: true)
|
||||
|
||||
msgrc = custom_target('messages-win32.rc',
|
||||
input: 'messages-win32.mc',
|
||||
output: ['messages-win32.rc', 'MSG00409.bin', 'messages-win32.h'],
|
||||
command: [windmc, '-h', '@OUTDIR@', '-r', '@OUTDIR@', '@INPUT@'])
|
||||
|
||||
windows = import('windows')
|
||||
msgobj = windows.compile_resources(msgrc[0])
|
||||
|
||||
qga_objs = [msgobj]
|
||||
endif
|
||||
|
||||
qga = executable('qemu-ga', qga_ss.sources() + qga_objs,
|
||||
link_args: qga_libs,
|
||||
dependencies: [qemuutil, libudev],
|
||||
install: true)
|
||||
all_qga += qga
|
||||
|
||||
if host_os == 'windows'
|
||||
qemu_ga_msi_arch = {
|
||||
'x86': ['-D', 'Arch=32'],
|
||||
'x86_64': ['-a', 'x64', '-D', 'Arch=64']
|
||||
}
|
||||
wixl = not_found
|
||||
if cpu in qemu_ga_msi_arch
|
||||
wixl = find_program('wixl', required: get_option('guest_agent_msi'))
|
||||
elif get_option('guest_agent_msi').enabled()
|
||||
error('CPU not supported for building guest agent installation package')
|
||||
endif
|
||||
|
||||
if wixl.found()
|
||||
deps = [gen_tlb, qga]
|
||||
qemu_ga_msi_vss = []
|
||||
if have_qga_vss
|
||||
qemu_ga_msi_vss = ['-D', 'InstallVss']
|
||||
deps += qga_vss
|
||||
endif
|
||||
if glib.version().version_compare('<2.73.2')
|
||||
libpcre = 'libpcre1'
|
||||
else
|
||||
libpcre = 'libpcre2'
|
||||
endif
|
||||
qga_msi_version = get_option('qemu_ga_version') == '' \
|
||||
? meson.project_version() \
|
||||
: get_option('qemu_ga_version')
|
||||
qga_msi = custom_target('QGA MSI',
|
||||
input: files('installer/qemu-ga.wxs'),
|
||||
output: 'qemu-ga-@[email protected]'.format(host_arch),
|
||||
depends: deps,
|
||||
command: [
|
||||
wixl, '-o', '@OUTPUT0@', '@INPUT0@',
|
||||
qemu_ga_msi_arch[cpu],
|
||||
qemu_ga_msi_vss,
|
||||
'-D', 'BUILD_DIR=' + meson.project_build_root(),
|
||||
'-D', 'BIN_DIR=' + glib_pc.get_variable('bindir'),
|
||||
'-D', 'QEMU_GA_VERSION=' + qga_msi_version,
|
||||
'-D', 'QEMU_GA_MANUFACTURER=' + get_option('qemu_ga_manufacturer'),
|
||||
'-D', 'QEMU_GA_DISTRO=' + get_option('qemu_ga_distro'),
|
||||
'-D', 'LIBPCRE=' + libpcre,
|
||||
])
|
||||
all_qga += [qga_msi]
|
||||
alias_target('msi', qga_msi)
|
||||
endif
|
||||
else
|
||||
if get_option('guest_agent_msi').enabled()
|
||||
error('MSI guest agent package is available only for MinGW Windows cross-compilation')
|
||||
endif
|
||||
install_emptydir(get_option('localstatedir') / 'run')
|
||||
endif
|
||||
|
||||
alias_target('qemu-ga', all_qga)
|
||||
|
||||
test_env = environment()
|
||||
test_env.set('G_TEST_SRCDIR', meson.current_source_dir())
|
||||
test_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
|
||||
|
||||
# disable qga-ssh-test with fuzzing: glib's G_TEST_OPTION_ISOLATE_DIRS triggers
|
||||
# the leak detector in build-oss-fuzz Gitlab CI test. we should re-enable
|
||||
# this when an alternative is implemented or when the underlying glib
|
||||
# issue is identified/fix
|
||||
if host_os != 'windows' and not get_option('fuzzing')
|
||||
srcs = [files('commands-common-ssh.c', 'commands-posix-ssh.c')]
|
||||
i = 0
|
||||
foreach output: qga_qapi_outputs
|
||||
if output.startswith('qga-qapi-types') or output.startswith('qga-qapi-visit')
|
||||
srcs += qga_qapi_files[i]
|
||||
endif
|
||||
i = i + 1
|
||||
endforeach
|
||||
qga_ssh_test = executable('qga-ssh-test', srcs,
|
||||
dependencies: [qemuutil],
|
||||
c_args: ['-DQGA_BUILD_UNIT_TEST'])
|
||||
|
||||
test('qga-ssh-test',
|
||||
qga_ssh_test,
|
||||
env: test_env,
|
||||
suite: ['unit', 'qga'])
|
||||
endif
|
||||
@@ -0,0 +1,9 @@
|
||||
LanguageNames=(
|
||||
English=0x409:MSG00409
|
||||
)
|
||||
|
||||
MessageId=1
|
||||
SymbolicName=QEMU_GA_EVENTLOG_GENERAL
|
||||
Language=English
|
||||
%1
|
||||
.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* QEMU Guest Agent helpers for win32 service management
|
||||
*
|
||||
* Copyright IBM Corp. 2012
|
||||
*
|
||||
* Authors:
|
||||
* Gal Hammer <[email protected]>
|
||||
* Michael Roth <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include <windows.h>
|
||||
#include "qga/service-win32.h"
|
||||
|
||||
static int printf_win_error(const char *text)
|
||||
{
|
||||
DWORD err = GetLastError();
|
||||
char *message;
|
||||
int n;
|
||||
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL,
|
||||
err,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(char *)&message, 0,
|
||||
NULL);
|
||||
n = fprintf(stderr, "%s. (Error: %d) %s", text, (int)err, message);
|
||||
LocalFree(message);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Windows command line escaping. Based on
|
||||
* <http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx> and
|
||||
* <http://msdn.microsoft.com/en-us/library/windows/desktop/17w5ykft%28v=vs.85%29.aspx>.
|
||||
*
|
||||
* The caller is responsible for initializing @buffer; prior contents are lost.
|
||||
*/
|
||||
static const char *win_escape_arg(const char *to_escape, GString *buffer)
|
||||
{
|
||||
size_t backslash_count;
|
||||
const char *c;
|
||||
|
||||
/* open with a double quote */
|
||||
g_string_assign(buffer, "\"");
|
||||
|
||||
backslash_count = 0;
|
||||
for (c = to_escape; *c != '\0'; ++c) {
|
||||
switch (*c) {
|
||||
case '\\':
|
||||
/* The meaning depends on the first non-backslash character coming
|
||||
* up.
|
||||
*/
|
||||
++backslash_count;
|
||||
break;
|
||||
|
||||
case '"':
|
||||
/* We must escape each pending backslash, then escape the double
|
||||
* quote. This creates a case of "odd number of backslashes [...]
|
||||
* followed by a double quotation mark".
|
||||
*/
|
||||
while (backslash_count) {
|
||||
--backslash_count;
|
||||
g_string_append(buffer, "\\\\");
|
||||
}
|
||||
g_string_append(buffer, "\\\"");
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Any pending backslashes are without special meaning, flush them.
|
||||
* "Backslashes are interpreted literally, unless they immediately
|
||||
* precede a double quotation mark."
|
||||
*/
|
||||
while (backslash_count) {
|
||||
--backslash_count;
|
||||
g_string_append_c(buffer, '\\');
|
||||
}
|
||||
g_string_append_c(buffer, *c);
|
||||
}
|
||||
}
|
||||
|
||||
/* We're about to close with a double quote in string delimiter role.
|
||||
* Double all pending backslashes, creating a case of "even number of
|
||||
* backslashes [...] followed by a double quotation mark".
|
||||
*/
|
||||
while (backslash_count) {
|
||||
--backslash_count;
|
||||
g_string_append(buffer, "\\\\");
|
||||
}
|
||||
g_string_append_c(buffer, '"');
|
||||
|
||||
return buffer->str;
|
||||
}
|
||||
|
||||
int ga_install_service(const char *path, const char *logfile,
|
||||
const char *state_dir)
|
||||
{
|
||||
int ret = EXIT_FAILURE;
|
||||
SC_HANDLE manager;
|
||||
SC_HANDLE service;
|
||||
TCHAR module_fname[MAX_PATH];
|
||||
GString *esc;
|
||||
GString *cmdline;
|
||||
SERVICE_DESCRIPTION desc = { (char *)QGA_SERVICE_DESCRIPTION };
|
||||
|
||||
if (GetModuleFileName(NULL, module_fname, MAX_PATH) == 0) {
|
||||
printf_win_error("No full path to service's executable");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
esc = g_string_new("");
|
||||
cmdline = g_string_new("");
|
||||
|
||||
g_string_append_printf(cmdline, "%s -d",
|
||||
win_escape_arg(module_fname, esc));
|
||||
|
||||
if (path) {
|
||||
g_string_append_printf(cmdline, " -p %s", win_escape_arg(path, esc));
|
||||
}
|
||||
if (logfile) {
|
||||
g_string_append_printf(cmdline, " -l %s -v",
|
||||
win_escape_arg(logfile, esc));
|
||||
}
|
||||
if (state_dir) {
|
||||
g_string_append_printf(cmdline, " -t %s",
|
||||
win_escape_arg(state_dir, esc));
|
||||
}
|
||||
|
||||
g_debug("service's cmdline: %s", cmdline->str);
|
||||
|
||||
manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
||||
if (manager == NULL) {
|
||||
printf_win_error("No handle to service control manager");
|
||||
goto out_strings;
|
||||
}
|
||||
|
||||
service = CreateService(manager, QGA_SERVICE_NAME, QGA_SERVICE_DISPLAY_NAME,
|
||||
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START,
|
||||
SERVICE_ERROR_NORMAL, cmdline->str, NULL, NULL, NULL, NULL, NULL);
|
||||
if (service == NULL) {
|
||||
printf_win_error("Failed to install service");
|
||||
goto out_manager;
|
||||
}
|
||||
|
||||
ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &desc);
|
||||
fprintf(stderr, "Service was installed successfully.\n");
|
||||
ret = EXIT_SUCCESS;
|
||||
CloseServiceHandle(service);
|
||||
|
||||
out_manager:
|
||||
CloseServiceHandle(manager);
|
||||
|
||||
out_strings:
|
||||
g_string_free(cmdline, TRUE);
|
||||
g_string_free(esc, TRUE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ga_uninstall_service(void)
|
||||
{
|
||||
SC_HANDLE manager;
|
||||
SC_HANDLE service;
|
||||
|
||||
manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
||||
if (manager == NULL) {
|
||||
printf_win_error("No handle to service control manager");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
service = OpenService(manager, QGA_SERVICE_NAME, DELETE);
|
||||
if (service == NULL) {
|
||||
printf_win_error("No handle to service");
|
||||
CloseServiceHandle(manager);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (DeleteService(service) == FALSE) {
|
||||
printf_win_error("Failed to delete service");
|
||||
} else {
|
||||
fprintf(stderr, "Service was deleted successfully.\n");
|
||||
}
|
||||
|
||||
CloseServiceHandle(service);
|
||||
CloseServiceHandle(manager);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* QEMU Guest Agent helpers for win32 service management
|
||||
*
|
||||
* Copyright IBM Corp. 2012
|
||||
*
|
||||
* Authors:
|
||||
* Gal Hammer <[email protected]>
|
||||
* Michael Roth <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#ifndef QGA_SERVICE_WIN32_H
|
||||
#define QGA_SERVICE_WIN32_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#define QGA_SERVICE_DISPLAY_NAME "QEMU Guest Agent"
|
||||
#define QGA_SERVICE_NAME "qemu-ga"
|
||||
#define QGA_SERVICE_DESCRIPTION "Enables integration with QEMU machine emulator and virtualizer."
|
||||
|
||||
static const GUID GUID_VIOSERIAL_PORT = { 0x6fde7521, 0x1b65, 0x48ae,
|
||||
{ 0xb6, 0x28, 0x80, 0xbe, 0x62, 0x1, 0x60, 0x26 } };
|
||||
|
||||
typedef struct GAService {
|
||||
SERVICE_STATUS status;
|
||||
SERVICE_STATUS_HANDLE status_handle;
|
||||
HDEVNOTIFY device_notification_handle;
|
||||
} GAService;
|
||||
|
||||
int ga_install_service(const char *path, const char *logfile,
|
||||
const char *state_dir);
|
||||
int ga_uninstall_service(void);
|
||||
|
||||
#endif
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* QEMU Guest Agent VSS utility functions
|
||||
*
|
||||
* Copyright Hitachi Data Systems Corp. 2013
|
||||
*
|
||||
* Authors:
|
||||
* Tomoki Sekiyama <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include <windows.h>
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "guest-agent-core.h"
|
||||
#include "vss-win32.h"
|
||||
#include "vss-win32/requester.h"
|
||||
|
||||
#define QGA_VSS_DLL "qga-vss.dll"
|
||||
|
||||
static HMODULE provider_lib;
|
||||
|
||||
/* Call a function in qga-vss.dll with the specified name */
|
||||
static HRESULT call_vss_provider_func(const char *func_name)
|
||||
{
|
||||
FARPROC WINAPI func;
|
||||
|
||||
g_assert(provider_lib);
|
||||
|
||||
func = GetProcAddress(provider_lib, func_name);
|
||||
if (!func) {
|
||||
char *msg;
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(char *)&msg, 0, NULL);
|
||||
fprintf(stderr, "failed to load %s from %s: %s",
|
||||
func_name, QGA_VSS_DLL, msg);
|
||||
LocalFree(msg);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
return func();
|
||||
}
|
||||
|
||||
/* Check whether this OS version supports VSS providers */
|
||||
static bool vss_check_os_version(void)
|
||||
{
|
||||
OSVERSIONINFO OSver;
|
||||
|
||||
OSver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
|
||||
GetVersionEx(&OSver);
|
||||
if ((OSver.dwMajorVersion == 5 && OSver.dwMinorVersion >= 2) ||
|
||||
OSver.dwMajorVersion > 5) {
|
||||
BOOL wow64 = false;
|
||||
#ifndef _WIN64
|
||||
/* Provider doesn't work under WOW64 (32bit agent on 64bit OS) */
|
||||
if (!IsWow64Process(GetCurrentProcess(), &wow64)) {
|
||||
fprintf(stderr, "failed to IsWow64Process (Error: %lx\n)\n",
|
||||
GetLastError());
|
||||
return false;
|
||||
}
|
||||
if (wow64) {
|
||||
warn_report("Running under WOW64");
|
||||
}
|
||||
#endif
|
||||
return !wow64;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Load qga-vss.dll */
|
||||
bool vss_init(bool init_requester)
|
||||
{
|
||||
if (!vss_check_os_version()) {
|
||||
/* Do nothing if OS doesn't support providers. */
|
||||
fprintf(stderr, "VSS provider is not supported in this OS version: "
|
||||
"fsfreeze is disabled.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
provider_lib = LoadLibraryA(QGA_VSS_DLL);
|
||||
if (!provider_lib) {
|
||||
char *msg;
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(char *)&msg, 0, NULL);
|
||||
fprintf(stderr, "failed to load %s: %sfsfreeze is disabled\n",
|
||||
QGA_VSS_DLL, msg);
|
||||
LocalFree(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (init_requester) {
|
||||
HRESULT hr = call_vss_provider_func("requester_init");
|
||||
if (FAILED(hr)) {
|
||||
fprintf(stderr, "fsfreeze is disabled.\n");
|
||||
vss_deinit(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Unload qga-provider.dll */
|
||||
void vss_deinit(bool deinit_requester)
|
||||
{
|
||||
if (deinit_requester) {
|
||||
call_vss_provider_func("requester_deinit");
|
||||
}
|
||||
FreeLibrary(provider_lib);
|
||||
provider_lib = NULL;
|
||||
}
|
||||
|
||||
bool vss_initialized(void)
|
||||
{
|
||||
return !!provider_lib;
|
||||
}
|
||||
|
||||
int ga_install_vss_provider(void)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
if (!vss_init(false)) {
|
||||
fprintf(stderr, "Installation of VSS provider is skipped. "
|
||||
"fsfreeze will be disabled.\n");
|
||||
return 0;
|
||||
}
|
||||
hr = call_vss_provider_func("COMRegister");
|
||||
vss_deinit(false);
|
||||
|
||||
return SUCCEEDED(hr) ? 0 : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
void ga_uninstall_vss_provider(void)
|
||||
{
|
||||
if (!vss_init(false)) {
|
||||
fprintf(stderr, "Removal of VSS provider is skipped.\n");
|
||||
return;
|
||||
}
|
||||
call_vss_provider_func("COMUnregister");
|
||||
vss_deinit(false);
|
||||
}
|
||||
|
||||
/* Call VSS requester and freeze/thaw filesystems and applications */
|
||||
void qga_vss_fsfreeze(int *nr_volume, bool freeze,
|
||||
strList *mountpoints, Error **errp)
|
||||
{
|
||||
const char *func_name = freeze ? "requester_freeze" : "requester_thaw";
|
||||
QGAVSSRequesterFunc func;
|
||||
ErrorSet errset = {
|
||||
.error_setg_win32_wrapper = error_setg_win32_internal,
|
||||
.errp = errp,
|
||||
};
|
||||
|
||||
*nr_volume = 0;
|
||||
|
||||
g_assert(errp); /* requester.cpp requires it */
|
||||
func = (QGAVSSRequesterFunc)GetProcAddress(provider_lib, func_name);
|
||||
if (!func) {
|
||||
error_setg_win32(errp, GetLastError(), "failed to load %s from %s",
|
||||
func_name, QGA_VSS_DLL);
|
||||
return;
|
||||
}
|
||||
|
||||
func(nr_volume, mountpoints, &errset);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* QEMU Guest Agent VSS utility declarations
|
||||
*
|
||||
* Copyright Hitachi Data Systems Corp. 2013
|
||||
*
|
||||
* Authors:
|
||||
* Tomoki Sekiyama <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#ifndef VSS_WIN32_H
|
||||
#define VSS_WIN32_H
|
||||
|
||||
#include "qga/vss-win32/vss-handles.h"
|
||||
|
||||
bool vss_init(bool init_requester);
|
||||
void vss_deinit(bool deinit_requester);
|
||||
bool vss_initialized(void);
|
||||
|
||||
int ga_install_vss_provider(void);
|
||||
void ga_uninstall_vss_provider(void);
|
||||
|
||||
void qga_vss_fsfreeze(int *nr_volume, bool freeze,
|
||||
strList *mountpints, Error **errp);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,603 @@
|
||||
/*
|
||||
* QEMU Guest Agent win32 VSS Provider installer
|
||||
*
|
||||
* Copyright Hitachi Data Systems Corp. 2013
|
||||
*
|
||||
* Authors:
|
||||
* Tomoki Sekiyama <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "vss-common.h"
|
||||
#include "vss-debug.h"
|
||||
#ifdef HAVE_VSS_SDK
|
||||
#include <vscoordint.h>
|
||||
#else
|
||||
#include <vsadmin.h>
|
||||
#endif
|
||||
#include "install.h"
|
||||
#include <wbemidl.h>
|
||||
#include <comdef.h>
|
||||
#include <comutil.h>
|
||||
#include <sddl.h>
|
||||
#include <winsvc.h>
|
||||
|
||||
#define BUFFER_SIZE 1024
|
||||
|
||||
extern HINSTANCE g_hinstDll;
|
||||
|
||||
const GUID CLSID_COMAdminCatalog = { 0xF618C514, 0xDFB8, 0x11d1,
|
||||
{0xA2, 0xCF, 0x00, 0x80, 0x5F, 0xC7, 0x92, 0x35} };
|
||||
const GUID IID_ICOMAdminCatalog2 = { 0x790C6E0B, 0x9194, 0x4cc9,
|
||||
{0x94, 0x26, 0xA4, 0x8A, 0x63, 0x18, 0x56, 0x96} };
|
||||
const GUID CLSID_WbemLocator = { 0x4590f811, 0x1d3a, 0x11d0,
|
||||
{0x89, 0x1f, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24} };
|
||||
const GUID IID_IWbemLocator = { 0xdc12a687, 0x737f, 0x11cf,
|
||||
{0x88, 0x4d, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24} };
|
||||
|
||||
static void errmsg(DWORD err, const char *text)
|
||||
{
|
||||
/*
|
||||
* `text' contains function call statement when errmsg is called via chk().
|
||||
* To make error message more readable, we cut off the text after '('.
|
||||
* If text doesn't contains '(', negative precision is given, which is
|
||||
* treated as though it were missing.
|
||||
*/
|
||||
char *msg = NULL;
|
||||
const char *nul = strchr(text, '(');
|
||||
int len = nul ? nul - text : -1;
|
||||
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(char *)&msg, 0, NULL);
|
||||
qga_debug("%.*s. (Error: %lx) %s", len, text, err, msg);
|
||||
LocalFree(msg);
|
||||
}
|
||||
|
||||
static void errmsg_dialog(DWORD err, const char *text, const char *opt = "")
|
||||
{
|
||||
char *msg, buf[512];
|
||||
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(char *)&msg, 0, NULL);
|
||||
snprintf(buf, sizeof(buf), "%s%s. (Error: %lx) %s", text, opt, err, msg);
|
||||
MessageBox(NULL, buf, "Error from " QGA_PROVIDER_NAME, MB_OK|MB_ICONERROR);
|
||||
LocalFree(msg);
|
||||
}
|
||||
|
||||
#define _chk(hr, status, msg, err_label) \
|
||||
do { \
|
||||
hr = (status); \
|
||||
if (FAILED(hr)) { \
|
||||
errmsg(hr, msg); \
|
||||
goto err_label; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define chk(status) _chk(hr, status, "Failed to " #status, out)
|
||||
|
||||
#if !defined(__MINGW64_VERSION_MAJOR) || !defined(__MINGW64_VERSION_MINOR) || \
|
||||
__MINGW64_VERSION_MAJOR * 100 + __MINGW64_VERSION_MINOR < 301
|
||||
void __stdcall _com_issue_error(HRESULT hr)
|
||||
{
|
||||
errmsg(hr, "Unexpected error in COM");
|
||||
}
|
||||
#endif
|
||||
|
||||
template<class T>
|
||||
HRESULT put_Value(ICatalogObject *pObj, LPCWSTR name, T val)
|
||||
{
|
||||
return pObj->put_Value(_bstr_t(name), _variant_t(val));
|
||||
}
|
||||
|
||||
/* Lookup Administrators group name from winmgmt */
|
||||
static HRESULT GetAdminName(_bstr_t *name)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
HRESULT hr;
|
||||
COMPointer<IWbemLocator> pLoc;
|
||||
COMPointer<IWbemServices> pSvc;
|
||||
COMPointer<IEnumWbemClassObject> pEnum;
|
||||
COMPointer<IWbemClassObject> pWobj;
|
||||
ULONG returned;
|
||||
_variant_t var;
|
||||
|
||||
chk(CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER,
|
||||
IID_IWbemLocator, (LPVOID *)pLoc.replace()));
|
||||
chk(pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, NULL,
|
||||
0, 0, 0, pSvc.replace()));
|
||||
chk(CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
|
||||
NULL, RPC_C_AUTHN_LEVEL_CALL,
|
||||
RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE));
|
||||
chk(pSvc->ExecQuery(_bstr_t(L"WQL"),
|
||||
_bstr_t(L"select * from Win32_Account where "
|
||||
"SID='S-1-5-32-544' and localAccount=TRUE"),
|
||||
WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY,
|
||||
NULL, pEnum.replace()));
|
||||
if (!pEnum) {
|
||||
hr = E_FAIL;
|
||||
errmsg(hr, "Failed to query for Administrators");
|
||||
goto out;
|
||||
}
|
||||
chk(pEnum->Next(WBEM_INFINITE, 1, pWobj.replace(), &returned));
|
||||
if (returned == 0) {
|
||||
hr = E_FAIL;
|
||||
errmsg(hr, "No Administrators found");
|
||||
goto out;
|
||||
}
|
||||
|
||||
chk(pWobj->Get(_bstr_t(L"Name"), 0, &var, 0, 0));
|
||||
try {
|
||||
*name = var;
|
||||
} catch(...) {
|
||||
hr = E_FAIL;
|
||||
errmsg(hr, "Failed to get name of Administrators");
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
qga_debug_end;
|
||||
return hr;
|
||||
}
|
||||
|
||||
/* Acquire group or user name by SID */
|
||||
static HRESULT getNameByStringSID(
|
||||
const wchar_t *sid, LPWSTR buffer, LPDWORD bufferLen)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
PSID psid = NULL;
|
||||
SID_NAME_USE groupType;
|
||||
DWORD domainNameLen = BUFFER_SIZE;
|
||||
wchar_t domainName[BUFFER_SIZE];
|
||||
|
||||
if (!ConvertStringSidToSidW(sid, &psid)) {
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
goto out;
|
||||
}
|
||||
if (!LookupAccountSidW(NULL, psid, buffer, bufferLen,
|
||||
domainName, &domainNameLen, &groupType)) {
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
/* Fall through and free psid */
|
||||
}
|
||||
|
||||
LocalFree(psid);
|
||||
|
||||
out:
|
||||
qga_debug_end;
|
||||
return hr;
|
||||
}
|
||||
|
||||
/* Find and iterate QGA VSS provider in COM+ Application Catalog */
|
||||
static HRESULT QGAProviderFind(
|
||||
HRESULT (*found)(ICatalogCollection *, int, void *), void *arg)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
HRESULT hr;
|
||||
COMInitializer initializer;
|
||||
COMPointer<IUnknown> pUnknown;
|
||||
COMPointer<ICOMAdminCatalog2> pCatalog;
|
||||
COMPointer<ICatalogCollection> pColl;
|
||||
COMPointer<ICatalogObject> pObj;
|
||||
_variant_t var;
|
||||
long i, n;
|
||||
|
||||
chk(CoCreateInstance(CLSID_COMAdminCatalog, NULL, CLSCTX_INPROC_SERVER,
|
||||
IID_IUnknown, (void **)pUnknown.replace()));
|
||||
chk(pUnknown->QueryInterface(IID_ICOMAdminCatalog2,
|
||||
(void **)pCatalog.replace()));
|
||||
chk(pCatalog->GetCollection(_bstr_t(L"Applications"),
|
||||
(IDispatch **)pColl.replace()));
|
||||
chk(pColl->Populate());
|
||||
|
||||
chk(pColl->get_Count(&n));
|
||||
for (i = n - 1; i >= 0; i--) {
|
||||
chk(pColl->get_Item(i, (IDispatch **)pObj.replace()));
|
||||
chk(pObj->get_Value(_bstr_t(L"Name"), &var));
|
||||
if (var == _variant_t(QGA_PROVIDER_LNAME)) {
|
||||
if (FAILED(found(pColl, i, arg))) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
chk(pColl->SaveChanges(&n));
|
||||
|
||||
out:
|
||||
qga_debug_end;
|
||||
return hr;
|
||||
}
|
||||
|
||||
/* Count QGA VSS provider in COM+ Application Catalog */
|
||||
static HRESULT QGAProviderCount(ICatalogCollection *coll, int i, void *arg)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
(*(int *)arg)++;
|
||||
|
||||
qga_debug_end;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/* Remove QGA VSS provider from COM+ Application Catalog Collection */
|
||||
static HRESULT QGAProviderRemove(ICatalogCollection *coll, int i, void *arg)
|
||||
{
|
||||
qga_debug_begin;
|
||||
HRESULT hr;
|
||||
|
||||
qga_debug("Removing COM+ Application: %s", QGA_PROVIDER_NAME);
|
||||
chk(coll->Remove(i));
|
||||
out:
|
||||
qga_debug_end;
|
||||
return hr;
|
||||
}
|
||||
|
||||
/* Unregister this module from COM+ Applications Catalog */
|
||||
STDAPI COMUnregister(void);
|
||||
STDAPI COMUnregister(void)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
HRESULT hr;
|
||||
|
||||
DllUnregisterServer();
|
||||
chk(QGAProviderFind(QGAProviderRemove, NULL));
|
||||
out:
|
||||
qga_debug_end;
|
||||
return hr;
|
||||
}
|
||||
|
||||
/* Register this module to COM+ Applications Catalog */
|
||||
STDAPI COMRegister(void);
|
||||
STDAPI COMRegister(void)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
HRESULT hr;
|
||||
COMInitializer initializer;
|
||||
COMPointer<IUnknown> pUnknown;
|
||||
COMPointer<ICOMAdminCatalog2> pCatalog;
|
||||
COMPointer<ICatalogCollection> pApps, pRoles, pUsersInRole;
|
||||
COMPointer<ICatalogObject> pObj;
|
||||
long n;
|
||||
_bstr_t name;
|
||||
_variant_t key;
|
||||
CHAR dllPath[MAX_PATH], tlbPath[MAX_PATH];
|
||||
bool unregisterOnFailure = false;
|
||||
int count = 0;
|
||||
DWORD bufferLen = BUFFER_SIZE;
|
||||
wchar_t buffer[BUFFER_SIZE];
|
||||
const wchar_t *administratorsGroupSID = L"S-1-5-32-544";
|
||||
const wchar_t *systemUserSID = L"S-1-5-18";
|
||||
|
||||
if (!g_hinstDll) {
|
||||
errmsg(E_FAIL, "Failed to initialize DLL");
|
||||
qga_debug_end;
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
chk(QGAProviderFind(QGAProviderCount, (void *)&count));
|
||||
if (count) {
|
||||
qga_debug("QGA VSS Provider is already installed. Attempting to unregister first.");
|
||||
hr = COMUnregister();
|
||||
if (FAILED(hr)) {
|
||||
errmsg(hr, "Failed to unregister existing QGA VSS Provider. Aborting installation.");
|
||||
qga_debug_end;
|
||||
return E_ABORT;
|
||||
}
|
||||
}
|
||||
|
||||
chk(CoCreateInstance(CLSID_COMAdminCatalog, NULL, CLSCTX_INPROC_SERVER,
|
||||
IID_IUnknown, (void **)pUnknown.replace()));
|
||||
chk(pUnknown->QueryInterface(IID_ICOMAdminCatalog2,
|
||||
(void **)pCatalog.replace()));
|
||||
|
||||
/* Install COM+ Component */
|
||||
|
||||
chk(pCatalog->GetCollection(_bstr_t(L"Applications"),
|
||||
(IDispatch **)pApps.replace()));
|
||||
chk(pApps->Populate());
|
||||
chk(pApps->Add((IDispatch **)&pObj));
|
||||
chk(put_Value(pObj, L"Name", QGA_PROVIDER_LNAME));
|
||||
chk(put_Value(pObj, L"Description", QGA_PROVIDER_LNAME));
|
||||
chk(put_Value(pObj, L"ApplicationAccessChecksEnabled", true));
|
||||
chk(put_Value(pObj, L"Authentication", short(6)));
|
||||
chk(put_Value(pObj, L"AuthenticationCapability", short(2)));
|
||||
chk(put_Value(pObj, L"ImpersonationLevel", short(2)));
|
||||
chk(pApps->SaveChanges(&n));
|
||||
|
||||
/* The app should be deleted if something fails after SaveChanges */
|
||||
unregisterOnFailure = true;
|
||||
|
||||
chk(pObj->get_Key(&key));
|
||||
|
||||
if (!GetModuleFileName(g_hinstDll, dllPath, sizeof(dllPath))) {
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
errmsg(hr, "GetModuleFileName failed");
|
||||
goto out;
|
||||
}
|
||||
n = strlen(dllPath);
|
||||
if (n < 3) {
|
||||
hr = E_FAIL;
|
||||
errmsg(hr, "Failed to lookup dll");
|
||||
goto out;
|
||||
}
|
||||
strcpy(tlbPath, dllPath);
|
||||
strcpy(tlbPath+n-3, "tlb");
|
||||
qga_debug("Registering " QGA_PROVIDER_NAME ": %s %s",
|
||||
dllPath, tlbPath);
|
||||
if (!PathFileExists(tlbPath)) {
|
||||
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
|
||||
errmsg(hr, "Failed to lookup tlb");
|
||||
goto out;
|
||||
}
|
||||
|
||||
chk(pCatalog->CreateServiceForApplication(
|
||||
_bstr_t(QGA_PROVIDER_LNAME), _bstr_t(QGA_PROVIDER_LNAME),
|
||||
_bstr_t(L"SERVICE_DEMAND_START"), _bstr_t(L"SERVICE_ERROR_NORMAL"),
|
||||
_bstr_t(L""), _bstr_t(L".\\localsystem"), _bstr_t(L""), FALSE));
|
||||
chk(pCatalog->InstallComponent(_bstr_t(QGA_PROVIDER_LNAME),
|
||||
_bstr_t(dllPath), _bstr_t(tlbPath),
|
||||
_bstr_t("")));
|
||||
|
||||
/* Setup roles of the application */
|
||||
|
||||
chk(getNameByStringSID(administratorsGroupSID, buffer, &bufferLen));
|
||||
chk(pApps->GetCollection(_bstr_t(L"Roles"), key,
|
||||
(IDispatch **)pRoles.replace()));
|
||||
chk(pRoles->Populate());
|
||||
chk(pRoles->Add((IDispatch **)pObj.replace()));
|
||||
chk(put_Value(pObj, L"Name", buffer));
|
||||
chk(put_Value(pObj, L"Description", L"Administrators group"));
|
||||
chk(pRoles->SaveChanges(&n));
|
||||
chk(pObj->get_Key(&key));
|
||||
|
||||
/* Setup users in the role */
|
||||
|
||||
chk(pRoles->GetCollection(_bstr_t(L"UsersInRole"), key,
|
||||
(IDispatch **)pUsersInRole.replace()));
|
||||
chk(pUsersInRole->Populate());
|
||||
|
||||
chk(pUsersInRole->Add((IDispatch **)pObj.replace()));
|
||||
chk(GetAdminName(&name));
|
||||
chk(put_Value(pObj, L"User", _bstr_t(".\\") + name));
|
||||
|
||||
bufferLen = BUFFER_SIZE;
|
||||
chk(getNameByStringSID(systemUserSID, buffer, &bufferLen));
|
||||
chk(pUsersInRole->Add((IDispatch **)pObj.replace()));
|
||||
chk(put_Value(pObj, L"User", buffer));
|
||||
chk(pUsersInRole->SaveChanges(&n));
|
||||
|
||||
out:
|
||||
if (unregisterOnFailure && FAILED(hr)) {
|
||||
COMUnregister();
|
||||
}
|
||||
|
||||
qga_debug_end;
|
||||
return hr;
|
||||
}
|
||||
|
||||
STDAPI_(void) CALLBACK DLLCOMRegister(HWND, HINSTANCE, LPSTR, int);
|
||||
STDAPI_(void) CALLBACK DLLCOMRegister(HWND, HINSTANCE, LPSTR, int)
|
||||
{
|
||||
HRESULT hr = COMRegister();
|
||||
if (FAILED(hr)) {
|
||||
exit(hr);
|
||||
}
|
||||
}
|
||||
|
||||
STDAPI_(void) CALLBACK DLLCOMUnregister(HWND, HINSTANCE, LPSTR, int);
|
||||
STDAPI_(void) CALLBACK DLLCOMUnregister(HWND, HINSTANCE, LPSTR, int)
|
||||
{
|
||||
COMUnregister();
|
||||
}
|
||||
|
||||
static BOOL CreateRegistryKey(LPCTSTR key, LPCTSTR value, LPCTSTR data)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
HKEY hKey;
|
||||
LONG ret;
|
||||
DWORD size;
|
||||
|
||||
ret = RegCreateKeyEx(HKEY_CLASSES_ROOT, key, 0, NULL,
|
||||
REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (data != NULL) {
|
||||
size = strlen(data) + 1;
|
||||
} else {
|
||||
size = 0;
|
||||
}
|
||||
|
||||
ret = RegSetValueEx(hKey, value, 0, REG_SZ, (LPBYTE)data, size);
|
||||
RegCloseKey(hKey);
|
||||
|
||||
out:
|
||||
qga_debug_end;
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
/* As we cannot printf within DllRegisterServer(), show a dialog. */
|
||||
errmsg_dialog(ret, "Cannot add registry", key);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Register this dll as a VSS provider */
|
||||
STDAPI DllRegisterServer(void)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
COMInitializer initializer;
|
||||
COMPointer<IVssAdmin> pVssAdmin;
|
||||
HRESULT hr = E_FAIL;
|
||||
char dllPath[MAX_PATH];
|
||||
char key[256];
|
||||
|
||||
if (!g_hinstDll) {
|
||||
errmsg_dialog(hr, "Module instance is not available");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Add this module to registry */
|
||||
|
||||
sprintf(key, "CLSID\\%s", g_szClsid);
|
||||
if (!CreateRegistryKey(key, NULL, g_szClsid)) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!GetModuleFileName(g_hinstDll, dllPath, sizeof(dllPath))) {
|
||||
errmsg_dialog(GetLastError(), "GetModuleFileName failed");
|
||||
goto out;
|
||||
}
|
||||
|
||||
sprintf(key, "CLSID\\%s\\InprocServer32", g_szClsid);
|
||||
if (!CreateRegistryKey(key, NULL, dllPath)) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!CreateRegistryKey(key, "ThreadingModel", "Apartment")) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
sprintf(key, "CLSID\\%s\\ProgID", g_szClsid);
|
||||
if (!CreateRegistryKey(key, NULL, g_szProgid)) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!CreateRegistryKey(g_szProgid, NULL, QGA_PROVIDER_NAME)) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
sprintf(key, "%s\\CLSID", g_szProgid);
|
||||
if (!CreateRegistryKey(key, NULL, g_szClsid)) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
hr = CoCreateInstance(CLSID_VSSCoordinator, NULL, CLSCTX_ALL,
|
||||
IID_IVssAdmin, (void **)pVssAdmin.replace());
|
||||
if (FAILED(hr)) {
|
||||
errmsg_dialog(hr, "CoCreateInstance(VSSCoordinator) failed");
|
||||
goto out;
|
||||
}
|
||||
|
||||
hr = pVssAdmin->RegisterProvider(g_gProviderId, CLSID_QGAVSSProvider,
|
||||
const_cast<WCHAR*>(QGA_PROVIDER_LNAME),
|
||||
VSS_PROV_SOFTWARE,
|
||||
const_cast<WCHAR*>(QGA_PROVIDER_VERSION),
|
||||
g_gProviderVersion);
|
||||
if (hr == (long int) VSS_E_PROVIDER_ALREADY_REGISTERED) {
|
||||
DllUnregisterServer();
|
||||
hr = pVssAdmin->RegisterProvider(g_gProviderId, CLSID_QGAVSSProvider,
|
||||
const_cast<WCHAR * >
|
||||
(QGA_PROVIDER_LNAME),
|
||||
VSS_PROV_SOFTWARE,
|
||||
const_cast<WCHAR * >
|
||||
(QGA_PROVIDER_VERSION),
|
||||
g_gProviderVersion);
|
||||
}
|
||||
|
||||
if (FAILED(hr)) {
|
||||
errmsg_dialog(hr, "RegisterProvider failed");
|
||||
}
|
||||
|
||||
out:
|
||||
if (FAILED(hr)) {
|
||||
DllUnregisterServer();
|
||||
}
|
||||
|
||||
qga_debug_end;
|
||||
return hr;
|
||||
}
|
||||
|
||||
/* Unregister this VSS hardware provider from the system */
|
||||
STDAPI DllUnregisterServer(void)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
TCHAR key[256];
|
||||
COMInitializer initializer;
|
||||
COMPointer<IVssAdmin> pVssAdmin;
|
||||
|
||||
HRESULT hr = CoCreateInstance(CLSID_VSSCoordinator,
|
||||
NULL, CLSCTX_ALL, IID_IVssAdmin,
|
||||
(void **)pVssAdmin.replace());
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = pVssAdmin->UnregisterProvider(g_gProviderId);
|
||||
} else {
|
||||
errmsg(hr, "CoCreateInstance(VSSCoordinator) failed");
|
||||
}
|
||||
|
||||
sprintf(key, "CLSID\\%s", g_szClsid);
|
||||
SHDeleteKey(HKEY_CLASSES_ROOT, key);
|
||||
SHDeleteKey(HKEY_CLASSES_ROOT, g_szProgid);
|
||||
|
||||
qga_debug_end;
|
||||
return S_OK; /* Uninstall should never fail */
|
||||
}
|
||||
|
||||
|
||||
/* Support function to convert ASCII string into BSTR (used in _bstr_t) */
|
||||
#ifndef CONFIG_CONVERT_STRING_TO_BSTR
|
||||
namespace _com_util
|
||||
{
|
||||
BSTR WINAPI ConvertStringToBSTR(const char *ascii) {
|
||||
int len = strlen(ascii);
|
||||
BSTR bstr = SysAllocStringLen(NULL, len);
|
||||
|
||||
if (!bstr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (mbstowcs(bstr, ascii, len) == (size_t)-1) {
|
||||
qga_debug("Failed to convert string '%s' into BSTR", ascii);
|
||||
bstr[0] = 0;
|
||||
}
|
||||
return bstr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Stop QGA VSS provider service using Winsvc API */
|
||||
STDAPI StopService(void)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
SC_HANDLE manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
||||
SC_HANDLE service = NULL;
|
||||
|
||||
if (!manager) {
|
||||
errmsg(E_FAIL, "Failed to open service manager");
|
||||
hr = E_FAIL;
|
||||
goto out;
|
||||
}
|
||||
service = OpenService(manager, QGA_PROVIDER_NAME, SC_MANAGER_ALL_ACCESS);
|
||||
|
||||
if (!service) {
|
||||
errmsg(E_FAIL, "Failed to open service");
|
||||
hr = E_FAIL;
|
||||
goto out;
|
||||
}
|
||||
if (!(ControlService(service, SERVICE_CONTROL_STOP, NULL))) {
|
||||
errmsg(E_FAIL, "Failed to stop service");
|
||||
hr = E_FAIL;
|
||||
}
|
||||
|
||||
out:
|
||||
CloseServiceHandle(service);
|
||||
CloseServiceHandle(manager);
|
||||
qga_debug_end;
|
||||
return hr;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* QEMU Guest Agent VSS requester declarations
|
||||
*
|
||||
* Copyright Hitachi Data Systems Corp. 2013
|
||||
*
|
||||
* Authors:
|
||||
* Tomoki Sekiyama <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#ifndef INSTALL_H
|
||||
#define INSTALL_H
|
||||
|
||||
#include <comadmin.h>
|
||||
|
||||
STDAPI StopService(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,36 @@
|
||||
link_args = cc.get_supported_link_arguments([
|
||||
'-fstack-protector-all',
|
||||
'-fstack-protector-strong',
|
||||
'-Wl,--add-stdcall-alias',
|
||||
'-Wl,--enable-stdcall-fixup'
|
||||
])
|
||||
|
||||
qga_vss = shared_module(
|
||||
'qga-vss',
|
||||
['requester.cpp', 'provider.cpp', 'install.cpp', 'vss-debug.cpp', genh],
|
||||
name_prefix: '',
|
||||
cpp_args: ['-Wno-unknown-pragmas', '-Wno-delete-non-virtual-dtor', '-Wno-non-virtual-dtor'],
|
||||
link_args: link_args,
|
||||
vs_module_defs: 'qga-vss.def',
|
||||
dependencies: [
|
||||
socket,
|
||||
cc.find_library('ole32'),
|
||||
cc.find_library('oleaut32'),
|
||||
cc.find_library('shlwapi'),
|
||||
cc.find_library('uuid')
|
||||
]
|
||||
)
|
||||
|
||||
if midl.found()
|
||||
gen_tlb = custom_target('gen-tlb',
|
||||
input: 'qga-vss.idl',
|
||||
output: 'qga-vss.tlb',
|
||||
command: [midl, '@INPUT@', '/tlb', '@OUTPUT@'])
|
||||
else
|
||||
gen_tlb = custom_target('gen-tlb',
|
||||
input: 'qga-vss.idl',
|
||||
output: 'qga-vss.tlb',
|
||||
command: [widl, '-t', '@INPUT@', '-o', '@OUTPUT@'])
|
||||
endif
|
||||
|
||||
all_qga += [ qga_vss, gen_tlb ]
|
||||
@@ -0,0 +1,543 @@
|
||||
/*
|
||||
* QEMU Guest Agent win32 VSS Provider implementations
|
||||
*
|
||||
* Copyright Hitachi Data Systems Corp. 2013
|
||||
*
|
||||
* Authors:
|
||||
* Tomoki Sekiyama <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "vss-common.h"
|
||||
#include "vss-debug.h"
|
||||
#ifdef HAVE_VSS_SDK
|
||||
#include <vscoordint.h>
|
||||
#else
|
||||
#include <vsadmin.h>
|
||||
#endif
|
||||
#include <vsprov.h>
|
||||
|
||||
#define VSS_TIMEOUT_MSEC (60*1000)
|
||||
|
||||
static long g_nComObjsInUse;
|
||||
HINSTANCE g_hinstDll;
|
||||
|
||||
/* VSS common GUID's */
|
||||
|
||||
const CLSID CLSID_VSSCoordinator = { 0xE579AB5F, 0x1CC4, 0x44b4,
|
||||
{0xBE, 0xD9, 0xDE, 0x09, 0x91, 0xFF, 0x06, 0x23} };
|
||||
const IID IID_IVssAdmin = { 0x77ED5996, 0x2F63, 0x11d3,
|
||||
{0x8A, 0x39, 0x00, 0xC0, 0x4F, 0x72, 0xD8, 0xE3} };
|
||||
|
||||
const IID IID_IVssHardwareSnapshotProvider = { 0x9593A157, 0x44E9, 0x4344,
|
||||
{0xBB, 0xEB, 0x44, 0xFB, 0xF9, 0xB0, 0x6B, 0x10} };
|
||||
const IID IID_IVssSoftwareSnapshotProvider = { 0x609e123e, 0x2c5a, 0x44d3,
|
||||
{0x8f, 0x01, 0x0b, 0x1d, 0x9a, 0x47, 0xd1, 0xff} };
|
||||
const IID IID_IVssProviderCreateSnapshotSet = { 0x5F894E5B, 0x1E39, 0x4778,
|
||||
{0x8E, 0x23, 0x9A, 0xBA, 0xD9, 0xF0, 0xE0, 0x8C} };
|
||||
const IID IID_IVssProviderNotifications = { 0xE561901F, 0x03A5, 0x4afe,
|
||||
{0x86, 0xD0, 0x72, 0xBA, 0xEE, 0xCE, 0x70, 0x04} };
|
||||
|
||||
const IID IID_IVssEnumObject = { 0xAE1C7110, 0x2F60, 0x11d3,
|
||||
{0x8A, 0x39, 0x00, 0xC0, 0x4F, 0x72, 0xD8, 0xE3} };
|
||||
|
||||
|
||||
static void LockModule(BOOL lock)
|
||||
{
|
||||
if (lock) {
|
||||
InterlockedIncrement(&g_nComObjsInUse);
|
||||
} else {
|
||||
InterlockedDecrement(&g_nComObjsInUse);
|
||||
}
|
||||
}
|
||||
|
||||
/* Empty enumerator for VssObject */
|
||||
|
||||
class CQGAVSSEnumObject : public IVssEnumObject
|
||||
{
|
||||
public:
|
||||
STDMETHODIMP QueryInterface(REFIID riid, void **ppObj);
|
||||
STDMETHODIMP_(ULONG) AddRef();
|
||||
STDMETHODIMP_(ULONG) Release();
|
||||
|
||||
/* IVssEnumObject Methods */
|
||||
STDMETHODIMP Next(
|
||||
ULONG celt, VSS_OBJECT_PROP *rgelt, ULONG *pceltFetched);
|
||||
STDMETHODIMP Skip(ULONG celt);
|
||||
STDMETHODIMP Reset(void);
|
||||
STDMETHODIMP Clone(IVssEnumObject **ppenum);
|
||||
|
||||
/* CQGAVSSEnumObject Methods */
|
||||
CQGAVSSEnumObject();
|
||||
~CQGAVSSEnumObject();
|
||||
|
||||
private:
|
||||
long m_nRefCount;
|
||||
};
|
||||
|
||||
CQGAVSSEnumObject::CQGAVSSEnumObject()
|
||||
{
|
||||
m_nRefCount = 0;
|
||||
LockModule(TRUE);
|
||||
}
|
||||
|
||||
CQGAVSSEnumObject::~CQGAVSSEnumObject()
|
||||
{
|
||||
LockModule(FALSE);
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVSSEnumObject::QueryInterface(REFIID riid, void **ppObj)
|
||||
{
|
||||
if (riid == IID_IUnknown || riid == IID_IVssEnumObject) {
|
||||
*ppObj = static_cast<void*>(static_cast<IVssEnumObject*>(this));
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
*ppObj = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
STDMETHODIMP_(ULONG) CQGAVSSEnumObject::AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&m_nRefCount);
|
||||
}
|
||||
|
||||
STDMETHODIMP_(ULONG) CQGAVSSEnumObject::Release()
|
||||
{
|
||||
long nRefCount = InterlockedDecrement(&m_nRefCount);
|
||||
if (m_nRefCount == 0) {
|
||||
delete this;
|
||||
}
|
||||
return nRefCount;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVSSEnumObject::Next(
|
||||
ULONG celt, VSS_OBJECT_PROP *rgelt, ULONG *pceltFetched)
|
||||
{
|
||||
*pceltFetched = 0;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVSSEnumObject::Skip(ULONG celt)
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVSSEnumObject::Reset(void)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVSSEnumObject::Clone(IVssEnumObject **ppenum)
|
||||
{
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
|
||||
/* QGAVssProvider */
|
||||
|
||||
class CQGAVssProvider :
|
||||
public IVssSoftwareSnapshotProvider,
|
||||
public IVssProviderCreateSnapshotSet,
|
||||
public IVssProviderNotifications
|
||||
{
|
||||
public:
|
||||
STDMETHODIMP QueryInterface(REFIID riid, void **ppObj);
|
||||
STDMETHODIMP_(ULONG) AddRef();
|
||||
STDMETHODIMP_(ULONG) Release();
|
||||
|
||||
/* IVssSoftwareSnapshotProvider Methods */
|
||||
STDMETHODIMP SetContext(LONG lContext);
|
||||
STDMETHODIMP GetSnapshotProperties(
|
||||
VSS_ID SnapshotId, VSS_SNAPSHOT_PROP *pProp);
|
||||
STDMETHODIMP Query(
|
||||
VSS_ID QueriedObjectId, VSS_OBJECT_TYPE eQueriedObjectType,
|
||||
VSS_OBJECT_TYPE eReturnedObjectsType, IVssEnumObject **ppEnum);
|
||||
STDMETHODIMP DeleteSnapshots(
|
||||
VSS_ID SourceObjectId, VSS_OBJECT_TYPE eSourceObjectType,
|
||||
BOOL bForceDelete, LONG *plDeletedSnapshots,
|
||||
VSS_ID *pNondeletedSnapshotID);
|
||||
STDMETHODIMP BeginPrepareSnapshot(
|
||||
VSS_ID SnapshotSetId, VSS_ID SnapshotId,
|
||||
VSS_PWSZ pwszVolumeName, LONG lNewContext);
|
||||
STDMETHODIMP IsVolumeSupported(
|
||||
VSS_PWSZ pwszVolumeName, BOOL *pbSupportedByThisProvider);
|
||||
STDMETHODIMP IsVolumeSnapshotted(
|
||||
VSS_PWSZ pwszVolumeName, BOOL *pbSnapshotsPresent,
|
||||
LONG *plSnapshotCompatibility);
|
||||
STDMETHODIMP SetSnapshotProperty(
|
||||
VSS_ID SnapshotId, VSS_SNAPSHOT_PROPERTY_ID eSnapshotPropertyId,
|
||||
VARIANT vProperty);
|
||||
STDMETHODIMP RevertToSnapshot(VSS_ID SnapshotId);
|
||||
STDMETHODIMP QueryRevertStatus(VSS_PWSZ pwszVolume, IVssAsync **ppAsync);
|
||||
|
||||
/* IVssProviderCreateSnapshotSet Methods */
|
||||
STDMETHODIMP EndPrepareSnapshots(VSS_ID SnapshotSetId);
|
||||
STDMETHODIMP PreCommitSnapshots(VSS_ID SnapshotSetId);
|
||||
STDMETHODIMP CommitSnapshots(VSS_ID SnapshotSetId);
|
||||
STDMETHODIMP PostCommitSnapshots(
|
||||
VSS_ID SnapshotSetId, LONG lSnapshotsCount);
|
||||
STDMETHODIMP PreFinalCommitSnapshots(VSS_ID SnapshotSetId);
|
||||
STDMETHODIMP PostFinalCommitSnapshots(VSS_ID SnapshotSetId);
|
||||
STDMETHODIMP AbortSnapshots(VSS_ID SnapshotSetId);
|
||||
|
||||
/* IVssProviderNotifications Methods */
|
||||
STDMETHODIMP OnLoad(IUnknown *pCallback);
|
||||
STDMETHODIMP OnUnload(BOOL bForceUnload);
|
||||
|
||||
/* CQGAVssProvider Methods */
|
||||
CQGAVssProvider();
|
||||
~CQGAVssProvider();
|
||||
|
||||
private:
|
||||
long m_nRefCount;
|
||||
};
|
||||
|
||||
CQGAVssProvider::CQGAVssProvider()
|
||||
{
|
||||
m_nRefCount = 0;
|
||||
LockModule(TRUE);
|
||||
}
|
||||
|
||||
CQGAVssProvider::~CQGAVssProvider()
|
||||
{
|
||||
LockModule(FALSE);
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::QueryInterface(REFIID riid, void **ppObj)
|
||||
{
|
||||
if (riid == IID_IUnknown) {
|
||||
*ppObj = static_cast<void*>(this);
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
if (riid == IID_IVssSoftwareSnapshotProvider) {
|
||||
*ppObj = static_cast<void*>(
|
||||
static_cast<IVssSoftwareSnapshotProvider*>(this));
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
if (riid == IID_IVssProviderCreateSnapshotSet) {
|
||||
*ppObj = static_cast<void*>(
|
||||
static_cast<IVssProviderCreateSnapshotSet*>(this));
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
if (riid == IID_IVssProviderNotifications) {
|
||||
*ppObj = static_cast<void*>(
|
||||
static_cast<IVssProviderNotifications*>(this));
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
*ppObj = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
STDMETHODIMP_(ULONG) CQGAVssProvider::AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&m_nRefCount);
|
||||
}
|
||||
|
||||
STDMETHODIMP_(ULONG) CQGAVssProvider::Release()
|
||||
{
|
||||
long nRefCount = InterlockedDecrement(&m_nRefCount);
|
||||
if (m_nRefCount == 0) {
|
||||
delete this;
|
||||
}
|
||||
return nRefCount;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* IVssSoftwareSnapshotProvider methods
|
||||
*/
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::SetContext(LONG lContext)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::GetSnapshotProperties(
|
||||
VSS_ID SnapshotId, VSS_SNAPSHOT_PROP *pProp)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::Query(
|
||||
VSS_ID QueriedObjectId, VSS_OBJECT_TYPE eQueriedObjectType,
|
||||
VSS_OBJECT_TYPE eReturnedObjectsType, IVssEnumObject **ppEnum)
|
||||
{
|
||||
try {
|
||||
*ppEnum = new CQGAVSSEnumObject;
|
||||
} catch (...) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
(*ppEnum)->AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::DeleteSnapshots(
|
||||
VSS_ID SourceObjectId, VSS_OBJECT_TYPE eSourceObjectType,
|
||||
BOOL bForceDelete, LONG *plDeletedSnapshots, VSS_ID *pNondeletedSnapshotID)
|
||||
{
|
||||
*plDeletedSnapshots = 0;
|
||||
*pNondeletedSnapshotID = SourceObjectId;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::BeginPrepareSnapshot(
|
||||
VSS_ID SnapshotSetId, VSS_ID SnapshotId,
|
||||
VSS_PWSZ pwszVolumeName, LONG lNewContext)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::IsVolumeSupported(
|
||||
VSS_PWSZ pwszVolumeName, BOOL *pbSupportedByThisProvider)
|
||||
{
|
||||
HANDLE hEventFrozen;
|
||||
|
||||
/* Check if a requester is qemu-ga by whether an event is created */
|
||||
hEventFrozen = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_FROZEN);
|
||||
if (!hEventFrozen) {
|
||||
*pbSupportedByThisProvider = FALSE;
|
||||
return S_OK;
|
||||
}
|
||||
CloseHandle(hEventFrozen);
|
||||
|
||||
*pbSupportedByThisProvider = TRUE;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::IsVolumeSnapshotted(VSS_PWSZ pwszVolumeName,
|
||||
BOOL *pbSnapshotsPresent, LONG *plSnapshotCompatibility)
|
||||
{
|
||||
*pbSnapshotsPresent = FALSE;
|
||||
*plSnapshotCompatibility = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::SetSnapshotProperty(VSS_ID SnapshotId,
|
||||
VSS_SNAPSHOT_PROPERTY_ID eSnapshotPropertyId, VARIANT vProperty)
|
||||
{
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::RevertToSnapshot(VSS_ID SnapshotId)
|
||||
{
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::QueryRevertStatus(
|
||||
VSS_PWSZ pwszVolume, IVssAsync **ppAsync)
|
||||
{
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* IVssProviderCreateSnapshotSet methods
|
||||
*/
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::EndPrepareSnapshots(VSS_ID SnapshotSetId)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::PreCommitSnapshots(VSS_ID SnapshotSetId)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::CommitSnapshots(VSS_ID SnapshotSetId)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
HANDLE hEventFrozen, hEventThaw, hEventTimeout;
|
||||
|
||||
hEventFrozen = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_FROZEN);
|
||||
if (!hEventFrozen) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
hEventThaw = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_THAW);
|
||||
if (!hEventThaw) {
|
||||
CloseHandle(hEventFrozen);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
hEventTimeout = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_TIMEOUT);
|
||||
if (!hEventTimeout) {
|
||||
CloseHandle(hEventFrozen);
|
||||
CloseHandle(hEventThaw);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
/* Send event to qemu-ga to notify filesystem is frozen */
|
||||
SetEvent(hEventFrozen);
|
||||
|
||||
/* Wait until the snapshot is taken by the host. */
|
||||
if (WaitForSingleObject(hEventThaw, VSS_TIMEOUT_MSEC) != WAIT_OBJECT_0) {
|
||||
/* Send event to qemu-ga to notify the provider is timed out */
|
||||
SetEvent(hEventTimeout);
|
||||
}
|
||||
|
||||
CloseHandle(hEventThaw);
|
||||
CloseHandle(hEventFrozen);
|
||||
CloseHandle(hEventTimeout);
|
||||
return hr;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::PostCommitSnapshots(
|
||||
VSS_ID SnapshotSetId, LONG lSnapshotsCount)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::PreFinalCommitSnapshots(VSS_ID SnapshotSetId)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::PostFinalCommitSnapshots(VSS_ID SnapshotSetId)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::AbortSnapshots(VSS_ID SnapshotSetId)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* IVssProviderNotifications methods
|
||||
*/
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::OnLoad(IUnknown *pCallback)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProvider::OnUnload(BOOL bForceUnload)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CQGAVssProviderFactory class
|
||||
*/
|
||||
|
||||
class CQGAVssProviderFactory : public IClassFactory
|
||||
{
|
||||
public:
|
||||
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
|
||||
STDMETHODIMP_(ULONG) AddRef();
|
||||
STDMETHODIMP_(ULONG) Release();
|
||||
STDMETHODIMP CreateInstance(
|
||||
IUnknown *pUnknownOuter, REFIID iid, void **ppv);
|
||||
STDMETHODIMP LockServer(BOOL lock) { return E_NOTIMPL; }
|
||||
|
||||
CQGAVssProviderFactory();
|
||||
~CQGAVssProviderFactory();
|
||||
|
||||
private:
|
||||
long m_nRefCount;
|
||||
};
|
||||
|
||||
CQGAVssProviderFactory::CQGAVssProviderFactory()
|
||||
{
|
||||
m_nRefCount = 0;
|
||||
LockModule(TRUE);
|
||||
}
|
||||
|
||||
CQGAVssProviderFactory::~CQGAVssProviderFactory()
|
||||
{
|
||||
LockModule(FALSE);
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProviderFactory::QueryInterface(REFIID riid, void **ppv)
|
||||
{
|
||||
if (riid == IID_IUnknown || riid == IID_IClassFactory) {
|
||||
*ppv = static_cast<void*>(this);
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
*ppv = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
STDMETHODIMP_(ULONG) CQGAVssProviderFactory::AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&m_nRefCount);
|
||||
}
|
||||
|
||||
STDMETHODIMP_(ULONG) CQGAVssProviderFactory::Release()
|
||||
{
|
||||
long nRefCount = InterlockedDecrement(&m_nRefCount);
|
||||
if (m_nRefCount == 0) {
|
||||
delete this;
|
||||
}
|
||||
return nRefCount;
|
||||
}
|
||||
|
||||
STDMETHODIMP CQGAVssProviderFactory::CreateInstance(
|
||||
IUnknown *pUnknownOuter, REFIID iid, void **ppv)
|
||||
{
|
||||
CQGAVssProvider *pObj;
|
||||
|
||||
if (pUnknownOuter) {
|
||||
return CLASS_E_NOAGGREGATION;
|
||||
}
|
||||
try {
|
||||
pObj = new CQGAVssProvider;
|
||||
} catch (...) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
HRESULT hr = pObj->QueryInterface(iid, ppv);
|
||||
if (FAILED(hr)) {
|
||||
delete pObj;
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DLL functions
|
||||
*/
|
||||
|
||||
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv)
|
||||
{
|
||||
CQGAVssProviderFactory *factory;
|
||||
try {
|
||||
factory = new CQGAVssProviderFactory;
|
||||
} catch (...) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
factory->AddRef();
|
||||
HRESULT hr = factory->QueryInterface(riid, ppv);
|
||||
factory->Release();
|
||||
return hr;
|
||||
}
|
||||
|
||||
STDAPI DllCanUnloadNow()
|
||||
{
|
||||
return g_nComObjsInUse == 0 ? S_OK : S_FALSE;
|
||||
}
|
||||
|
||||
EXTERN_C
|
||||
BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD dwReason, LPVOID lpReserved);
|
||||
|
||||
EXTERN_C
|
||||
BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD dwReason, LPVOID lpReserved)
|
||||
{
|
||||
qga_debug("begin, reason = %lu", dwReason);
|
||||
if (dwReason == DLL_PROCESS_ATTACH) {
|
||||
g_hinstDll = hinstDll;
|
||||
DisableThreadLibraryCalls(hinstDll);
|
||||
}
|
||||
qga_debug_end;
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
LIBRARY "QGA-PROVIDER.DLL"
|
||||
|
||||
EXPORTS
|
||||
DLLCOMRegister
|
||||
DLLCOMUnregister
|
||||
COMRegister PRIVATE
|
||||
COMUnregister PRIVATE
|
||||
DllCanUnloadNow PRIVATE
|
||||
DllGetClassObject PRIVATE
|
||||
DllRegisterServer PRIVATE
|
||||
DllUnregisterServer PRIVATE
|
||||
requester_init PRIVATE
|
||||
requester_deinit PRIVATE
|
||||
requester_freeze PRIVATE
|
||||
requester_thaw PRIVATE
|
||||
@@ -0,0 +1,20 @@
|
||||
import "oaidl.idl";
|
||||
import "ocidl.idl";
|
||||
|
||||
[
|
||||
uuid(103B8142-6CE5-48A7-BDE1-794D3192FCF1),
|
||||
version(1.0),
|
||||
helpstring("QGAVSSProvider Type Library")
|
||||
]
|
||||
library QGAVSSHWProviderLib
|
||||
{
|
||||
importlib("stdole2.tlb");
|
||||
[
|
||||
uuid(6E6A3492-8D4D-440C-9619-5E5D0CC31CA8),
|
||||
helpstring("QGAVSSProvider Class")
|
||||
]
|
||||
coclass QGAVSSHWProvider
|
||||
{
|
||||
[default] interface IUnknown;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,637 @@
|
||||
/*
|
||||
* QEMU Guest Agent win32 VSS Requester implementations
|
||||
*
|
||||
* Copyright Hitachi Data Systems Corp. 2013
|
||||
*
|
||||
* Authors:
|
||||
* Tomoki Sekiyama <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "vss-common.h"
|
||||
#include "vss-debug.h"
|
||||
#include "requester.h"
|
||||
#include "install.h"
|
||||
#include <vswriter.h>
|
||||
#include <vsbackup.h>
|
||||
|
||||
/* Max wait time for frozen event (VSS can only hold writes for 10 seconds) */
|
||||
#define VSS_TIMEOUT_FREEZE_MSEC 60000
|
||||
|
||||
/* Call QueryStatus every 10 ms while waiting for frozen event */
|
||||
#define VSS_TIMEOUT_EVENT_MSEC 10
|
||||
|
||||
#define DEFAULT_VSS_BACKUP_TYPE VSS_BT_FULL
|
||||
|
||||
#define err_set(e, err, fmt, ...) { \
|
||||
(e)->error_setg_win32_wrapper((e)->errp, __FILE__, __LINE__, __func__, \
|
||||
err, fmt ": Windows error 0x%lx", \
|
||||
## __VA_ARGS__, err); \
|
||||
qga_debug(fmt ": Windows error 0x%lx", ## __VA_ARGS__, err); \
|
||||
}
|
||||
/* Bad idea, works only when (e)->errp != NULL: */
|
||||
#define err_is_set(e) ((e)->errp && *(e)->errp)
|
||||
/* To lift this restriction, error_propagate(), like we do in QEMU code */
|
||||
|
||||
/* Handle to VSSAPI.DLL */
|
||||
static HMODULE hLib;
|
||||
|
||||
/* Functions in VSSAPI.DLL */
|
||||
typedef HRESULT(STDAPICALLTYPE * t_CreateVssBackupComponents)(
|
||||
OUT IVssBackupComponents**);
|
||||
typedef void(APIENTRY * t_VssFreeSnapshotProperties)(IN VSS_SNAPSHOT_PROP*);
|
||||
static t_CreateVssBackupComponents pCreateVssBackupComponents;
|
||||
static t_VssFreeSnapshotProperties pVssFreeSnapshotProperties;
|
||||
|
||||
/* Variables used while applications and filesystes are frozen by VSS */
|
||||
static struct QGAVSSContext {
|
||||
IVssBackupComponents *pVssbc; /* VSS requester interface */
|
||||
IVssAsync *pAsyncSnapshot; /* async info of VSS snapshot operation */
|
||||
HANDLE hEventFrozen; /* notify fs/writer freeze from provider */
|
||||
HANDLE hEventThaw; /* request provider to thaw */
|
||||
HANDLE hEventTimeout; /* notify timeout in provider */
|
||||
int cFrozenVols; /* number of frozen volumes */
|
||||
} vss_ctx;
|
||||
|
||||
STDAPI requester_init(void)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
hLib = LoadLibraryA("VSSAPI.DLL");
|
||||
if (!hLib) {
|
||||
qga_debug("failed to load VSSAPI.DLL");
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
pCreateVssBackupComponents = (t_CreateVssBackupComponents)
|
||||
GetProcAddress(hLib,
|
||||
#ifdef _WIN64 /* 64bit environment */
|
||||
"?CreateVssBackupComponents@@YAJPEAPEAVIVssBackupComponents@@@Z"
|
||||
#else /* 32bit environment */
|
||||
"?CreateVssBackupComponents@@YGJPAPAVIVssBackupComponents@@@Z"
|
||||
#endif
|
||||
);
|
||||
if (!pCreateVssBackupComponents) {
|
||||
qga_debug("failed to get proc address from VSSAPI.DLL");
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
pVssFreeSnapshotProperties = (t_VssFreeSnapshotProperties)
|
||||
GetProcAddress(hLib, "VssFreeSnapshotProperties");
|
||||
if (!pVssFreeSnapshotProperties) {
|
||||
qga_debug("failed to get proc address from VSSAPI.DLL");
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
qga_debug_end;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static void requester_cleanup(void)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
if (vss_ctx.hEventFrozen) {
|
||||
CloseHandle(vss_ctx.hEventFrozen);
|
||||
vss_ctx.hEventFrozen = NULL;
|
||||
}
|
||||
if (vss_ctx.hEventThaw) {
|
||||
CloseHandle(vss_ctx.hEventThaw);
|
||||
vss_ctx.hEventThaw = NULL;
|
||||
}
|
||||
if (vss_ctx.hEventTimeout) {
|
||||
CloseHandle(vss_ctx.hEventTimeout);
|
||||
vss_ctx.hEventTimeout = NULL;
|
||||
}
|
||||
if (vss_ctx.pAsyncSnapshot) {
|
||||
vss_ctx.pAsyncSnapshot->Release();
|
||||
vss_ctx.pAsyncSnapshot = NULL;
|
||||
}
|
||||
if (vss_ctx.pVssbc) {
|
||||
vss_ctx.pVssbc->Release();
|
||||
vss_ctx.pVssbc = NULL;
|
||||
}
|
||||
vss_ctx.cFrozenVols = 0;
|
||||
qga_debug_end;
|
||||
}
|
||||
|
||||
STDAPI requester_deinit(void)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
requester_cleanup();
|
||||
|
||||
pCreateVssBackupComponents = NULL;
|
||||
pVssFreeSnapshotProperties = NULL;
|
||||
if (hLib) {
|
||||
FreeLibrary(hLib);
|
||||
hLib = NULL;
|
||||
}
|
||||
|
||||
qga_debug_end;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT WaitForAsync(IVssAsync *pAsync)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
HRESULT ret, hr;
|
||||
|
||||
do {
|
||||
hr = pAsync->Wait();
|
||||
if (FAILED(hr)) {
|
||||
ret = hr;
|
||||
break;
|
||||
}
|
||||
hr = pAsync->QueryStatus(&ret, NULL);
|
||||
if (FAILED(hr)) {
|
||||
ret = hr;
|
||||
break;
|
||||
}
|
||||
} while (ret == VSS_S_ASYNC_PENDING);
|
||||
|
||||
qga_debug_end;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void AddComponents(ErrorSet *errset)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
unsigned int cWriters, i;
|
||||
VSS_ID id, idInstance, idWriter;
|
||||
BSTR bstrWriterName = NULL;
|
||||
VSS_USAGE_TYPE usage;
|
||||
VSS_SOURCE_TYPE source;
|
||||
unsigned int cComponents, c1, c2, j;
|
||||
COMPointer<IVssExamineWriterMetadata> pMetadata;
|
||||
COMPointer<IVssWMComponent> pComponent;
|
||||
PVSSCOMPONENTINFO info;
|
||||
HRESULT hr;
|
||||
|
||||
hr = vss_ctx.pVssbc->GetWriterMetadataCount(&cWriters);
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to get writer metadata count");
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (i = 0; i < cWriters; i++) {
|
||||
hr = vss_ctx.pVssbc->GetWriterMetadata(i, &id, pMetadata.replace());
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to get writer metadata of %d/%d",
|
||||
i, cWriters);
|
||||
goto out;
|
||||
}
|
||||
|
||||
hr = pMetadata->GetIdentity(&idInstance, &idWriter,
|
||||
&bstrWriterName, &usage, &source);
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to get identity of writer %d/%d",
|
||||
i, cWriters);
|
||||
goto out;
|
||||
}
|
||||
|
||||
hr = pMetadata->GetFileCounts(&c1, &c2, &cComponents);
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to get file counts of %S",
|
||||
bstrWriterName);
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (j = 0; j < cComponents; j++) {
|
||||
hr = pMetadata->GetComponent(j, pComponent.replace());
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr,
|
||||
"failed to get component %d/%d of %S",
|
||||
j, cComponents, bstrWriterName);
|
||||
goto out;
|
||||
}
|
||||
|
||||
hr = pComponent->GetComponentInfo(&info);
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr,
|
||||
"failed to get component info %d/%d of %S",
|
||||
j, cComponents, bstrWriterName);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (info->bSelectable) {
|
||||
hr = vss_ctx.pVssbc->AddComponent(idInstance, idWriter,
|
||||
info->type,
|
||||
info->bstrLogicalPath,
|
||||
info->bstrComponentName);
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to add component %S(%S)",
|
||||
info->bstrComponentName, bstrWriterName);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
SysFreeString(bstrWriterName);
|
||||
bstrWriterName = NULL;
|
||||
pComponent->FreeComponentInfo(info);
|
||||
info = NULL;
|
||||
}
|
||||
}
|
||||
out:
|
||||
if (bstrWriterName) {
|
||||
SysFreeString(bstrWriterName);
|
||||
}
|
||||
if (pComponent && info) {
|
||||
pComponent->FreeComponentInfo(info);
|
||||
}
|
||||
qga_debug_end;
|
||||
}
|
||||
|
||||
static DWORD get_reg_dword_value(HKEY baseKey, LPCSTR subKey, LPCSTR valueName,
|
||||
DWORD defaultData)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
DWORD regGetValueError;
|
||||
DWORD dwordData;
|
||||
DWORD dataSize = sizeof(DWORD);
|
||||
|
||||
regGetValueError = RegGetValue(baseKey, subKey, valueName, RRF_RT_DWORD,
|
||||
NULL, &dwordData, &dataSize);
|
||||
qga_debug_end;
|
||||
if (regGetValueError != ERROR_SUCCESS) {
|
||||
return defaultData;
|
||||
}
|
||||
return dwordData;
|
||||
}
|
||||
|
||||
static bool is_valid_vss_backup_type(VSS_BACKUP_TYPE vssBT)
|
||||
{
|
||||
return (vssBT > VSS_BT_UNDEFINED && vssBT < VSS_BT_OTHER);
|
||||
}
|
||||
|
||||
static VSS_BACKUP_TYPE get_vss_backup_type(
|
||||
VSS_BACKUP_TYPE defaultVssBT = DEFAULT_VSS_BACKUP_TYPE)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
VSS_BACKUP_TYPE vssBackupType;
|
||||
|
||||
vssBackupType = static_cast<VSS_BACKUP_TYPE>(
|
||||
get_reg_dword_value(HKEY_LOCAL_MACHINE,
|
||||
QGA_PROVIDER_REGISTRY_ADDRESS,
|
||||
"VssOption",
|
||||
defaultVssBT));
|
||||
qga_debug_end;
|
||||
if (!is_valid_vss_backup_type(vssBackupType)) {
|
||||
return defaultVssBT;
|
||||
}
|
||||
return vssBackupType;
|
||||
}
|
||||
|
||||
void requester_freeze(int *num_vols, void *mountpoints, ErrorSet *errset)
|
||||
{
|
||||
qga_debug_begin;
|
||||
|
||||
COMPointer<IVssAsync> pAsync;
|
||||
HANDLE volume;
|
||||
HRESULT hr;
|
||||
LONG ctx;
|
||||
GUID guidSnapshotSet = GUID_NULL;
|
||||
SECURITY_DESCRIPTOR sd;
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
WCHAR short_volume_name[64], *display_name = short_volume_name;
|
||||
DWORD wait_status;
|
||||
int num_fixed_drives = 0, i;
|
||||
int num_mount_points = 0;
|
||||
VSS_BACKUP_TYPE vss_bt = get_vss_backup_type();
|
||||
|
||||
if (vss_ctx.pVssbc) { /* already frozen */
|
||||
*num_vols = 0;
|
||||
qga_debug("finished, already frozen");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Allow unrestricted access to events */
|
||||
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
|
||||
SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.lpSecurityDescriptor = &sd;
|
||||
sa.bInheritHandle = FALSE;
|
||||
|
||||
vss_ctx.hEventFrozen = CreateEvent(&sa, TRUE, FALSE, EVENT_NAME_FROZEN);
|
||||
if (!vss_ctx.hEventFrozen) {
|
||||
err_set(errset, GetLastError(), "failed to create event %s",
|
||||
EVENT_NAME_FROZEN);
|
||||
goto out;
|
||||
}
|
||||
vss_ctx.hEventThaw = CreateEvent(&sa, TRUE, FALSE, EVENT_NAME_THAW);
|
||||
if (!vss_ctx.hEventThaw) {
|
||||
err_set(errset, GetLastError(), "failed to create event %s",
|
||||
EVENT_NAME_THAW);
|
||||
goto out;
|
||||
}
|
||||
vss_ctx.hEventTimeout = CreateEvent(&sa, TRUE, FALSE, EVENT_NAME_TIMEOUT);
|
||||
if (!vss_ctx.hEventTimeout) {
|
||||
err_set(errset, GetLastError(), "failed to create event %s",
|
||||
EVENT_NAME_TIMEOUT);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!pCreateVssBackupComponents) {
|
||||
err_set(errset, (HRESULT)ERROR_PROC_NOT_FOUND,
|
||||
"CreateVssBackupComponents proc address absent. Did you call requester_init()?");
|
||||
goto out;
|
||||
}
|
||||
|
||||
hr = pCreateVssBackupComponents(&vss_ctx.pVssbc);
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to create VSS backup components");
|
||||
goto out;
|
||||
}
|
||||
|
||||
hr = vss_ctx.pVssbc->InitializeForBackup();
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to initialize for backup");
|
||||
goto out;
|
||||
}
|
||||
|
||||
hr = vss_ctx.pVssbc->SetBackupState(true, true, vss_bt, false);
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to set backup state");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Currently writable snapshots are not supported.
|
||||
* To prevent the final commit (which requires to write to snapshots),
|
||||
* ATTR_NO_AUTORECOVERY and ATTR_TRANSPORTABLE are specified here.
|
||||
*/
|
||||
ctx = VSS_CTX_APP_ROLLBACK;
|
||||
ctx |= VSS_VOLSNAP_ATTR_TRANSPORTABLE |
|
||||
VSS_VOLSNAP_ATTR_NO_AUTORECOVERY |
|
||||
VSS_VOLSNAP_ATTR_TXF_RECOVERY;
|
||||
hr = vss_ctx.pVssbc->SetContext(ctx);
|
||||
if (hr == (HRESULT)VSS_E_UNSUPPORTED_CONTEXT) {
|
||||
/* Non-server version of Windows doesn't support ATTR_TRANSPORTABLE */
|
||||
ctx &= ~VSS_VOLSNAP_ATTR_TRANSPORTABLE;
|
||||
hr = vss_ctx.pVssbc->SetContext(ctx);
|
||||
}
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to set backup context");
|
||||
goto out;
|
||||
}
|
||||
|
||||
hr = vss_ctx.pVssbc->GatherWriterMetadata(pAsync.replace());
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = WaitForAsync(pAsync);
|
||||
}
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to gather writer metadata");
|
||||
goto out;
|
||||
}
|
||||
|
||||
AddComponents(errset);
|
||||
if (err_is_set(errset)) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
hr = vss_ctx.pVssbc->StartSnapshotSet(&guidSnapshotSet);
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to start snapshot set");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (mountpoints) {
|
||||
PWCHAR volume_name_wchar;
|
||||
for (volList *list = (volList *)mountpoints; list; list = list->next) {
|
||||
size_t len = strlen(list->value) + 1;
|
||||
size_t converted = 0;
|
||||
VSS_ID pid;
|
||||
|
||||
volume_name_wchar = new wchar_t[len];
|
||||
mbstowcs_s(&converted, volume_name_wchar, len,
|
||||
list->value, _TRUNCATE);
|
||||
|
||||
hr = vss_ctx.pVssbc->AddToSnapshotSet(volume_name_wchar,
|
||||
g_gProviderId, &pid);
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to add %S to snapshot set",
|
||||
volume_name_wchar);
|
||||
delete[] volume_name_wchar;
|
||||
goto out;
|
||||
}
|
||||
num_mount_points++;
|
||||
|
||||
delete[] volume_name_wchar;
|
||||
}
|
||||
|
||||
if (num_mount_points == 0) {
|
||||
/* If there is no valid mount points, just exit. */
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mountpoints) {
|
||||
volume = FindFirstVolumeW(short_volume_name, sizeof(short_volume_name));
|
||||
if (volume == INVALID_HANDLE_VALUE) {
|
||||
err_set(errset, hr, "failed to find first volume");
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
if (GetDriveTypeW(short_volume_name) == DRIVE_FIXED) {
|
||||
VSS_ID pid;
|
||||
hr = vss_ctx.pVssbc->AddToSnapshotSet(short_volume_name,
|
||||
g_gProviderId, &pid);
|
||||
if (FAILED(hr)) {
|
||||
WCHAR volume_path_name[MAX_PATH];
|
||||
if (GetVolumePathNamesForVolumeNameW(
|
||||
short_volume_name, volume_path_name,
|
||||
sizeof(volume_path_name), NULL) &&
|
||||
*volume_path_name) {
|
||||
display_name = volume_path_name;
|
||||
}
|
||||
err_set(errset, hr, "failed to add %S to snapshot set",
|
||||
display_name);
|
||||
FindVolumeClose(volume);
|
||||
goto out;
|
||||
}
|
||||
num_fixed_drives++;
|
||||
}
|
||||
if (!FindNextVolumeW(volume, short_volume_name,
|
||||
sizeof(short_volume_name))) {
|
||||
FindVolumeClose(volume);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_fixed_drives == 0) {
|
||||
goto out; /* If there is no fixed drive, just exit. */
|
||||
}
|
||||
}
|
||||
|
||||
qga_debug("preparing for backup");
|
||||
hr = vss_ctx.pVssbc->PrepareForBackup(pAsync.replace());
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = WaitForAsync(pAsync);
|
||||
}
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to prepare for backup");
|
||||
goto out;
|
||||
}
|
||||
|
||||
hr = vss_ctx.pVssbc->GatherWriterStatus(pAsync.replace());
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = WaitForAsync(pAsync);
|
||||
}
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to gather writer status");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start VSS quiescing operations.
|
||||
* CQGAVssProvider::CommitSnapshots will kick vss_ctx.hEventFrozen
|
||||
* after the applications and filesystems are frozen.
|
||||
*/
|
||||
qga_debug("do snapshot set");
|
||||
hr = vss_ctx.pVssbc->DoSnapshotSet(&vss_ctx.pAsyncSnapshot);
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to do snapshot set");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Need to call QueryStatus several times to make VSS provider progress */
|
||||
for (i = 0; i < VSS_TIMEOUT_FREEZE_MSEC/VSS_TIMEOUT_EVENT_MSEC; i++) {
|
||||
HRESULT hr2 = vss_ctx.pAsyncSnapshot->QueryStatus(&hr, NULL);
|
||||
if (FAILED(hr2)) {
|
||||
err_set(errset, hr, "failed to do snapshot set");
|
||||
goto out;
|
||||
}
|
||||
if (hr != VSS_S_ASYNC_PENDING) {
|
||||
err_set(errset, E_FAIL,
|
||||
"DoSnapshotSet exited without Frozen event");
|
||||
goto out;
|
||||
}
|
||||
wait_status = WaitForSingleObject(vss_ctx.hEventFrozen,
|
||||
VSS_TIMEOUT_EVENT_MSEC);
|
||||
if (wait_status != WAIT_TIMEOUT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (wait_status == WAIT_TIMEOUT) {
|
||||
err_set(errset, E_FAIL,
|
||||
"timeout when try to receive Frozen event from VSS provider");
|
||||
/* If we are here, VSS had timeout.
|
||||
* Don't call AbortBackup, just return directly.
|
||||
*/
|
||||
goto out1;
|
||||
}
|
||||
|
||||
if (wait_status != WAIT_OBJECT_0) {
|
||||
err_set(errset, E_FAIL,
|
||||
"couldn't receive Frozen event from VSS provider");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (mountpoints) {
|
||||
*num_vols = vss_ctx.cFrozenVols = num_mount_points;
|
||||
} else {
|
||||
*num_vols = vss_ctx.cFrozenVols = num_fixed_drives;
|
||||
}
|
||||
|
||||
qga_debug("end successful");
|
||||
return;
|
||||
|
||||
out:
|
||||
if (vss_ctx.pVssbc) {
|
||||
vss_ctx.pVssbc->AbortBackup();
|
||||
}
|
||||
|
||||
out1:
|
||||
requester_cleanup();
|
||||
|
||||
qga_debug_end;
|
||||
}
|
||||
|
||||
|
||||
void requester_thaw(int *num_vols, void *mountpints, ErrorSet *errset)
|
||||
{
|
||||
qga_debug_begin;
|
||||
COMPointer<IVssAsync> pAsync;
|
||||
|
||||
if (!vss_ctx.hEventThaw) {
|
||||
/*
|
||||
* In this case, DoSnapshotSet is aborted or not started,
|
||||
* and no volumes must be frozen. We return without an error.
|
||||
*/
|
||||
*num_vols = 0;
|
||||
qga_debug("finished, no volumes were frozen");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Tell the provider that the snapshot is finished. */
|
||||
SetEvent(vss_ctx.hEventThaw);
|
||||
|
||||
if (!vss_ctx.pVssbc) {
|
||||
err_set(errset, (HRESULT)VSS_E_BAD_STATE,
|
||||
"CreateVssBackupComponents is missing. Did you freeze the volumes?");
|
||||
return;
|
||||
}
|
||||
if (!vss_ctx.pAsyncSnapshot) {
|
||||
err_set(errset, (HRESULT)VSS_E_BAD_STATE,
|
||||
"AsyncSnapshot set is missing. Did you freeze the volumes?");
|
||||
return;
|
||||
}
|
||||
|
||||
HRESULT hr = WaitForAsync(vss_ctx.pAsyncSnapshot);
|
||||
switch (hr) {
|
||||
case VSS_S_ASYNC_FINISHED:
|
||||
hr = vss_ctx.pVssbc->BackupComplete(pAsync.replace());
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = WaitForAsync(pAsync);
|
||||
}
|
||||
if (FAILED(hr)) {
|
||||
err_set(errset, hr, "failed to complete backup");
|
||||
}
|
||||
break;
|
||||
|
||||
case (HRESULT)VSS_E_OBJECT_NOT_FOUND:
|
||||
/*
|
||||
* On Windows earlier than 2008 SP2 which does not support
|
||||
* VSS_VOLSNAP_ATTR_NO_AUTORECOVERY context, the final commit is not
|
||||
* skipped and VSS is aborted by VSS_E_OBJECT_NOT_FOUND. However, as
|
||||
* the system had been frozen until fsfreeze-thaw command was issued,
|
||||
* we ignore this error.
|
||||
*/
|
||||
vss_ctx.pVssbc->AbortBackup();
|
||||
break;
|
||||
|
||||
case VSS_E_UNEXPECTED_PROVIDER_ERROR:
|
||||
if (WaitForSingleObject(vss_ctx.hEventTimeout, 0) != WAIT_OBJECT_0) {
|
||||
err_set(errset, hr, "unexpected error in VSS provider");
|
||||
break;
|
||||
}
|
||||
/* fall through if hEventTimeout is signaled */
|
||||
|
||||
case (HRESULT)VSS_E_HOLD_WRITES_TIMEOUT:
|
||||
err_set(errset, hr, "couldn't hold writes: "
|
||||
"fsfreeze is limited up to 10 seconds");
|
||||
break;
|
||||
|
||||
default:
|
||||
err_set(errset, hr, "failed to do snapshot set");
|
||||
}
|
||||
|
||||
if (err_is_set(errset)) {
|
||||
vss_ctx.pVssbc->AbortBackup();
|
||||
}
|
||||
*num_vols = vss_ctx.cFrozenVols;
|
||||
requester_cleanup();
|
||||
|
||||
StopService();
|
||||
|
||||
qga_debug_end;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* QEMU Guest Agent VSS requester declarations
|
||||
*
|
||||
* Copyright Hitachi Data Systems Corp. 2013
|
||||
*
|
||||
* Authors:
|
||||
* Tomoki Sekiyama <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#ifndef VSS_WIN32_REQUESTER_H
|
||||
#define VSS_WIN32_REQUESTER_H
|
||||
|
||||
#include <basetyps.h> /* STDAPI */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Error;
|
||||
|
||||
/* Callback to set Error; used to avoid linking glib to the DLL */
|
||||
typedef void (*ErrorSetFunc)(struct Error **errp,
|
||||
const char *src, int line, const char *func,
|
||||
int win32_err, const char *fmt, ...)
|
||||
G_GNUC_PRINTF(6, 7);
|
||||
typedef struct ErrorSet {
|
||||
ErrorSetFunc error_setg_win32_wrapper;
|
||||
struct Error **errp; /* restriction: must not be null */
|
||||
} ErrorSet;
|
||||
|
||||
STDAPI requester_init(void);
|
||||
STDAPI requester_deinit(void);
|
||||
|
||||
typedef struct volList volList;
|
||||
|
||||
struct volList {
|
||||
volList *next;
|
||||
char *value;
|
||||
};
|
||||
|
||||
typedef void (*QGAVSSRequesterFunc)(int *, void *, ErrorSet *);
|
||||
void requester_freeze(int *num_vols, void *volList, ErrorSet *errset);
|
||||
void requester_thaw(int *num_vols, void *volList, ErrorSet *errset);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* QEMU Guest Agent win32 VSS common declarations
|
||||
*
|
||||
* Copyright Hitachi Data Systems Corp. 2013
|
||||
*
|
||||
* Authors:
|
||||
* Tomoki Sekiyama <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#ifndef VSS_COMMON_H
|
||||
#define VSS_COMMON_H
|
||||
|
||||
#define __MIDL_user_allocate_free_DEFINED__
|
||||
#include <windows.h>
|
||||
#include <shlwapi.h>
|
||||
|
||||
/* Reduce warnings to include vss.h */
|
||||
|
||||
/* Ignore annotations for MS IDE */
|
||||
#define __in IN
|
||||
#define __out OUT
|
||||
#define __RPC_unique_pointer
|
||||
#define __RPC_string
|
||||
#define __RPC__deref_inout_opt
|
||||
#define __RPC__out
|
||||
#ifndef __RPC__out_ecount_part
|
||||
#define __RPC__out_ecount_part(x, y)
|
||||
#endif
|
||||
#define _declspec(x)
|
||||
|
||||
#include <vss.h>
|
||||
#include "vss-handles.h"
|
||||
|
||||
/* Macros to convert char definitions to wchar */
|
||||
#define _L(a) L##a
|
||||
#define L(a) _L(a)
|
||||
|
||||
const GUID g_gProviderId = { 0x3629d4ed, 0xee09, 0x4e0e,
|
||||
{0x9a, 0x5c, 0x6d, 0x8b, 0xa2, 0x87, 0x2a, 0xef} };
|
||||
const GUID g_gProviderVersion = { 0x11ef8b15, 0xcac6, 0x40d6,
|
||||
{0x8d, 0x5c, 0x8f, 0xfc, 0x16, 0x3f, 0x24, 0xca} };
|
||||
|
||||
const CLSID CLSID_QGAVSSProvider = { 0x6e6a3492, 0x8d4d, 0x440c,
|
||||
{0x96, 0x19, 0x5e, 0x5d, 0x0c, 0xc3, 0x1c, 0xa8} };
|
||||
|
||||
const TCHAR g_szClsid[] = TEXT("{6E6A3492-8D4D-440C-9619-5E5D0CC31CA8}");
|
||||
const TCHAR g_szProgid[] = TEXT("QGAVSSProvider");
|
||||
|
||||
#ifdef HAVE_VSS_SDK
|
||||
/* Enums undefined in VSS SDK 7.2 but defined in newer Windows SDK */
|
||||
enum __VSS_VOLUME_SNAPSHOT_ATTRIBUTES {
|
||||
VSS_VOLSNAP_ATTR_NO_AUTORECOVERY = 0x00000002,
|
||||
VSS_VOLSNAP_ATTR_TXF_RECOVERY = 0x02000000
|
||||
};
|
||||
#endif
|
||||
|
||||
/* COM pointer utility; call ->Release() when it goes out of scope */
|
||||
template <class T>
|
||||
class COMPointer {
|
||||
COMPointer(const COMPointer<T> &p) { } /* no copy */
|
||||
T *p;
|
||||
public:
|
||||
COMPointer &operator=(T *new_p)
|
||||
{
|
||||
/* Assignment of a new T* (or NULL) causes release of previous p */
|
||||
if (p && p != new_p) {
|
||||
p->Release();
|
||||
}
|
||||
p = new_p;
|
||||
return *this;
|
||||
}
|
||||
/* Replace by assignment to the pointer of p */
|
||||
T **replace(void)
|
||||
{
|
||||
*this = NULL;
|
||||
return &p;
|
||||
}
|
||||
/* Make COMPointer be used like T* */
|
||||
operator T*() { return p; }
|
||||
T *operator->(void) { return p; }
|
||||
T &operator*(void) { return *p; }
|
||||
operator bool() { return !!p; }
|
||||
|
||||
COMPointer(T *p = NULL) : p(p) { }
|
||||
~COMPointer() { *this = NULL; } /* Automatic release */
|
||||
};
|
||||
|
||||
/*
|
||||
* COM initializer; this should declared before COMPointer to uninitialize COM
|
||||
* after releasing COM objects.
|
||||
*/
|
||||
class COMInitializer {
|
||||
public:
|
||||
COMInitializer() { CoInitialize(NULL); }
|
||||
~COMInitializer() { CoUninitialize(); }
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* QEMU Guest Agent VSS debug declarations
|
||||
*
|
||||
* Copyright (C) 2023 Red Hat Inc
|
||||
*
|
||||
* Authors:
|
||||
* Konstantin Kostiuk <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "vss-debug.h"
|
||||
#include "vss-common.h"
|
||||
|
||||
void qga_debug_internal(const char *funcname, const char *fmt, ...)
|
||||
{
|
||||
char user_string[512] = {0};
|
||||
char full_string[640] = {0};
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if (vsnprintf(user_string, _countof(user_string), fmt, args) <= 0) {
|
||||
va_end(args);
|
||||
return;
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
|
||||
if (snprintf(full_string, _countof(full_string),
|
||||
QGA_PROVIDER_NAME "[%lu]: %s %s\n",
|
||||
GetCurrentThreadId(), funcname, user_string) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
OutputDebugString(full_string);
|
||||
fputs(full_string, stderr);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* QEMU Guest Agent VSS debug declarations
|
||||
*
|
||||
* Copyright (C) 2023 Red Hat Inc
|
||||
*
|
||||
* Authors:
|
||||
* Konstantin Kostiuk <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include <vss-handles.h>
|
||||
|
||||
#ifndef VSS_DEBUG_H
|
||||
#define VSS_DEBUG_H
|
||||
|
||||
void qga_debug_internal(const char *funcname, const char *fmt, ...) G_GNUC_PRINTF(2, 3);
|
||||
|
||||
#define qga_debug(fmt, ...) qga_debug_internal(__func__, fmt, ## __VA_ARGS__)
|
||||
#define qga_debug_begin qga_debug("begin")
|
||||
#define qga_debug_end qga_debug("end")
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef VSS_HANDLES_H
|
||||
#define VSS_HANDLES_H
|
||||
|
||||
/* Constants for QGA VSS Provider */
|
||||
|
||||
#define QGA_PROVIDER_NAME "QEMU Guest Agent VSS Provider"
|
||||
#define QGA_PROVIDER_LNAME L(QGA_PROVIDER_NAME)
|
||||
#define QGA_PROVIDER_VERSION L(QEMU_VERSION)
|
||||
#define QGA_PROVIDER_REGISTRY_ADDRESS "SYSTEM\\CurrentControlSet"\
|
||||
"\\Services"\
|
||||
"\\" QGA_PROVIDER_NAME
|
||||
|
||||
#define EVENT_NAME_FROZEN "Global\\QGAVSSEvent-frozen"
|
||||
#define EVENT_NAME_THAW "Global\\QGAVSSEvent-thaw"
|
||||
#define EVENT_NAME_TIMEOUT "Global\\QGAVSSEvent-timeout"
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user