2012-01-20 08:04:34 +04:00
|
|
|
/*
|
|
|
|
* QEMU Guest Agent win32-specific command implementations
|
|
|
|
*
|
|
|
|
* Copyright IBM Corp. 2012
|
|
|
|
*
|
|
|
|
* Authors:
|
|
|
|
* Michael Roth <mdroth@linux.vnet.ibm.com>
|
2012-01-29 13:53:31 +04:00
|
|
|
* Gal Hammer <ghammer@redhat.com>
|
2012-01-20 08:04:34 +04:00
|
|
|
*
|
|
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
|
|
* See the COPYING file in the top-level directory.
|
|
|
|
*/
|
|
|
|
|
2016-01-29 20:49:58 +03:00
|
|
|
#include "qemu/osdep.h"
|
2012-01-29 13:53:31 +04:00
|
|
|
#include <wtypes.h>
|
|
|
|
#include <powrprof.h>
|
2015-06-02 20:41:07 +03:00
|
|
|
#include <winsock2.h>
|
|
|
|
#include <ws2tcpip.h>
|
|
|
|
#include <iptypes.h>
|
|
|
|
#include <iphlpapi.h>
|
2015-06-30 13:25:22 +03:00
|
|
|
#ifdef CONFIG_QGA_NTDDSCSI
|
|
|
|
#include <winioctl.h>
|
|
|
|
#include <ntddscsi.h>
|
2015-07-08 03:12:18 +03:00
|
|
|
#include <setupapi.h>
|
|
|
|
#include <initguid.h>
|
2015-06-30 13:25:22 +03:00
|
|
|
#endif
|
2015-06-30 17:37:13 +03:00
|
|
|
#include <lm.h>
|
|
|
|
|
2012-01-20 08:04:34 +04:00
|
|
|
#include "qga/guest-agent-core.h"
|
2013-08-07 19:40:25 +04:00
|
|
|
#include "qga/vss-win32.h"
|
2012-01-20 08:04:34 +04:00
|
|
|
#include "qga-qmp-commands.h"
|
2012-12-17 21:19:43 +04:00
|
|
|
#include "qapi/qmp/qerror.h"
|
2015-02-06 20:59:55 +03:00
|
|
|
#include "qemu/queue.h"
|
2015-06-02 20:41:07 +03:00
|
|
|
#include "qemu/host-utils.h"
|
2015-11-23 18:37:07 +03:00
|
|
|
#include "qemu/base64.h"
|
2012-01-20 08:04:34 +04:00
|
|
|
|
2012-01-23 06:24:37 +04:00
|
|
|
#ifndef SHTDN_REASON_FLAG_PLANNED
|
|
|
|
#define SHTDN_REASON_FLAG_PLANNED 0x80000000
|
|
|
|
#endif
|
|
|
|
|
2013-03-15 13:29:04 +04:00
|
|
|
/* multiple of 100 nanoseconds elapsed between windows baseline
|
|
|
|
* (1/1/1601) and Unix Epoch (1/1/1970), accounting for leap years */
|
|
|
|
#define W32_FT_OFFSET (10000000ULL * 60 * 60 * 24 * \
|
|
|
|
(365 * (1970 - 1601) + \
|
|
|
|
(1970 - 1601) / 4 - 3))
|
|
|
|
|
2015-02-06 20:59:55 +03:00
|
|
|
#define INVALID_SET_FILE_POINTER ((DWORD)-1)
|
|
|
|
|
|
|
|
typedef struct GuestFileHandle {
|
|
|
|
int64_t id;
|
|
|
|
HANDLE fh;
|
|
|
|
QTAILQ_ENTRY(GuestFileHandle) next;
|
|
|
|
} GuestFileHandle;
|
|
|
|
|
|
|
|
static struct {
|
|
|
|
QTAILQ_HEAD(, GuestFileHandle) filehandles;
|
2015-10-13 18:41:19 +03:00
|
|
|
} guest_file_state = {
|
|
|
|
.filehandles = QTAILQ_HEAD_INITIALIZER(guest_file_state.filehandles),
|
|
|
|
};
|
2015-02-06 20:59:55 +03:00
|
|
|
|
2015-11-11 02:19:11 +03:00
|
|
|
#define FILE_GENERIC_APPEND (FILE_GENERIC_WRITE & ~FILE_WRITE_DATA)
|
2015-02-06 20:59:55 +03:00
|
|
|
|
|
|
|
typedef struct OpenFlags {
|
|
|
|
const char *forms;
|
|
|
|
DWORD desired_access;
|
|
|
|
DWORD creation_disposition;
|
|
|
|
} OpenFlags;
|
|
|
|
static OpenFlags guest_file_open_modes[] = {
|
2015-11-11 02:19:11 +03:00
|
|
|
{"r", GENERIC_READ, OPEN_EXISTING},
|
|
|
|
{"rb", GENERIC_READ, OPEN_EXISTING},
|
|
|
|
{"w", GENERIC_WRITE, CREATE_ALWAYS},
|
|
|
|
{"wb", GENERIC_WRITE, CREATE_ALWAYS},
|
|
|
|
{"a", FILE_GENERIC_APPEND, OPEN_ALWAYS },
|
|
|
|
{"r+", GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
|
|
|
|
{"rb+", GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
|
|
|
|
{"r+b", GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
|
|
|
|
{"w+", GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
|
|
|
|
{"wb+", GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
|
|
|
|
{"w+b", GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
|
|
|
|
{"a+", FILE_GENERIC_APPEND|GENERIC_READ, OPEN_ALWAYS },
|
|
|
|
{"ab+", FILE_GENERIC_APPEND|GENERIC_READ, OPEN_ALWAYS },
|
|
|
|
{"a+b", FILE_GENERIC_APPEND|GENERIC_READ, OPEN_ALWAYS }
|
2015-02-06 20:59:55 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
static OpenFlags *find_open_flag(const char *mode_str)
|
|
|
|
{
|
|
|
|
int mode;
|
|
|
|
Error **errp = NULL;
|
|
|
|
|
|
|
|
for (mode = 0; mode < ARRAY_SIZE(guest_file_open_modes); ++mode) {
|
|
|
|
OpenFlags *flags = guest_file_open_modes + mode;
|
|
|
|
|
|
|
|
if (strcmp(flags->forms, mode_str) == 0) {
|
|
|
|
return flags;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
error_setg(errp, "invalid file open mode '%s'", mode_str);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int64_t guest_file_handle_add(HANDLE fh, Error **errp)
|
|
|
|
{
|
|
|
|
GuestFileHandle *gfh;
|
|
|
|
int64_t handle;
|
|
|
|
|
|
|
|
handle = ga_get_fd_handle(ga_state, errp);
|
|
|
|
if (handle < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2015-09-14 14:50:44 +03:00
|
|
|
gfh = g_new0(GuestFileHandle, 1);
|
2015-02-06 20:59:55 +03:00
|
|
|
gfh->id = handle;
|
|
|
|
gfh->fh = fh;
|
|
|
|
QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
|
|
|
|
|
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GuestFileHandle *guest_file_handle_find(int64_t id, Error **errp)
|
|
|
|
{
|
|
|
|
GuestFileHandle *gfh;
|
|
|
|
QTAILQ_FOREACH(gfh, &guest_file_state.filehandles, next) {
|
|
|
|
if (gfh->id == id) {
|
|
|
|
return gfh;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
error_setg(errp, "handle '%" PRId64 "' has not been found", id);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-10-28 18:13:57 +03:00
|
|
|
static void handle_set_nonblocking(HANDLE fh)
|
|
|
|
{
|
|
|
|
DWORD file_type, pipe_state;
|
|
|
|
file_type = GetFileType(fh);
|
|
|
|
if (file_type != FILE_TYPE_PIPE) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* If file_type == FILE_TYPE_PIPE, according to MSDN
|
|
|
|
* the specified file is socket or named pipe */
|
|
|
|
if (!GetNamedPipeHandleState(fh, &pipe_state, NULL,
|
|
|
|
NULL, NULL, NULL, 0)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* The fd is named pipe fd */
|
|
|
|
if (pipe_state & PIPE_NOWAIT) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
pipe_state |= PIPE_NOWAIT;
|
|
|
|
SetNamedPipeHandleState(fh, &pipe_state, NULL, NULL);
|
|
|
|
}
|
|
|
|
|
2015-02-06 20:59:55 +03:00
|
|
|
int64_t qmp_guest_file_open(const char *path, bool has_mode,
|
|
|
|
const char *mode, Error **errp)
|
|
|
|
{
|
|
|
|
int64_t fd;
|
|
|
|
HANDLE fh;
|
|
|
|
HANDLE templ_file = NULL;
|
|
|
|
DWORD share_mode = FILE_SHARE_READ;
|
|
|
|
DWORD flags_and_attr = FILE_ATTRIBUTE_NORMAL;
|
|
|
|
LPSECURITY_ATTRIBUTES sa_attr = NULL;
|
|
|
|
OpenFlags *guest_flags;
|
|
|
|
|
|
|
|
if (!has_mode) {
|
|
|
|
mode = "r";
|
|
|
|
}
|
|
|
|
slog("guest-file-open called, filepath: %s, mode: %s", path, mode);
|
|
|
|
guest_flags = find_open_flag(mode);
|
|
|
|
if (guest_flags == NULL) {
|
|
|
|
error_setg(errp, "invalid file open mode");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
fh = CreateFile(path, guest_flags->desired_access, share_mode, sa_attr,
|
|
|
|
guest_flags->creation_disposition, flags_and_attr,
|
|
|
|
templ_file);
|
|
|
|
if (fh == INVALID_HANDLE_VALUE) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed to open file '%s'",
|
|
|
|
path);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-10-28 18:13:57 +03:00
|
|
|
/* set fd non-blocking to avoid common use cases (like reading from a
|
|
|
|
* named pipe) from hanging the agent
|
|
|
|
*/
|
|
|
|
handle_set_nonblocking(fh);
|
|
|
|
|
2015-02-06 20:59:55 +03:00
|
|
|
fd = guest_file_handle_add(fh, errp);
|
|
|
|
if (fd < 0) {
|
2015-10-28 18:13:56 +03:00
|
|
|
CloseHandle(fh);
|
2015-02-06 20:59:55 +03:00
|
|
|
error_setg(errp, "failed to add handle to qmp handle table");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
slog("guest-file-open, handle: % " PRId64, fd);
|
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
void qmp_guest_file_close(int64_t handle, Error **errp)
|
|
|
|
{
|
|
|
|
bool ret;
|
|
|
|
GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
|
|
|
|
slog("guest-file-close called, handle: %" PRId64, handle);
|
|
|
|
if (gfh == NULL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ret = CloseHandle(gfh->fh);
|
|
|
|
if (!ret) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed close handle");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next);
|
|
|
|
g_free(gfh);
|
|
|
|
}
|
|
|
|
|
2014-05-02 15:26:30 +04:00
|
|
|
static void acquire_privilege(const char *name, Error **errp)
|
2012-01-20 08:04:34 +04:00
|
|
|
{
|
2014-05-19 11:26:03 +04:00
|
|
|
HANDLE token = NULL;
|
2012-01-23 06:24:37 +04:00
|
|
|
TOKEN_PRIVILEGES priv;
|
2012-01-29 13:53:31 +04:00
|
|
|
Error *local_err = NULL;
|
|
|
|
|
|
|
|
if (OpenProcessToken(GetCurrentProcess(),
|
|
|
|
TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token))
|
|
|
|
{
|
|
|
|
if (!LookupPrivilegeValue(NULL, name, &priv.Privileges[0].Luid)) {
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(&local_err, QERR_QGA_COMMAND_FAILED,
|
|
|
|
"no luid for requested privilege");
|
2012-01-29 13:53:31 +04:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv.PrivilegeCount = 1;
|
|
|
|
priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
|
|
|
|
|
|
|
if (!AdjustTokenPrivileges(token, FALSE, &priv, 0, NULL, 0)) {
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(&local_err, QERR_QGA_COMMAND_FAILED,
|
|
|
|
"unable to acquire requested privilege");
|
2012-01-29 13:53:31 +04:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(&local_err, QERR_QGA_COMMAND_FAILED,
|
|
|
|
"failed to open privilege token");
|
2012-01-29 13:53:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
2014-05-19 11:26:03 +04:00
|
|
|
if (token) {
|
|
|
|
CloseHandle(token);
|
|
|
|
}
|
2016-06-14 00:57:56 +03:00
|
|
|
error_propagate(errp, local_err);
|
2012-01-29 13:53:31 +04:00
|
|
|
}
|
|
|
|
|
2014-05-02 15:26:30 +04:00
|
|
|
static void execute_async(DWORD WINAPI (*func)(LPVOID), LPVOID opaque,
|
|
|
|
Error **errp)
|
2012-01-29 13:53:31 +04:00
|
|
|
{
|
|
|
|
Error *local_err = NULL;
|
|
|
|
|
|
|
|
HANDLE thread = CreateThread(NULL, 0, func, opaque, 0, NULL);
|
|
|
|
if (!thread) {
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(&local_err, QERR_QGA_COMMAND_FAILED,
|
|
|
|
"failed to dispatch asynchronous command");
|
2014-05-02 15:26:30 +04:00
|
|
|
error_propagate(errp, local_err);
|
2012-01-29 13:53:31 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-02 15:26:30 +04:00
|
|
|
void qmp_guest_shutdown(bool has_mode, const char *mode, Error **errp)
|
2012-01-29 13:53:31 +04:00
|
|
|
{
|
2014-05-02 15:26:38 +04:00
|
|
|
Error *local_err = NULL;
|
2012-01-23 06:24:37 +04:00
|
|
|
UINT shutdown_flag = EWX_FORCE;
|
|
|
|
|
|
|
|
slog("guest-shutdown called, mode: %s", mode);
|
|
|
|
|
|
|
|
if (!has_mode || strcmp(mode, "powerdown") == 0) {
|
|
|
|
shutdown_flag |= EWX_POWEROFF;
|
|
|
|
} else if (strcmp(mode, "halt") == 0) {
|
|
|
|
shutdown_flag |= EWX_SHUTDOWN;
|
|
|
|
} else if (strcmp(mode, "reboot") == 0) {
|
|
|
|
shutdown_flag |= EWX_REBOOT;
|
|
|
|
} else {
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "mode",
|
|
|
|
"halt|powerdown|reboot");
|
2012-01-23 06:24:37 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Request a shutdown privilege, but try to shut down the system
|
|
|
|
anyway. */
|
2014-05-02 15:26:38 +04:00
|
|
|
acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
|
|
|
|
if (local_err) {
|
|
|
|
error_propagate(errp, local_err);
|
2012-01-29 13:53:31 +04:00
|
|
|
return;
|
2012-01-23 06:24:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!ExitWindowsEx(shutdown_flag, SHTDN_REASON_FLAG_PLANNED)) {
|
2013-11-25 23:54:17 +04:00
|
|
|
slog("guest-shutdown failed: %lu", GetLastError());
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(errp, QERR_UNDEFINED_ERROR);
|
2012-01-23 06:24:37 +04:00
|
|
|
}
|
2012-01-20 08:04:34 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count,
|
2014-05-02 15:26:30 +04:00
|
|
|
int64_t count, Error **errp)
|
2012-01-20 08:04:34 +04:00
|
|
|
{
|
2015-02-06 20:59:55 +03:00
|
|
|
GuestFileRead *read_data = NULL;
|
|
|
|
guchar *buf;
|
|
|
|
HANDLE fh;
|
|
|
|
bool is_ok;
|
|
|
|
DWORD read_count;
|
|
|
|
GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
|
|
|
|
|
|
|
|
if (!gfh) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
if (!has_count) {
|
|
|
|
count = QGA_READ_COUNT_DEFAULT;
|
|
|
|
} else if (count < 0) {
|
|
|
|
error_setg(errp, "value '%" PRId64
|
|
|
|
"' is invalid for argument count", count);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
fh = gfh->fh;
|
|
|
|
buf = g_malloc0(count+1);
|
|
|
|
is_ok = ReadFile(fh, buf, count, &read_count, NULL);
|
|
|
|
if (!is_ok) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed to read file");
|
|
|
|
slog("guest-file-read failed, handle %" PRId64, handle);
|
|
|
|
} else {
|
|
|
|
buf[read_count] = 0;
|
2015-09-14 14:50:44 +03:00
|
|
|
read_data = g_new0(GuestFileRead, 1);
|
2015-02-06 20:59:55 +03:00
|
|
|
read_data->count = (size_t)read_count;
|
|
|
|
read_data->eof = read_count == 0;
|
|
|
|
|
|
|
|
if (read_count != 0) {
|
|
|
|
read_data->buf_b64 = g_base64_encode(buf, read_count);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
g_free(buf);
|
|
|
|
|
|
|
|
return read_data;
|
2012-01-20 08:04:34 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64,
|
2014-05-02 15:26:30 +04:00
|
|
|
bool has_count, int64_t count,
|
|
|
|
Error **errp)
|
2012-01-20 08:04:34 +04:00
|
|
|
{
|
2015-02-06 20:59:55 +03:00
|
|
|
GuestFileWrite *write_data = NULL;
|
|
|
|
guchar *buf;
|
|
|
|
gsize buf_len;
|
|
|
|
bool is_ok;
|
|
|
|
DWORD write_count;
|
|
|
|
GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
|
|
|
|
HANDLE fh;
|
|
|
|
|
|
|
|
if (!gfh) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
fh = gfh->fh;
|
2015-11-23 18:37:07 +03:00
|
|
|
buf = qbase64_decode(buf_b64, -1, &buf_len, errp);
|
|
|
|
if (!buf) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2015-02-06 20:59:55 +03:00
|
|
|
|
|
|
|
if (!has_count) {
|
|
|
|
count = buf_len;
|
|
|
|
} else if (count < 0 || count > buf_len) {
|
|
|
|
error_setg(errp, "value '%" PRId64
|
|
|
|
"' is invalid for argument count", count);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
is_ok = WriteFile(fh, buf, count, &write_count, NULL);
|
|
|
|
if (!is_ok) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed to write to file");
|
|
|
|
slog("guest-file-write-failed, handle: %" PRId64, handle);
|
|
|
|
} else {
|
2015-09-14 14:50:44 +03:00
|
|
|
write_data = g_new0(GuestFileWrite, 1);
|
2015-02-06 20:59:55 +03:00
|
|
|
write_data->count = (size_t) write_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
|
|
|
g_free(buf);
|
|
|
|
return write_data;
|
2012-01-20 08:04:34 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset,
|
2016-02-10 00:27:16 +03:00
|
|
|
GuestFileWhence *whence_code,
|
|
|
|
Error **errp)
|
2012-01-20 08:04:34 +04:00
|
|
|
{
|
2015-02-06 20:59:55 +03:00
|
|
|
GuestFileHandle *gfh;
|
|
|
|
GuestFileSeek *seek_data;
|
|
|
|
HANDLE fh;
|
|
|
|
LARGE_INTEGER new_pos, off_pos;
|
|
|
|
off_pos.QuadPart = offset;
|
|
|
|
BOOL res;
|
2015-11-25 20:37:15 +03:00
|
|
|
int whence;
|
2016-02-10 00:27:16 +03:00
|
|
|
Error *err = NULL;
|
2015-11-25 20:37:15 +03:00
|
|
|
|
2015-02-06 20:59:55 +03:00
|
|
|
gfh = guest_file_handle_find(handle, errp);
|
|
|
|
if (!gfh) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-11-25 20:37:15 +03:00
|
|
|
/* We stupidly exposed 'whence':'int' in our qapi */
|
2016-02-10 00:27:16 +03:00
|
|
|
whence = ga_parse_whence(whence_code, &err);
|
|
|
|
if (err) {
|
|
|
|
error_propagate(errp, err);
|
2015-11-25 20:37:15 +03:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-02-06 20:59:55 +03:00
|
|
|
fh = gfh->fh;
|
|
|
|
res = SetFilePointerEx(fh, off_pos, &new_pos, whence);
|
|
|
|
if (!res) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed to seek file");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
seek_data = g_new0(GuestFileSeek, 1);
|
|
|
|
seek_data->position = new_pos.QuadPart;
|
|
|
|
return seek_data;
|
2012-01-20 08:04:34 +04:00
|
|
|
}
|
|
|
|
|
2014-05-02 15:26:30 +04:00
|
|
|
void qmp_guest_file_flush(int64_t handle, Error **errp)
|
2012-01-20 08:04:34 +04:00
|
|
|
{
|
2015-02-06 20:59:55 +03:00
|
|
|
HANDLE fh;
|
|
|
|
GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
|
|
|
|
if (!gfh) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
fh = gfh->fh;
|
|
|
|
if (!FlushFileBuffers(fh)) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed to flush file");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-30 13:25:22 +03:00
|
|
|
#ifdef CONFIG_QGA_NTDDSCSI
|
|
|
|
|
|
|
|
static STORAGE_BUS_TYPE win2qemu[] = {
|
|
|
|
[BusTypeUnknown] = GUEST_DISK_BUS_TYPE_UNKNOWN,
|
|
|
|
[BusTypeScsi] = GUEST_DISK_BUS_TYPE_SCSI,
|
|
|
|
[BusTypeAtapi] = GUEST_DISK_BUS_TYPE_IDE,
|
|
|
|
[BusTypeAta] = GUEST_DISK_BUS_TYPE_IDE,
|
|
|
|
[BusType1394] = GUEST_DISK_BUS_TYPE_IEEE1394,
|
|
|
|
[BusTypeSsa] = GUEST_DISK_BUS_TYPE_SSA,
|
|
|
|
[BusTypeFibre] = GUEST_DISK_BUS_TYPE_SSA,
|
|
|
|
[BusTypeUsb] = GUEST_DISK_BUS_TYPE_USB,
|
|
|
|
[BusTypeRAID] = GUEST_DISK_BUS_TYPE_RAID,
|
|
|
|
#if (_WIN32_WINNT >= 0x0600)
|
|
|
|
[BusTypeiScsi] = GUEST_DISK_BUS_TYPE_ISCSI,
|
|
|
|
[BusTypeSas] = GUEST_DISK_BUS_TYPE_SAS,
|
|
|
|
[BusTypeSata] = GUEST_DISK_BUS_TYPE_SATA,
|
|
|
|
[BusTypeSd] = GUEST_DISK_BUS_TYPE_SD,
|
|
|
|
[BusTypeMmc] = GUEST_DISK_BUS_TYPE_MMC,
|
|
|
|
#endif
|
|
|
|
#if (_WIN32_WINNT >= 0x0601)
|
|
|
|
[BusTypeVirtual] = GUEST_DISK_BUS_TYPE_VIRTUAL,
|
|
|
|
[BusTypeFileBackedVirtual] = GUEST_DISK_BUS_TYPE_FILE_BACKED_VIRTUAL,
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
|
|
|
static GuestDiskBusType find_bus_type(STORAGE_BUS_TYPE bus)
|
|
|
|
{
|
|
|
|
if (bus > ARRAY_SIZE(win2qemu) || (int)bus < 0) {
|
|
|
|
return GUEST_DISK_BUS_TYPE_UNKNOWN;
|
|
|
|
}
|
|
|
|
return win2qemu[(int)bus];
|
|
|
|
}
|
|
|
|
|
2015-07-08 03:12:18 +03:00
|
|
|
DEFINE_GUID(GUID_DEVINTERFACE_VOLUME,
|
|
|
|
0x53f5630dL, 0xb6bf, 0x11d0, 0x94, 0xf2,
|
|
|
|
0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
|
|
|
|
|
2015-06-30 13:25:22 +03:00
|
|
|
static GuestPCIAddress *get_pci_info(char *guid, Error **errp)
|
|
|
|
{
|
2015-07-08 03:12:18 +03:00
|
|
|
HDEVINFO dev_info;
|
|
|
|
SP_DEVINFO_DATA dev_info_data;
|
|
|
|
DWORD size = 0;
|
|
|
|
int i;
|
|
|
|
char dev_name[MAX_PATH];
|
|
|
|
char *buffer = NULL;
|
|
|
|
GuestPCIAddress *pci = NULL;
|
|
|
|
char *name = g_strdup(&guid[4]);
|
|
|
|
|
|
|
|
if (!QueryDosDevice(name, dev_name, ARRAY_SIZE(dev_name))) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed to get dos device name");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_info = SetupDiGetClassDevs(&GUID_DEVINTERFACE_VOLUME, 0, 0,
|
|
|
|
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|
|
|
if (dev_info == INVALID_HANDLE_VALUE) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed to get devices tree");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
|
|
|
|
for (i = 0; SetupDiEnumDeviceInfo(dev_info, i, &dev_info_data); i++) {
|
|
|
|
DWORD addr, bus, slot, func, dev, data, size2;
|
|
|
|
while (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
|
|
|
|
SPDRP_PHYSICAL_DEVICE_OBJECT_NAME,
|
|
|
|
&data, (PBYTE)buffer, size,
|
|
|
|
&size2)) {
|
|
|
|
size = MAX(size, size2);
|
|
|
|
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
|
|
|
g_free(buffer);
|
|
|
|
/* Double the size to avoid problems on
|
|
|
|
* W2k MBCS systems per KB 888609.
|
|
|
|
* https://support.microsoft.com/en-us/kb/259695 */
|
|
|
|
buffer = g_malloc(size * 2);
|
|
|
|
} else {
|
|
|
|
error_setg_win32(errp, GetLastError(),
|
|
|
|
"failed to get device name");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (g_strcmp0(buffer, dev_name)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* There is no need to allocate buffer in the next functions. The size
|
|
|
|
* is known and ULONG according to
|
|
|
|
* https://support.microsoft.com/en-us/kb/253232
|
|
|
|
* https://msdn.microsoft.com/en-us/library/windows/hardware/ff543095(v=vs.85).aspx
|
|
|
|
*/
|
|
|
|
if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
|
|
|
|
SPDRP_BUSNUMBER, &data, (PBYTE)&bus, size, NULL)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The function retrieves the device's address. This value will be
|
|
|
|
* transformed into device function and number */
|
|
|
|
if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
|
|
|
|
SPDRP_ADDRESS, &data, (PBYTE)&addr, size, NULL)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This call returns UINumber of DEVICE_CAPABILITIES structure.
|
|
|
|
* This number is typically a user-perceived slot number. */
|
|
|
|
if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
|
|
|
|
SPDRP_UI_NUMBER, &data, (PBYTE)&slot, size, NULL)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* SetupApi gives us the same information as driver with
|
|
|
|
* IoGetDeviceProperty. According to Microsoft
|
|
|
|
* https://support.microsoft.com/en-us/kb/253232
|
|
|
|
* FunctionNumber = (USHORT)((propertyAddress) & 0x0000FFFF);
|
|
|
|
* DeviceNumber = (USHORT)(((propertyAddress) >> 16) & 0x0000FFFF);
|
|
|
|
* SPDRP_ADDRESS is propertyAddress, so we do the same.*/
|
|
|
|
|
|
|
|
func = addr & 0x0000FFFF;
|
|
|
|
dev = (addr >> 16) & 0x0000FFFF;
|
|
|
|
pci = g_malloc0(sizeof(*pci));
|
|
|
|
pci->domain = dev;
|
|
|
|
pci->slot = slot;
|
|
|
|
pci->function = func;
|
|
|
|
pci->bus = bus;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
g_free(buffer);
|
|
|
|
g_free(name);
|
|
|
|
return pci;
|
2015-06-30 13:25:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static int get_disk_bus_type(HANDLE vol_h, Error **errp)
|
|
|
|
{
|
|
|
|
STORAGE_PROPERTY_QUERY query;
|
|
|
|
STORAGE_DEVICE_DESCRIPTOR *dev_desc, buf;
|
|
|
|
DWORD received;
|
|
|
|
|
|
|
|
dev_desc = &buf;
|
|
|
|
dev_desc->Size = sizeof(buf);
|
|
|
|
query.PropertyId = StorageDeviceProperty;
|
|
|
|
query.QueryType = PropertyStandardQuery;
|
|
|
|
|
|
|
|
if (!DeviceIoControl(vol_h, IOCTL_STORAGE_QUERY_PROPERTY, &query,
|
|
|
|
sizeof(STORAGE_PROPERTY_QUERY), dev_desc,
|
|
|
|
dev_desc->Size, &received, NULL)) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed to get bus type");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dev_desc->BusType;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* VSS provider works with volumes, thus there is no difference if
|
|
|
|
* the volume consist of spanned disks. Info about the first disk in the
|
|
|
|
* volume is returned for the spanned disk group (LVM) */
|
|
|
|
static GuestDiskAddressList *build_guest_disk_info(char *guid, Error **errp)
|
|
|
|
{
|
|
|
|
GuestDiskAddressList *list = NULL;
|
|
|
|
GuestDiskAddress *disk;
|
|
|
|
SCSI_ADDRESS addr, *scsi_ad;
|
|
|
|
DWORD len;
|
|
|
|
int bus;
|
|
|
|
HANDLE vol_h;
|
|
|
|
|
|
|
|
scsi_ad = &addr;
|
|
|
|
char *name = g_strndup(guid, strlen(guid)-1);
|
|
|
|
|
|
|
|
vol_h = CreateFile(name, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
|
|
|
0, NULL);
|
|
|
|
if (vol_h == INVALID_HANDLE_VALUE) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed to open volume");
|
|
|
|
goto out_free;
|
|
|
|
}
|
|
|
|
|
|
|
|
bus = get_disk_bus_type(vol_h, errp);
|
|
|
|
if (bus < 0) {
|
|
|
|
goto out_close;
|
|
|
|
}
|
|
|
|
|
|
|
|
disk = g_malloc0(sizeof(*disk));
|
|
|
|
disk->bus_type = find_bus_type(bus);
|
|
|
|
if (bus == BusTypeScsi || bus == BusTypeAta || bus == BusTypeRAID
|
|
|
|
#if (_WIN32_WINNT >= 0x0600)
|
|
|
|
/* This bus type is not supported before Windows Server 2003 SP1 */
|
|
|
|
|| bus == BusTypeSas
|
|
|
|
#endif
|
|
|
|
) {
|
|
|
|
/* We are able to use the same ioctls for different bus types
|
|
|
|
* according to Microsoft docs
|
|
|
|
* https://technet.microsoft.com/en-us/library/ee851589(v=ws.10).aspx */
|
|
|
|
if (DeviceIoControl(vol_h, IOCTL_SCSI_GET_ADDRESS, NULL, 0, scsi_ad,
|
|
|
|
sizeof(SCSI_ADDRESS), &len, NULL)) {
|
|
|
|
disk->unit = addr.Lun;
|
|
|
|
disk->target = addr.TargetId;
|
|
|
|
disk->bus = addr.PathId;
|
|
|
|
disk->pci_controller = get_pci_info(name, errp);
|
|
|
|
}
|
|
|
|
/* We do not set error in this case, because we still have enough
|
|
|
|
* information about volume. */
|
|
|
|
} else {
|
|
|
|
disk->pci_controller = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
list = g_malloc0(sizeof(*list));
|
|
|
|
list->value = disk;
|
|
|
|
list->next = NULL;
|
|
|
|
out_close:
|
|
|
|
CloseHandle(vol_h);
|
|
|
|
out_free:
|
|
|
|
g_free(name);
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
static GuestDiskAddressList *build_guest_disk_info(char *guid, Error **errp)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* CONFIG_QGA_NTDDSCSI */
|
|
|
|
|
2015-06-30 13:25:21 +03:00
|
|
|
static GuestFilesystemInfo *build_guest_fsinfo(char *guid, Error **errp)
|
|
|
|
{
|
|
|
|
DWORD info_size;
|
|
|
|
char mnt, *mnt_point;
|
|
|
|
char fs_name[32];
|
|
|
|
char vol_info[MAX_PATH+1];
|
|
|
|
size_t len;
|
|
|
|
GuestFilesystemInfo *fs = NULL;
|
|
|
|
|
|
|
|
GetVolumePathNamesForVolumeName(guid, (LPCH)&mnt, 0, &info_size);
|
|
|
|
if (GetLastError() != ERROR_MORE_DATA) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed to get volume name");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
mnt_point = g_malloc(info_size + 1);
|
|
|
|
if (!GetVolumePathNamesForVolumeName(guid, mnt_point, info_size,
|
|
|
|
&info_size)) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed to get volume name");
|
|
|
|
goto free;
|
|
|
|
}
|
|
|
|
|
|
|
|
len = strlen(mnt_point);
|
|
|
|
mnt_point[len] = '\\';
|
|
|
|
mnt_point[len+1] = 0;
|
|
|
|
if (!GetVolumeInformation(mnt_point, vol_info, sizeof(vol_info), NULL, NULL,
|
|
|
|
NULL, (LPSTR)&fs_name, sizeof(fs_name))) {
|
|
|
|
if (GetLastError() != ERROR_NOT_READY) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed to get volume info");
|
|
|
|
}
|
|
|
|
goto free;
|
|
|
|
}
|
|
|
|
|
|
|
|
fs_name[sizeof(fs_name) - 1] = 0;
|
|
|
|
fs = g_malloc(sizeof(*fs));
|
|
|
|
fs->name = g_strdup(guid);
|
|
|
|
if (len == 0) {
|
|
|
|
fs->mountpoint = g_strdup("System Reserved");
|
|
|
|
} else {
|
|
|
|
fs->mountpoint = g_strndup(mnt_point, len);
|
|
|
|
}
|
|
|
|
fs->type = g_strdup(fs_name);
|
2015-08-26 14:17:12 +03:00
|
|
|
fs->disk = build_guest_disk_info(guid, errp);
|
2015-06-30 13:25:21 +03:00
|
|
|
free:
|
|
|
|
g_free(mnt_point);
|
|
|
|
return fs;
|
|
|
|
}
|
|
|
|
|
qga: Add guest-get-fsinfo command
Add command to get mounted filesystems information in the guest.
The returned value contains a list of mountpoint paths and
corresponding disks info such as disk bus type, drive address,
and the disk controllers' PCI addresses, so that management layer
such as libvirt can resolve the disk backends.
For example, when `lsblk' result is:
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sdb 8:16 0 1G 0 disk
`-sdb1 8:17 0 1024M 0 part
`-vg0-lv0 253:1 0 1.4G 0 lvm /mnt/test
sdc 8:32 0 1G 0 disk
`-sdc1 8:33 0 512M 0 part
`-vg0-lv0 253:1 0 1.4G 0 lvm /mnt/test
vda 252:0 0 25G 0 disk
`-vda1 252:1 0 25G 0 part /
where sdb is a SCSI disk with PCI controller 0000:00:0a.0 and ID=1,
sdc is an IDE disk with PCI controller 0000:00:01.1, and
vda is a virtio-blk disk with PCI device 0000:00:06.0,
guest-get-fsinfo command will return the following result:
{"return":
[{"name":"dm-1",
"mountpoint":"/mnt/test",
"disk":[
{"bus-type":"scsi","bus":0,"unit":1,"target":0,
"pci-controller":{"bus":0,"slot":10,"domain":0,"function":0}},
{"bus-type":"ide","bus":0,"unit":0,"target":0,
"pci-controller":{"bus":0,"slot":1,"domain":0,"function":1}}],
"type":"xfs"},
{"name":"vda1", "mountpoint":"/",
"disk":[
{"bus-type":"virtio","bus":0,"unit":0,"target":0,
"pci-controller":{"bus":0,"slot":6,"domain":0,"function":0}}],
"type":"ext4"}]}
In Linux guest, the disk information is resolved from sysfs. So far,
it only supports virtio-blk, virtio-scsi, IDE, SATA, SCSI disks on x86
hosts, and "disk" parameter may be empty for unsupported disk types.
Signed-off-by: Tomoki Sekiyama <tomoki.sekiyama@hds.com>
*updated schema to report 2.2 as initial supported version
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
2014-07-01 01:51:34 +04:00
|
|
|
GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
|
|
|
|
{
|
2015-06-30 13:25:20 +03:00
|
|
|
HANDLE vol_h;
|
|
|
|
GuestFilesystemInfoList *new, *ret = NULL;
|
|
|
|
char guid[256];
|
|
|
|
|
|
|
|
vol_h = FindFirstVolume(guid, sizeof(guid));
|
|
|
|
if (vol_h == INVALID_HANDLE_VALUE) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed to find any volume");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
2015-06-30 13:25:21 +03:00
|
|
|
GuestFilesystemInfo *info = build_guest_fsinfo(guid, errp);
|
|
|
|
if (info == NULL) {
|
|
|
|
continue;
|
|
|
|
}
|
2015-06-30 13:25:20 +03:00
|
|
|
new = g_malloc(sizeof(*ret));
|
2015-06-30 13:25:21 +03:00
|
|
|
new->value = info;
|
2015-06-30 13:25:20 +03:00
|
|
|
new->next = ret;
|
|
|
|
ret = new;
|
|
|
|
} while (FindNextVolume(vol_h, guid, sizeof(guid)));
|
|
|
|
|
|
|
|
if (GetLastError() != ERROR_NO_MORE_FILES) {
|
|
|
|
error_setg_win32(errp, GetLastError(), "failed to find next volume");
|
|
|
|
}
|
|
|
|
|
|
|
|
FindVolumeClose(vol_h);
|
|
|
|
return ret;
|
qga: Add guest-get-fsinfo command
Add command to get mounted filesystems information in the guest.
The returned value contains a list of mountpoint paths and
corresponding disks info such as disk bus type, drive address,
and the disk controllers' PCI addresses, so that management layer
such as libvirt can resolve the disk backends.
For example, when `lsblk' result is:
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sdb 8:16 0 1G 0 disk
`-sdb1 8:17 0 1024M 0 part
`-vg0-lv0 253:1 0 1.4G 0 lvm /mnt/test
sdc 8:32 0 1G 0 disk
`-sdc1 8:33 0 512M 0 part
`-vg0-lv0 253:1 0 1.4G 0 lvm /mnt/test
vda 252:0 0 25G 0 disk
`-vda1 252:1 0 25G 0 part /
where sdb is a SCSI disk with PCI controller 0000:00:0a.0 and ID=1,
sdc is an IDE disk with PCI controller 0000:00:01.1, and
vda is a virtio-blk disk with PCI device 0000:00:06.0,
guest-get-fsinfo command will return the following result:
{"return":
[{"name":"dm-1",
"mountpoint":"/mnt/test",
"disk":[
{"bus-type":"scsi","bus":0,"unit":1,"target":0,
"pci-controller":{"bus":0,"slot":10,"domain":0,"function":0}},
{"bus-type":"ide","bus":0,"unit":0,"target":0,
"pci-controller":{"bus":0,"slot":1,"domain":0,"function":1}}],
"type":"xfs"},
{"name":"vda1", "mountpoint":"/",
"disk":[
{"bus-type":"virtio","bus":0,"unit":0,"target":0,
"pci-controller":{"bus":0,"slot":6,"domain":0,"function":0}}],
"type":"ext4"}]}
In Linux guest, the disk information is resolved from sysfs. So far,
it only supports virtio-blk, virtio-scsi, IDE, SATA, SCSI disks on x86
hosts, and "disk" parameter may be empty for unsupported disk types.
Signed-off-by: Tomoki Sekiyama <tomoki.sekiyama@hds.com>
*updated schema to report 2.2 as initial supported version
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
2014-07-01 01:51:34 +04:00
|
|
|
}
|
|
|
|
|
2012-01-20 08:04:34 +04:00
|
|
|
/*
|
|
|
|
* Return status of freeze/thaw
|
|
|
|
*/
|
2014-05-02 15:26:30 +04:00
|
|
|
GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
|
2012-01-20 08:04:34 +04:00
|
|
|
{
|
2013-08-07 19:40:25 +04:00
|
|
|
if (!vss_initialized()) {
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(errp, QERR_UNSUPPORTED);
|
2013-08-07 19:40:25 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ga_is_frozen(ga_state)) {
|
|
|
|
return GUEST_FSFREEZE_STATUS_FROZEN;
|
|
|
|
}
|
|
|
|
|
|
|
|
return GUEST_FSFREEZE_STATUS_THAWED;
|
2012-01-20 08:04:34 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2013-08-07 19:40:25 +04:00
|
|
|
* Freeze local file systems using Volume Shadow-copy Service.
|
|
|
|
* The frozen state is limited for up to 10 seconds by VSS.
|
2012-01-20 08:04:34 +04:00
|
|
|
*/
|
2014-05-02 15:26:30 +04:00
|
|
|
int64_t qmp_guest_fsfreeze_freeze(Error **errp)
|
2012-01-20 08:04:34 +04:00
|
|
|
{
|
2013-08-07 19:40:25 +04:00
|
|
|
int i;
|
|
|
|
Error *local_err = NULL;
|
|
|
|
|
|
|
|
if (!vss_initialized()) {
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(errp, QERR_UNSUPPORTED);
|
2013-08-07 19:40:25 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
slog("guest-fsfreeze called");
|
|
|
|
|
|
|
|
/* cannot risk guest agent blocking itself on a write in this state */
|
|
|
|
ga_set_frozen(ga_state);
|
|
|
|
|
2014-05-02 15:26:38 +04:00
|
|
|
qga_vss_fsfreeze(&i, &local_err, true);
|
|
|
|
if (local_err) {
|
|
|
|
error_propagate(errp, local_err);
|
2013-08-07 19:40:25 +04:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
return i;
|
|
|
|
|
|
|
|
error:
|
2014-05-02 15:26:38 +04:00
|
|
|
local_err = NULL;
|
2013-08-07 19:40:25 +04:00
|
|
|
qmp_guest_fsfreeze_thaw(&local_err);
|
2014-01-30 18:07:28 +04:00
|
|
|
if (local_err) {
|
2013-08-07 19:40:25 +04:00
|
|
|
g_debug("cleanup thaw: %s", error_get_pretty(local_err));
|
|
|
|
error_free(local_err);
|
|
|
|
}
|
2012-01-20 08:04:34 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-07-01 01:51:27 +04:00
|
|
|
int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
|
|
|
|
strList *mountpoints,
|
|
|
|
Error **errp)
|
|
|
|
{
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(errp, QERR_UNSUPPORTED);
|
2014-07-01 01:51:27 +04:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-01-20 08:04:34 +04:00
|
|
|
/*
|
2013-08-07 19:40:25 +04:00
|
|
|
* Thaw local file systems using Volume Shadow-copy Service.
|
2012-01-20 08:04:34 +04:00
|
|
|
*/
|
2014-05-02 15:26:30 +04:00
|
|
|
int64_t qmp_guest_fsfreeze_thaw(Error **errp)
|
2012-01-20 08:04:34 +04:00
|
|
|
{
|
2013-08-07 19:40:25 +04:00
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!vss_initialized()) {
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(errp, QERR_UNSUPPORTED);
|
2013-08-07 19:40:25 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-05-02 15:26:30 +04:00
|
|
|
qga_vss_fsfreeze(&i, errp, false);
|
2013-08-07 19:40:25 +04:00
|
|
|
|
|
|
|
ga_unset_frozen(ga_state);
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void guest_fsfreeze_cleanup(void)
|
|
|
|
{
|
|
|
|
Error *err = NULL;
|
|
|
|
|
|
|
|
if (!vss_initialized()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
|
|
|
|
qmp_guest_fsfreeze_thaw(&err);
|
|
|
|
if (err) {
|
|
|
|
slog("failed to clean up frozen filesystems: %s",
|
|
|
|
error_get_pretty(err));
|
|
|
|
error_free(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
vss_deinit(true);
|
2012-01-20 08:04:34 +04:00
|
|
|
}
|
|
|
|
|
2012-06-13 09:41:28 +04:00
|
|
|
/*
|
|
|
|
* Walk list of mounted file systems in the guest, and discard unused
|
|
|
|
* areas.
|
|
|
|
*/
|
2015-05-11 09:58:45 +03:00
|
|
|
GuestFilesystemTrimResponse *
|
|
|
|
qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
|
2012-06-13 09:41:28 +04:00
|
|
|
{
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(errp, QERR_UNSUPPORTED);
|
2015-05-11 09:58:45 +03:00
|
|
|
return NULL;
|
2012-06-13 09:41:28 +04:00
|
|
|
}
|
|
|
|
|
2012-01-29 13:53:31 +04:00
|
|
|
typedef enum {
|
2012-03-12 21:50:02 +04:00
|
|
|
GUEST_SUSPEND_MODE_DISK,
|
|
|
|
GUEST_SUSPEND_MODE_RAM
|
2012-01-29 13:53:31 +04:00
|
|
|
} GuestSuspendMode;
|
|
|
|
|
2014-05-02 15:26:30 +04:00
|
|
|
static void check_suspend_mode(GuestSuspendMode mode, Error **errp)
|
2012-01-29 13:53:31 +04:00
|
|
|
{
|
|
|
|
SYSTEM_POWER_CAPABILITIES sys_pwr_caps;
|
|
|
|
Error *local_err = NULL;
|
|
|
|
|
|
|
|
ZeroMemory(&sys_pwr_caps, sizeof(sys_pwr_caps));
|
|
|
|
if (!GetPwrCapabilities(&sys_pwr_caps)) {
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(&local_err, QERR_QGA_COMMAND_FAILED,
|
|
|
|
"failed to determine guest suspend capabilities");
|
2012-01-29 13:53:31 +04:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2012-03-12 21:50:02 +04:00
|
|
|
switch (mode) {
|
|
|
|
case GUEST_SUSPEND_MODE_DISK:
|
|
|
|
if (!sys_pwr_caps.SystemS4) {
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(&local_err, QERR_QGA_COMMAND_FAILED,
|
|
|
|
"suspend-to-disk not supported by OS");
|
2012-01-29 13:53:31 +04:00
|
|
|
}
|
2012-03-12 21:50:02 +04:00
|
|
|
break;
|
|
|
|
case GUEST_SUSPEND_MODE_RAM:
|
|
|
|
if (!sys_pwr_caps.SystemS3) {
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(&local_err, QERR_QGA_COMMAND_FAILED,
|
|
|
|
"suspend-to-ram not supported by OS");
|
2012-03-12 21:50:02 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(&local_err, QERR_INVALID_PARAMETER_VALUE, "mode",
|
|
|
|
"GuestSuspendMode");
|
2012-01-29 13:53:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
2016-06-14 00:57:56 +03:00
|
|
|
error_propagate(errp, local_err);
|
2012-01-29 13:53:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static DWORD WINAPI do_suspend(LPVOID opaque)
|
|
|
|
{
|
|
|
|
GuestSuspendMode *mode = opaque;
|
|
|
|
DWORD ret = 0;
|
|
|
|
|
|
|
|
if (!SetSuspendState(*mode == GUEST_SUSPEND_MODE_DISK, TRUE, TRUE)) {
|
2013-11-25 23:54:17 +04:00
|
|
|
slog("failed to suspend guest, %lu", GetLastError());
|
2012-01-29 13:53:31 +04:00
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
g_free(mode);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-05-02 15:26:30 +04:00
|
|
|
void qmp_guest_suspend_disk(Error **errp)
|
qemu-ga: add guest-suspend-disk
As the command name implies, this command suspends the guest to disk.
The suspend operation is implemented by two functions: bios_supports_mode()
and guest_suspend(). Both functions are generic enough to be used by
other suspend modes (introduced by next commits).
Both functions will try to use the scripts provided by the pm-utils
package if it's available. If it's not available, a manual method,
which consists of directly writing to '/sys/power/state', will be used.
To reap terminated children, a new signal handler is installed in the
parent to catch SIGCHLD signals and a non-blocking call to waitpid()
is done to collect their exit statuses. The statuses, however, are
discarded.
The approach used to query the guest for suspend support deserves some
explanation. It's implemented by bios_supports_mode() and shown below:
qemu-ga
|
create pipe
|
fork()
-----------------
| |
| |
| fork()
| --------------------------
| | |
| | |
| | exec('pm-is-supported')
| |
| wait()
| write exit status to pipe
| exit
|
read pipe
This might look complex, but the resulting code is quite simple.
The purpose of that approach is to allow qemu-ga to reap its children
(semi-)automatically from its SIGCHLD handler.
Implementing this the obvious way, that's, doing the exec() call from
the first child process, would force us to introduce a more complex way
to reap qemu-ga's children. Like registering PIDs to be reaped and
having a way to wait for them when returning their exit status to
qemu-ga is necessary. The approach explained above avoids that complexity.
Signed-off-by: Luiz Capitulino <lcapitulino@redhat.com>
2012-02-28 18:03:03 +04:00
|
|
|
{
|
2014-05-02 15:26:38 +04:00
|
|
|
Error *local_err = NULL;
|
2015-09-14 14:50:44 +03:00
|
|
|
GuestSuspendMode *mode = g_new(GuestSuspendMode, 1);
|
2012-01-29 13:53:31 +04:00
|
|
|
|
|
|
|
*mode = GUEST_SUSPEND_MODE_DISK;
|
2014-05-02 15:26:38 +04:00
|
|
|
check_suspend_mode(*mode, &local_err);
|
|
|
|
acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
|
|
|
|
execute_async(do_suspend, mode, &local_err);
|
2012-01-29 13:53:31 +04:00
|
|
|
|
2014-05-02 15:26:38 +04:00
|
|
|
if (local_err) {
|
|
|
|
error_propagate(errp, local_err);
|
2012-01-29 13:53:31 +04:00
|
|
|
g_free(mode);
|
|
|
|
}
|
qemu-ga: add guest-suspend-disk
As the command name implies, this command suspends the guest to disk.
The suspend operation is implemented by two functions: bios_supports_mode()
and guest_suspend(). Both functions are generic enough to be used by
other suspend modes (introduced by next commits).
Both functions will try to use the scripts provided by the pm-utils
package if it's available. If it's not available, a manual method,
which consists of directly writing to '/sys/power/state', will be used.
To reap terminated children, a new signal handler is installed in the
parent to catch SIGCHLD signals and a non-blocking call to waitpid()
is done to collect their exit statuses. The statuses, however, are
discarded.
The approach used to query the guest for suspend support deserves some
explanation. It's implemented by bios_supports_mode() and shown below:
qemu-ga
|
create pipe
|
fork()
-----------------
| |
| |
| fork()
| --------------------------
| | |
| | |
| | exec('pm-is-supported')
| |
| wait()
| write exit status to pipe
| exit
|
read pipe
This might look complex, but the resulting code is quite simple.
The purpose of that approach is to allow qemu-ga to reap its children
(semi-)automatically from its SIGCHLD handler.
Implementing this the obvious way, that's, doing the exec() call from
the first child process, would force us to introduce a more complex way
to reap qemu-ga's children. Like registering PIDs to be reaped and
having a way to wait for them when returning their exit status to
qemu-ga is necessary. The approach explained above avoids that complexity.
Signed-off-by: Luiz Capitulino <lcapitulino@redhat.com>
2012-02-28 18:03:03 +04:00
|
|
|
}
|
|
|
|
|
2014-05-02 15:26:30 +04:00
|
|
|
void qmp_guest_suspend_ram(Error **errp)
|
2012-02-28 18:03:04 +04:00
|
|
|
{
|
2014-05-02 15:26:38 +04:00
|
|
|
Error *local_err = NULL;
|
2015-09-14 14:50:44 +03:00
|
|
|
GuestSuspendMode *mode = g_new(GuestSuspendMode, 1);
|
2012-03-12 21:50:02 +04:00
|
|
|
|
|
|
|
*mode = GUEST_SUSPEND_MODE_RAM;
|
2014-05-02 15:26:38 +04:00
|
|
|
check_suspend_mode(*mode, &local_err);
|
|
|
|
acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
|
|
|
|
execute_async(do_suspend, mode, &local_err);
|
2012-03-12 21:50:02 +04:00
|
|
|
|
2014-05-02 15:26:38 +04:00
|
|
|
if (local_err) {
|
|
|
|
error_propagate(errp, local_err);
|
2012-03-12 21:50:02 +04:00
|
|
|
g_free(mode);
|
|
|
|
}
|
2012-02-28 18:03:04 +04:00
|
|
|
}
|
|
|
|
|
2014-05-02 15:26:30 +04:00
|
|
|
void qmp_guest_suspend_hybrid(Error **errp)
|
2012-02-28 18:03:05 +04:00
|
|
|
{
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(errp, QERR_UNSUPPORTED);
|
2012-02-28 18:03:05 +04:00
|
|
|
}
|
|
|
|
|
2015-06-02 20:41:07 +03:00
|
|
|
static IP_ADAPTER_ADDRESSES *guest_get_adapters_addresses(Error **errp)
|
2012-02-29 20:02:23 +04:00
|
|
|
{
|
2015-06-02 20:41:07 +03:00
|
|
|
IP_ADAPTER_ADDRESSES *adptr_addrs = NULL;
|
|
|
|
ULONG adptr_addrs_len = 0;
|
|
|
|
DWORD ret;
|
|
|
|
|
|
|
|
/* Call the first time to get the adptr_addrs_len. */
|
|
|
|
GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX,
|
|
|
|
NULL, adptr_addrs, &adptr_addrs_len);
|
|
|
|
|
|
|
|
adptr_addrs = g_malloc(adptr_addrs_len);
|
|
|
|
ret = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX,
|
|
|
|
NULL, adptr_addrs, &adptr_addrs_len);
|
|
|
|
if (ret != ERROR_SUCCESS) {
|
|
|
|
error_setg_win32(errp, ret, "failed to get adapters addresses");
|
|
|
|
g_free(adptr_addrs);
|
|
|
|
adptr_addrs = NULL;
|
|
|
|
}
|
|
|
|
return adptr_addrs;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *guest_wctomb_dup(WCHAR *wstr)
|
|
|
|
{
|
|
|
|
char *str;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
i = wcslen(wstr) + 1;
|
|
|
|
str = g_malloc(i);
|
|
|
|
WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK,
|
|
|
|
wstr, -1, str, i, NULL, NULL);
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *guest_addr_to_str(IP_ADAPTER_UNICAST_ADDRESS *ip_addr,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
char addr_str[INET6_ADDRSTRLEN + INET_ADDRSTRLEN];
|
|
|
|
DWORD len;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (ip_addr->Address.lpSockaddr->sa_family == AF_INET ||
|
|
|
|
ip_addr->Address.lpSockaddr->sa_family == AF_INET6) {
|
|
|
|
len = sizeof(addr_str);
|
|
|
|
ret = WSAAddressToString(ip_addr->Address.lpSockaddr,
|
|
|
|
ip_addr->Address.iSockaddrLength,
|
|
|
|
NULL,
|
|
|
|
addr_str,
|
|
|
|
&len);
|
|
|
|
if (ret != 0) {
|
|
|
|
error_setg_win32(errp, WSAGetLastError(),
|
|
|
|
"failed address presentation form conversion");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return g_strdup(addr_str);
|
|
|
|
}
|
2012-02-29 20:02:23 +04:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-06-02 20:41:07 +03:00
|
|
|
#if (_WIN32_WINNT >= 0x0600)
|
|
|
|
static int64_t guest_ip_prefix(IP_ADAPTER_UNICAST_ADDRESS *ip_addr)
|
|
|
|
{
|
|
|
|
/* For Windows Vista/2008 and newer, use the OnLinkPrefixLength
|
|
|
|
* field to obtain the prefix.
|
|
|
|
*/
|
|
|
|
return ip_addr->OnLinkPrefixLength;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
/* When using the Windows XP and 2003 build environment, do the best we can to
|
|
|
|
* figure out the prefix.
|
|
|
|
*/
|
|
|
|
static IP_ADAPTER_INFO *guest_get_adapters_info(void)
|
|
|
|
{
|
|
|
|
IP_ADAPTER_INFO *adptr_info = NULL;
|
|
|
|
ULONG adptr_info_len = 0;
|
|
|
|
DWORD ret;
|
|
|
|
|
|
|
|
/* Call the first time to get the adptr_info_len. */
|
|
|
|
GetAdaptersInfo(adptr_info, &adptr_info_len);
|
|
|
|
|
|
|
|
adptr_info = g_malloc(adptr_info_len);
|
|
|
|
ret = GetAdaptersInfo(adptr_info, &adptr_info_len);
|
|
|
|
if (ret != ERROR_SUCCESS) {
|
|
|
|
g_free(adptr_info);
|
|
|
|
adptr_info = NULL;
|
|
|
|
}
|
|
|
|
return adptr_info;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int64_t guest_ip_prefix(IP_ADAPTER_UNICAST_ADDRESS *ip_addr)
|
|
|
|
{
|
|
|
|
int64_t prefix = -1; /* Use for AF_INET6 and unknown/undetermined values. */
|
|
|
|
IP_ADAPTER_INFO *adptr_info, *info;
|
|
|
|
IP_ADDR_STRING *ip;
|
|
|
|
struct in_addr *p;
|
|
|
|
|
|
|
|
if (ip_addr->Address.lpSockaddr->sa_family != AF_INET) {
|
|
|
|
return prefix;
|
|
|
|
}
|
|
|
|
adptr_info = guest_get_adapters_info();
|
|
|
|
if (adptr_info == NULL) {
|
|
|
|
return prefix;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Match up the passed in ip_addr with one found in adaptr_info.
|
|
|
|
* The matching one in adptr_info will have the netmask.
|
|
|
|
*/
|
|
|
|
p = &((struct sockaddr_in *)ip_addr->Address.lpSockaddr)->sin_addr;
|
|
|
|
for (info = adptr_info; info; info = info->Next) {
|
|
|
|
for (ip = &info->IpAddressList; ip; ip = ip->Next) {
|
|
|
|
if (p->S_un.S_addr == inet_addr(ip->IpAddress.String)) {
|
|
|
|
prefix = ctpop32(inet_addr(ip->IpMask.String));
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
g_free(adptr_info);
|
|
|
|
return prefix;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
|
|
|
|
{
|
|
|
|
IP_ADAPTER_ADDRESSES *adptr_addrs, *addr;
|
|
|
|
IP_ADAPTER_UNICAST_ADDRESS *ip_addr = NULL;
|
|
|
|
GuestNetworkInterfaceList *head = NULL, *cur_item = NULL;
|
|
|
|
GuestIpAddressList *head_addr, *cur_addr;
|
|
|
|
GuestNetworkInterfaceList *info;
|
|
|
|
GuestIpAddressList *address_item = NULL;
|
|
|
|
unsigned char *mac_addr;
|
|
|
|
char *addr_str;
|
|
|
|
WORD wsa_version;
|
|
|
|
WSADATA wsa_data;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
adptr_addrs = guest_get_adapters_addresses(errp);
|
|
|
|
if (adptr_addrs == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Make WSA APIs available. */
|
|
|
|
wsa_version = MAKEWORD(2, 2);
|
|
|
|
ret = WSAStartup(wsa_version, &wsa_data);
|
|
|
|
if (ret != 0) {
|
|
|
|
error_setg_win32(errp, ret, "failed socket startup");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (addr = adptr_addrs; addr; addr = addr->Next) {
|
|
|
|
info = g_malloc0(sizeof(*info));
|
|
|
|
|
|
|
|
if (cur_item == NULL) {
|
|
|
|
head = cur_item = info;
|
|
|
|
} else {
|
|
|
|
cur_item->next = info;
|
|
|
|
cur_item = info;
|
|
|
|
}
|
|
|
|
|
|
|
|
info->value = g_malloc0(sizeof(*info->value));
|
|
|
|
info->value->name = guest_wctomb_dup(addr->FriendlyName);
|
|
|
|
|
|
|
|
if (addr->PhysicalAddressLength != 0) {
|
|
|
|
mac_addr = addr->PhysicalAddress;
|
|
|
|
|
|
|
|
info->value->hardware_address =
|
|
|
|
g_strdup_printf("%02x:%02x:%02x:%02x:%02x:%02x",
|
|
|
|
(int) mac_addr[0], (int) mac_addr[1],
|
|
|
|
(int) mac_addr[2], (int) mac_addr[3],
|
|
|
|
(int) mac_addr[4], (int) mac_addr[5]);
|
|
|
|
|
|
|
|
info->value->has_hardware_address = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
head_addr = NULL;
|
|
|
|
cur_addr = NULL;
|
|
|
|
for (ip_addr = addr->FirstUnicastAddress;
|
|
|
|
ip_addr;
|
|
|
|
ip_addr = ip_addr->Next) {
|
|
|
|
addr_str = guest_addr_to_str(ip_addr, errp);
|
|
|
|
if (addr_str == NULL) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
address_item = g_malloc0(sizeof(*address_item));
|
|
|
|
|
|
|
|
if (!cur_addr) {
|
|
|
|
head_addr = cur_addr = address_item;
|
|
|
|
} else {
|
|
|
|
cur_addr->next = address_item;
|
|
|
|
cur_addr = address_item;
|
|
|
|
}
|
|
|
|
|
|
|
|
address_item->value = g_malloc0(sizeof(*address_item->value));
|
|
|
|
address_item->value->ip_address = addr_str;
|
|
|
|
address_item->value->prefix = guest_ip_prefix(ip_addr);
|
|
|
|
if (ip_addr->Address.lpSockaddr->sa_family == AF_INET) {
|
|
|
|
address_item->value->ip_address_type =
|
|
|
|
GUEST_IP_ADDRESS_TYPE_IPV4;
|
|
|
|
} else if (ip_addr->Address.lpSockaddr->sa_family == AF_INET6) {
|
|
|
|
address_item->value->ip_address_type =
|
|
|
|
GUEST_IP_ADDRESS_TYPE_IPV6;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (head_addr) {
|
|
|
|
info->value->has_ip_addresses = true;
|
|
|
|
info->value->ip_addresses = head_addr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
WSACleanup();
|
|
|
|
out:
|
|
|
|
g_free(adptr_addrs);
|
|
|
|
return head;
|
|
|
|
}
|
|
|
|
|
2013-03-05 13:39:11 +04:00
|
|
|
int64_t qmp_guest_get_time(Error **errp)
|
|
|
|
{
|
2013-03-15 13:29:04 +04:00
|
|
|
SYSTEMTIME ts = {0};
|
|
|
|
FILETIME tf;
|
|
|
|
|
|
|
|
GetSystemTime(&ts);
|
|
|
|
if (ts.wYear < 1601 || ts.wYear > 30827) {
|
|
|
|
error_setg(errp, "Failed to get time");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!SystemTimeToFileTime(&ts, &tf)) {
|
|
|
|
error_setg(errp, "Failed to convert system time: %d", (int)GetLastError());
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2016-06-14 00:57:58 +03:00
|
|
|
return ((((int64_t)tf.dwHighDateTime << 32) | tf.dwLowDateTime)
|
2013-03-15 13:29:04 +04:00
|
|
|
- W32_FT_OFFSET) * 100;
|
2013-03-05 13:39:11 +04:00
|
|
|
}
|
|
|
|
|
2014-01-31 14:29:51 +04:00
|
|
|
void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
|
2013-03-05 13:39:12 +04:00
|
|
|
{
|
2014-05-02 15:26:38 +04:00
|
|
|
Error *local_err = NULL;
|
2013-03-15 13:29:05 +04:00
|
|
|
SYSTEMTIME ts;
|
|
|
|
FILETIME tf;
|
|
|
|
LONGLONG time;
|
|
|
|
|
2015-01-21 14:09:50 +03:00
|
|
|
if (!has_time) {
|
|
|
|
/* Unfortunately, Windows libraries don't provide an easy way to access
|
|
|
|
* RTC yet:
|
|
|
|
*
|
|
|
|
* https://msdn.microsoft.com/en-us/library/aa908981.aspx
|
|
|
|
*/
|
|
|
|
error_setg(errp, "Time argument is required on this platform");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Validate time passed by user. */
|
|
|
|
if (time_ns < 0 || time_ns / 100 > INT64_MAX - W32_FT_OFFSET) {
|
|
|
|
error_setg(errp, "Time %" PRId64 "is invalid", time_ns);
|
|
|
|
return;
|
|
|
|
}
|
2013-03-15 13:29:05 +04:00
|
|
|
|
2015-01-21 14:09:50 +03:00
|
|
|
time = time_ns / 100 + W32_FT_OFFSET;
|
2013-03-15 13:29:05 +04:00
|
|
|
|
2015-01-21 14:09:50 +03:00
|
|
|
tf.dwLowDateTime = (DWORD) time;
|
|
|
|
tf.dwHighDateTime = (DWORD) (time >> 32);
|
2013-03-15 13:29:05 +04:00
|
|
|
|
2015-01-21 14:09:50 +03:00
|
|
|
if (!FileTimeToSystemTime(&tf, &ts)) {
|
|
|
|
error_setg(errp, "Failed to convert system time %d",
|
|
|
|
(int)GetLastError());
|
|
|
|
return;
|
2013-03-15 13:29:05 +04:00
|
|
|
}
|
|
|
|
|
2014-05-02 15:26:38 +04:00
|
|
|
acquire_privilege(SE_SYSTEMTIME_NAME, &local_err);
|
|
|
|
if (local_err) {
|
|
|
|
error_propagate(errp, local_err);
|
2013-03-15 13:29:05 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!SetSystemTime(&ts)) {
|
|
|
|
error_setg(errp, "Failed to set time to guest: %d", (int)GetLastError());
|
|
|
|
return;
|
|
|
|
}
|
2013-03-05 13:39:12 +04:00
|
|
|
}
|
|
|
|
|
2013-03-07 01:59:29 +04:00
|
|
|
GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
|
|
|
|
{
|
2015-11-02 17:49:48 +03:00
|
|
|
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION pslpi, ptr;
|
|
|
|
DWORD length;
|
|
|
|
GuestLogicalProcessorList *head, **link;
|
|
|
|
Error *local_err = NULL;
|
|
|
|
int64_t current;
|
|
|
|
|
|
|
|
ptr = pslpi = NULL;
|
|
|
|
length = 0;
|
|
|
|
current = 0;
|
|
|
|
head = NULL;
|
|
|
|
link = &head;
|
|
|
|
|
|
|
|
if ((GetLogicalProcessorInformation(pslpi, &length) == FALSE) &&
|
|
|
|
(GetLastError() == ERROR_INSUFFICIENT_BUFFER) &&
|
|
|
|
(length > sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION))) {
|
|
|
|
ptr = pslpi = g_malloc0(length);
|
|
|
|
if (GetLogicalProcessorInformation(pslpi, &length) == FALSE) {
|
|
|
|
error_setg(&local_err, "Failed to get processor information: %d",
|
|
|
|
(int)GetLastError());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
error_setg(&local_err,
|
|
|
|
"Failed to get processor information buffer length: %d",
|
|
|
|
(int)GetLastError());
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((local_err == NULL) && (length > 0)) {
|
|
|
|
if (pslpi->Relationship == RelationProcessorCore) {
|
|
|
|
ULONG_PTR cpu_bits = pslpi->ProcessorMask;
|
|
|
|
|
|
|
|
while (cpu_bits > 0) {
|
|
|
|
if (!!(cpu_bits & 1)) {
|
|
|
|
GuestLogicalProcessor *vcpu;
|
|
|
|
GuestLogicalProcessorList *entry;
|
|
|
|
|
|
|
|
vcpu = g_malloc0(sizeof *vcpu);
|
|
|
|
vcpu->logical_id = current++;
|
|
|
|
vcpu->online = true;
|
|
|
|
vcpu->has_can_offline = false;
|
|
|
|
|
|
|
|
entry = g_malloc0(sizeof *entry);
|
|
|
|
entry->value = vcpu;
|
|
|
|
|
|
|
|
*link = entry;
|
|
|
|
link = &entry->next;
|
|
|
|
}
|
|
|
|
cpu_bits >>= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
length -= sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
|
|
|
|
pslpi++; /* next entry */
|
|
|
|
}
|
|
|
|
|
|
|
|
g_free(ptr);
|
|
|
|
|
|
|
|
if (local_err == NULL) {
|
|
|
|
if (head != NULL) {
|
|
|
|
return head;
|
|
|
|
}
|
|
|
|
/* there's no guest with zero VCPUs */
|
|
|
|
error_setg(&local_err, "Guest reported zero VCPUs");
|
|
|
|
}
|
|
|
|
|
|
|
|
qapi_free_GuestLogicalProcessorList(head);
|
|
|
|
error_propagate(errp, local_err);
|
2013-03-07 01:59:29 +04:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
|
|
|
|
{
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(errp, QERR_UNSUPPORTED);
|
2013-03-07 01:59:29 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-06-30 17:37:13 +03:00
|
|
|
static gchar *
|
|
|
|
get_net_error_message(gint error)
|
|
|
|
{
|
|
|
|
HMODULE module = NULL;
|
|
|
|
gchar *retval = NULL;
|
|
|
|
wchar_t *msg = NULL;
|
2016-02-17 19:47:52 +03:00
|
|
|
int flags;
|
|
|
|
size_t nchars;
|
2015-06-30 17:37:13 +03:00
|
|
|
|
2016-02-17 19:47:51 +03:00
|
|
|
flags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
|
|
FORMAT_MESSAGE_IGNORE_INSERTS |
|
|
|
|
FORMAT_MESSAGE_FROM_SYSTEM;
|
2015-06-30 17:37:13 +03:00
|
|
|
|
|
|
|
if (error >= NERR_BASE && error <= MAX_NERR) {
|
|
|
|
module = LoadLibraryExW(L"netmsg.dll", NULL, LOAD_LIBRARY_AS_DATAFILE);
|
|
|
|
|
|
|
|
if (module != NULL) {
|
|
|
|
flags |= FORMAT_MESSAGE_FROM_HMODULE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FormatMessageW(flags, module, error, 0, (LPWSTR)&msg, 0, NULL);
|
|
|
|
|
|
|
|
if (msg != NULL) {
|
|
|
|
nchars = wcslen(msg);
|
|
|
|
|
2016-02-17 19:47:54 +03:00
|
|
|
if (nchars >= 2 &&
|
2016-02-17 19:47:53 +03:00
|
|
|
msg[nchars - 1] == L'\n' &&
|
|
|
|
msg[nchars - 2] == L'\r') {
|
|
|
|
msg[nchars - 2] = L'\0';
|
2015-06-30 17:37:13 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
retval = g_utf16_to_utf8(msg, -1, NULL, NULL, NULL);
|
|
|
|
|
|
|
|
LocalFree(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (module != NULL) {
|
|
|
|
FreeLibrary(module);
|
|
|
|
}
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
qga: add guest-set-user-password command
Add a new 'guest-set-user-password' command for changing the password
of guest OS user accounts. This command is needed to enable OpenStack
to support its API for changing the admin password of guests running
on KVM/QEMU. It is not practical to provide a command at the QEMU
level explicitly targetting administrator account password change
only, since different guest OS have different names for the admin
account. While UNIX systems use 'root', Windows systems typically
use 'Administrator' and even that can be renamed. Higher level apps
like OpenStack have the ability to figure out the correct admin
account name since they have info that QEMU/libvirt do not.
The command accepts either the clear text password string, encoded
in base64 to make it 8-bit safe in JSON:
$ echo -n "123456" | base64
MTIzNDU2
$ virsh -c qemu:///system qemu-agent-command f21x86_64 \
'{ "execute": "guest-set-user-password",
"arguments": { "crypted": false,
"username": "root",
"password": "MTIzNDU2" } }'
{"return":{}}
Or a password that has already been run though a crypt(3) like
algorithm appropriate for the guest, again then base64 encoded:
$ echo -n '$6$n01A2Tau$e...snip...DfMOP7of9AJ1I8q0' | base64
JDYkb...snip...YT2Ey
$ virsh -c qemu:///system qemu-agent-command f21x86_64 \
'{ "execute": "guest-set-user-password",
"arguments": { "crypted": true,
"username": "root",
"password": "JDYkb...snip...YT2Ey" } }'
NB windows support is desirable, but not implemented in this
patch.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
2015-02-11 14:26:12 +03:00
|
|
|
void qmp_guest_set_user_password(const char *username,
|
|
|
|
const char *password,
|
|
|
|
bool crypted,
|
|
|
|
Error **errp)
|
|
|
|
{
|
2015-06-30 17:37:13 +03:00
|
|
|
NET_API_STATUS nas;
|
|
|
|
char *rawpasswddata = NULL;
|
|
|
|
size_t rawpasswdlen;
|
2016-02-17 19:47:55 +03:00
|
|
|
wchar_t *user = NULL, *wpass = NULL;
|
2015-06-30 17:37:13 +03:00
|
|
|
USER_INFO_1003 pi1003 = { 0, };
|
2016-02-17 19:47:55 +03:00
|
|
|
GError *gerr = NULL;
|
2015-06-30 17:37:13 +03:00
|
|
|
|
|
|
|
if (crypted) {
|
|
|
|
error_setg(errp, QERR_UNSUPPORTED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-23 18:37:07 +03:00
|
|
|
rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp);
|
|
|
|
if (!rawpasswddata) {
|
|
|
|
return;
|
|
|
|
}
|
2015-06-30 17:37:13 +03:00
|
|
|
rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
|
|
|
|
rawpasswddata[rawpasswdlen] = '\0';
|
|
|
|
|
2016-02-17 19:47:55 +03:00
|
|
|
user = g_utf8_to_utf16(username, -1, NULL, NULL, &gerr);
|
|
|
|
if (!user) {
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
wpass = g_utf8_to_utf16(rawpasswddata, -1, NULL, NULL, &gerr);
|
|
|
|
if (!wpass) {
|
|
|
|
goto done;
|
|
|
|
}
|
2015-06-30 17:37:13 +03:00
|
|
|
|
|
|
|
pi1003.usri1003_password = wpass;
|
|
|
|
nas = NetUserSetInfo(NULL, user,
|
|
|
|
1003, (LPBYTE)&pi1003,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
if (nas != NERR_Success) {
|
|
|
|
gchar *msg = get_net_error_message(nas);
|
|
|
|
error_setg(errp, "failed to set password: %s", msg);
|
|
|
|
g_free(msg);
|
|
|
|
}
|
|
|
|
|
2016-02-17 19:47:55 +03:00
|
|
|
done:
|
|
|
|
if (gerr) {
|
|
|
|
error_setg(errp, QERR_QGA_COMMAND_FAILED, gerr->message);
|
|
|
|
g_error_free(gerr);
|
|
|
|
}
|
2015-06-30 17:37:13 +03:00
|
|
|
g_free(user);
|
|
|
|
g_free(wpass);
|
|
|
|
g_free(rawpasswddata);
|
qga: add guest-set-user-password command
Add a new 'guest-set-user-password' command for changing the password
of guest OS user accounts. This command is needed to enable OpenStack
to support its API for changing the admin password of guests running
on KVM/QEMU. It is not practical to provide a command at the QEMU
level explicitly targetting administrator account password change
only, since different guest OS have different names for the admin
account. While UNIX systems use 'root', Windows systems typically
use 'Administrator' and even that can be renamed. Higher level apps
like OpenStack have the ability to figure out the correct admin
account name since they have info that QEMU/libvirt do not.
The command accepts either the clear text password string, encoded
in base64 to make it 8-bit safe in JSON:
$ echo -n "123456" | base64
MTIzNDU2
$ virsh -c qemu:///system qemu-agent-command f21x86_64 \
'{ "execute": "guest-set-user-password",
"arguments": { "crypted": false,
"username": "root",
"password": "MTIzNDU2" } }'
{"return":{}}
Or a password that has already been run though a crypt(3) like
algorithm appropriate for the guest, again then base64 encoded:
$ echo -n '$6$n01A2Tau$e...snip...DfMOP7of9AJ1I8q0' | base64
JDYkb...snip...YT2Ey
$ virsh -c qemu:///system qemu-agent-command f21x86_64 \
'{ "execute": "guest-set-user-password",
"arguments": { "crypted": true,
"username": "root",
"password": "JDYkb...snip...YT2Ey" } }'
NB windows support is desirable, but not implemented in this
patch.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
2015-02-11 14:26:12 +03:00
|
|
|
}
|
|
|
|
|
2015-01-22 05:40:02 +03:00
|
|
|
GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
|
|
|
|
{
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(errp, QERR_UNSUPPORTED);
|
2015-01-22 05:40:02 +03:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
GuestMemoryBlockResponseList *
|
|
|
|
qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp)
|
|
|
|
{
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(errp, QERR_UNSUPPORTED);
|
2015-01-22 05:40:02 +03:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
|
|
|
|
{
|
2015-03-17 13:54:50 +03:00
|
|
|
error_setg(errp, QERR_UNSUPPORTED);
|
2015-01-22 05:40:02 +03:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-07-01 01:51:40 +04:00
|
|
|
/* add unsupported commands to the blacklist */
|
|
|
|
GList *ga_command_blacklist_init(GList *blacklist)
|
|
|
|
{
|
|
|
|
const char *list_unsupported[] = {
|
2015-06-02 20:41:07 +03:00
|
|
|
"guest-suspend-hybrid",
|
2015-11-02 17:49:48 +03:00
|
|
|
"guest-set-vcpus",
|
2015-01-22 05:40:06 +03:00
|
|
|
"guest-get-memory-blocks", "guest-set-memory-blocks",
|
|
|
|
"guest-get-memory-block-size",
|
2015-06-30 13:25:20 +03:00
|
|
|
"guest-fsfreeze-freeze-list",
|
2014-07-01 01:51:40 +04:00
|
|
|
"guest-fstrim", NULL};
|
|
|
|
char **p = (char **)list_unsupported;
|
|
|
|
|
|
|
|
while (*p) {
|
2015-08-27 02:34:50 +03:00
|
|
|
blacklist = g_list_append(blacklist, g_strdup(*p++));
|
2014-07-01 01:51:40 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!vss_init(true)) {
|
2015-05-06 14:57:38 +03:00
|
|
|
g_debug("vss_init failed, vss commands are going to be disabled");
|
2014-07-01 01:51:40 +04:00
|
|
|
const char *list[] = {
|
|
|
|
"guest-get-fsinfo", "guest-fsfreeze-status",
|
|
|
|
"guest-fsfreeze-freeze", "guest-fsfreeze-thaw", NULL};
|
|
|
|
p = (char **)list;
|
|
|
|
|
|
|
|
while (*p) {
|
2015-08-27 02:34:50 +03:00
|
|
|
blacklist = g_list_append(blacklist, g_strdup(*p++));
|
2014-07-01 01:51:40 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return blacklist;
|
|
|
|
}
|
|
|
|
|
2012-01-20 08:04:34 +04:00
|
|
|
/* register init/cleanup routines for stateful command groups */
|
|
|
|
void ga_command_state_init(GAState *s, GACommandState *cs)
|
|
|
|
{
|
2014-07-01 01:51:40 +04:00
|
|
|
if (!vss_initialized()) {
|
2013-08-07 19:40:25 +04:00
|
|
|
ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
|
|
|
|
}
|
2012-01-20 08:04:34 +04:00
|
|
|
}
|