mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-01-03 19:45:21 +00:00
virDomainMemoryPeek API
* include/libvirt/libvirt.h.in, src/libvirt.c, src/driver.h, src/libvirt_sym.version: New virDomainMemoryPeek API. * qemud/remote.c, qemud/remote_protocol.x, src/remote_internal.c: Support for remote. * src/qemu_driver.c: QEMU driver implementation of API. * src/test.c: Test driver (null) implementation of API. * docs/hvsupport.html.in: Document API. * libvirt.spec.in: New path /var/cache/libvirt for temporary storage of memory images. * qemud/libvirtd.init.in: Remove any old temp files in /var/cache/libvirt on restarts. * src/Makefile.am: make install creates /var/cache/libvirt. * configure.in: Detect mkdir -p.
This commit is contained in:
parent
0d0c96722c
commit
6bcf25017b
17
ChangeLog
17
ChangeLog
@ -1,3 +1,20 @@
|
|||||||
|
Tue Jun 10 11:34:00 BST 2008 Richard W.M. Jones <rjones@redhat.com>
|
||||||
|
|
||||||
|
virDomainMemoryPeek API
|
||||||
|
* include/libvirt/libvirt.h.in, src/libvirt.c, src/driver.h,
|
||||||
|
src/libvirt_sym.version: New virDomainMemoryPeek API.
|
||||||
|
* qemud/remote.c, qemud/remote_protocol.x, src/remote_internal.c:
|
||||||
|
Support for remote.
|
||||||
|
* src/qemu_driver.c: QEMU driver implementation of API.
|
||||||
|
* src/test.c: Test driver (null) implementation of API.
|
||||||
|
* docs/hvsupport.html.in: Document API.
|
||||||
|
* libvirt.spec.in: New path /var/cache/libvirt for temporary
|
||||||
|
storage of memory images.
|
||||||
|
* qemud/libvirtd.init.in: Remove any old temp files in
|
||||||
|
/var/cache/libvirt on restarts.
|
||||||
|
* src/Makefile.am: make install creates /var/cache/libvirt.
|
||||||
|
* configure.in: Detect mkdir -p.
|
||||||
|
|
||||||
Mon Jun 9 15:42:34 PST 2008 David L. Leskovec <dlesko@linux.vnet.ibm.com>
|
Mon Jun 9 15:42:34 PST 2008 David L. Leskovec <dlesko@linux.vnet.ibm.com>
|
||||||
|
|
||||||
* src/lxc_driver.c: Console element is output only. Always open new
|
* src/lxc_driver.c: Console element is output only. Always open new
|
||||||
|
@ -99,6 +99,8 @@ AC_PATH_PROG([TAR], [tar], [/bin/tar])
|
|||||||
AC_PATH_PROG([XMLLINT], [xmllint], [/usr/bin/xmllint])
|
AC_PATH_PROG([XMLLINT], [xmllint], [/usr/bin/xmllint])
|
||||||
AC_PATH_PROG([XSLTPROC], [xsltproc], [/usr/bin/xsltproc])
|
AC_PATH_PROG([XSLTPROC], [xsltproc], [/usr/bin/xsltproc])
|
||||||
|
|
||||||
|
AC_PROG_MKDIR_P
|
||||||
|
|
||||||
dnl External programs that we can use if they are available.
|
dnl External programs that we can use if they are available.
|
||||||
dnl We will hard-code paths to these programs unless we cannot
|
dnl We will hard-code paths to these programs unless we cannot
|
||||||
dnl detect them, in which case we'll search for the program
|
dnl detect them, in which case we'll search for the program
|
||||||
|
@ -145,9 +145,9 @@ updated on <i>2008-06-05</i>.
|
|||||||
<tr>
|
<tr>
|
||||||
<td> virDomainBlockPeek </td>
|
<td> virDomainBlockPeek </td>
|
||||||
<td> 0.4.3 </td>
|
<td> 0.4.3 </td>
|
||||||
<td> x </td>
|
<td> 0.4.3 </td>
|
||||||
<td> x </td>
|
<td> 0.4.3 </td>
|
||||||
<td> x </td>
|
<td> 0.4.3 </td>
|
||||||
<td> x </td>
|
<td> x </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -486,6 +486,14 @@ updated on <i>2008-06-05</i>.
|
|||||||
<td> 0.1.0 </td>
|
<td> 0.1.0 </td>
|
||||||
<td colspan="4"> not a HV function </td>
|
<td colspan="4"> not a HV function </td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td> virDomainMemoryPeek </td>
|
||||||
|
<td> 0.4.3 </td>
|
||||||
|
<td> x </td>
|
||||||
|
<td> 0.4.3 </td>
|
||||||
|
<td> 0.4.3 </td>
|
||||||
|
<td> x </td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td> virNodeGetInfo </td>
|
<td> virNodeGetInfo </td>
|
||||||
<td> 0.1.0 </td>
|
<td> 0.1.0 </td>
|
||||||
|
@ -537,6 +537,17 @@ int virDomainBlockPeek (virDomainPtr dom,
|
|||||||
void *buffer,
|
void *buffer,
|
||||||
unsigned int flags);
|
unsigned int flags);
|
||||||
|
|
||||||
|
/* Memory peeking flags. */
|
||||||
|
typedef enum {
|
||||||
|
VIR_MEMORY_VIRTUAL = 1, /* addresses are virtual addresses */
|
||||||
|
} virDomainMemoryFlags;
|
||||||
|
|
||||||
|
int virDomainMemoryPeek (virDomainPtr dom,
|
||||||
|
unsigned long long start,
|
||||||
|
size_t size,
|
||||||
|
void *buffer,
|
||||||
|
unsigned int flags);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* defined but not running domains
|
* defined but not running domains
|
||||||
*/
|
*/
|
||||||
|
@ -537,6 +537,17 @@ int virDomainBlockPeek (virDomainPtr dom,
|
|||||||
void *buffer,
|
void *buffer,
|
||||||
unsigned int flags);
|
unsigned int flags);
|
||||||
|
|
||||||
|
/* Memory peeking flags. */
|
||||||
|
typedef enum {
|
||||||
|
VIR_MEMORY_VIRTUAL = 1, /* addresses are virtual addresses */
|
||||||
|
} virDomainMemoryFlags;
|
||||||
|
|
||||||
|
int virDomainMemoryPeek (virDomainPtr dom,
|
||||||
|
unsigned long long start,
|
||||||
|
size_t size,
|
||||||
|
void *buffer,
|
||||||
|
unsigned int flags);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* defined but not running domains
|
* defined but not running domains
|
||||||
*/
|
*/
|
||||||
|
@ -139,6 +139,7 @@ rm -f $RPM_BUILD_ROOT%{_libdir}/*.a
|
|||||||
rm -f $RPM_BUILD_ROOT%{_libdir}/python*/site-packages/*.la
|
rm -f $RPM_BUILD_ROOT%{_libdir}/python*/site-packages/*.la
|
||||||
rm -f $RPM_BUILD_ROOT%{_libdir}/python*/site-packages/*.a
|
rm -f $RPM_BUILD_ROOT%{_libdir}/python*/site-packages/*.a
|
||||||
install -d -m 0755 $RPM_BUILD_ROOT%{_localstatedir}/run/libvirt/
|
install -d -m 0755 $RPM_BUILD_ROOT%{_localstatedir}/run/libvirt/
|
||||||
|
install -d -m 0755 $RPM_BUILD_ROOT%{_localstatedir}/cache/libvirt/
|
||||||
|
|
||||||
# We don't want to install /etc/libvirt/qemu/networks in the main %files list
|
# We don't want to install /etc/libvirt/qemu/networks in the main %files list
|
||||||
# because if the admin wants to delete the default network completely, we don't
|
# because if the admin wants to delete the default network completely, we don't
|
||||||
@ -202,6 +203,7 @@ fi
|
|||||||
%dir %{_datadir}/libvirt/networks/
|
%dir %{_datadir}/libvirt/networks/
|
||||||
%{_datadir}/libvirt/networks/default.xml
|
%{_datadir}/libvirt/networks/default.xml
|
||||||
%dir %{_localstatedir}/run/libvirt/
|
%dir %{_localstatedir}/run/libvirt/
|
||||||
|
%dir %{_localstatedir}/cache/libvirt/
|
||||||
%dir %{_localstatedir}/lib/libvirt/
|
%dir %{_localstatedir}/lib/libvirt/
|
||||||
%if %{with_polkit}
|
%if %{with_polkit}
|
||||||
%{_datadir}/PolicyKit/policy/libvirtd.policy
|
%{_datadir}/PolicyKit/policy/libvirtd.policy
|
||||||
|
@ -51,6 +51,8 @@ RETVAL=0
|
|||||||
|
|
||||||
start() {
|
start() {
|
||||||
echo -n $"Starting $SERVICE daemon: "
|
echo -n $"Starting $SERVICE daemon: "
|
||||||
|
mkdir -p @localstatedir@/cache/libvirt
|
||||||
|
rm -rf @localstatedir@/cache/libvirt/*
|
||||||
KRB5_KTNAME=$KRB5_KTNAME daemon --check $SERVICE $PROCESS --daemon $LIBVIRTD_CONFIG_ARGS $LIBVIRTD_ARGS
|
KRB5_KTNAME=$KRB5_KTNAME daemon --check $SERVICE $PROCESS --daemon $LIBVIRTD_CONFIG_ARGS $LIBVIRTD_ARGS
|
||||||
RETVAL=$?
|
RETVAL=$?
|
||||||
echo
|
echo
|
||||||
@ -66,6 +68,7 @@ stop() {
|
|||||||
if [ $RETVAL -eq 0 ]; then
|
if [ $RETVAL -eq 0 ]; then
|
||||||
rm -f @localstatedir@/lock/subsys/$SERVICE
|
rm -f @localstatedir@/lock/subsys/$SERVICE
|
||||||
rm -f @localstatedir@/run/$SERVICE.pid
|
rm -f @localstatedir@/run/$SERVICE.pid
|
||||||
|
rm -rf @localstatedir@/cache/libvirt/*
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -938,6 +938,53 @@ remoteDispatchDomainBlockPeek (struct qemud_server *server ATTRIBUTE_UNUSED,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
remoteDispatchDomainMemoryPeek (struct qemud_server *server ATTRIBUTE_UNUSED,
|
||||||
|
struct qemud_client *client,
|
||||||
|
remote_message_header *req,
|
||||||
|
remote_domain_memory_peek_args *args,
|
||||||
|
remote_domain_memory_peek_ret *ret)
|
||||||
|
{
|
||||||
|
virDomainPtr dom;
|
||||||
|
unsigned long long offset;
|
||||||
|
size_t size;
|
||||||
|
unsigned int flags;
|
||||||
|
CHECK_CONN (client);
|
||||||
|
|
||||||
|
dom = get_nonnull_domain (client->conn, args->dom);
|
||||||
|
if (dom == NULL) {
|
||||||
|
remoteDispatchError (client, req, "%s", _("domain not found"));
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
offset = args->offset;
|
||||||
|
size = args->size;
|
||||||
|
flags = args->flags;
|
||||||
|
|
||||||
|
if (size > REMOTE_DOMAIN_MEMORY_PEEK_BUFFER_MAX) {
|
||||||
|
remoteDispatchError (client, req,
|
||||||
|
"%s", _("size > maximum buffer size"));
|
||||||
|
virDomainFree (dom);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret->buffer.buffer_len = size;
|
||||||
|
if (VIR_ALLOC_N (ret->buffer.buffer_val, size) < 0) {
|
||||||
|
remoteDispatchError (client, req, "%s", strerror (errno));
|
||||||
|
virDomainFree (dom);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (virDomainMemoryPeek (dom, offset, size,
|
||||||
|
ret->buffer.buffer_val, flags) == -1) {
|
||||||
|
/* free (ret->buffer.buffer_val); - caller frees */
|
||||||
|
virDomainFree (dom);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
virDomainFree (dom);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
remoteDispatchDomainAttachDevice (struct qemud_server *server ATTRIBUTE_UNUSED,
|
remoteDispatchDomainAttachDevice (struct qemud_server *server ATTRIBUTE_UNUSED,
|
||||||
struct qemud_client *client,
|
struct qemud_client *client,
|
||||||
|
@ -91,6 +91,8 @@ remote_domain_get_scheduler_parameters_ret lv_remote_domain_get_scheduler_parame
|
|||||||
remote_node_get_info_ret lv_remote_node_get_info_ret;
|
remote_node_get_info_ret lv_remote_node_get_info_ret;
|
||||||
remote_network_lookup_by_name_args lv_remote_network_lookup_by_name_args;
|
remote_network_lookup_by_name_args lv_remote_network_lookup_by_name_args;
|
||||||
remote_network_lookup_by_name_ret lv_remote_network_lookup_by_name_ret;
|
remote_network_lookup_by_name_ret lv_remote_network_lookup_by_name_ret;
|
||||||
|
remote_domain_memory_peek_args lv_remote_domain_memory_peek_args;
|
||||||
|
remote_domain_memory_peek_ret lv_remote_domain_memory_peek_ret;
|
||||||
remote_num_of_defined_domains_ret lv_remote_num_of_defined_domains_ret;
|
remote_num_of_defined_domains_ret lv_remote_num_of_defined_domains_ret;
|
||||||
remote_domain_block_stats_args lv_remote_domain_block_stats_args;
|
remote_domain_block_stats_args lv_remote_domain_block_stats_args;
|
||||||
remote_domain_block_stats_ret lv_remote_domain_block_stats_ret;
|
remote_domain_block_stats_ret lv_remote_domain_block_stats_ret;
|
||||||
|
@ -224,6 +224,15 @@ case REMOTE_PROC_DOMAIN_LOOKUP_BY_UUID:
|
|||||||
ret = (char *) &lv_remote_domain_lookup_by_uuid_ret;
|
ret = (char *) &lv_remote_domain_lookup_by_uuid_ret;
|
||||||
memset (&lv_remote_domain_lookup_by_uuid_ret, 0, sizeof lv_remote_domain_lookup_by_uuid_ret);
|
memset (&lv_remote_domain_lookup_by_uuid_ret, 0, sizeof lv_remote_domain_lookup_by_uuid_ret);
|
||||||
break;
|
break;
|
||||||
|
case REMOTE_PROC_DOMAIN_MEMORY_PEEK:
|
||||||
|
fn = (dispatch_fn) remoteDispatchDomainMemoryPeek;
|
||||||
|
args_filter = (xdrproc_t) xdr_remote_domain_memory_peek_args;
|
||||||
|
args = (char *) &lv_remote_domain_memory_peek_args;
|
||||||
|
memset (&lv_remote_domain_memory_peek_args, 0, sizeof lv_remote_domain_memory_peek_args);
|
||||||
|
ret_filter = (xdrproc_t) xdr_remote_domain_memory_peek_ret;
|
||||||
|
ret = (char *) &lv_remote_domain_memory_peek_ret;
|
||||||
|
memset (&lv_remote_domain_memory_peek_ret, 0, sizeof lv_remote_domain_memory_peek_ret);
|
||||||
|
break;
|
||||||
case REMOTE_PROC_DOMAIN_MIGRATE_FINISH:
|
case REMOTE_PROC_DOMAIN_MIGRATE_FINISH:
|
||||||
fn = (dispatch_fn) remoteDispatchDomainMigrateFinish;
|
fn = (dispatch_fn) remoteDispatchDomainMigrateFinish;
|
||||||
args_filter = (xdrproc_t) xdr_remote_domain_migrate_finish_args;
|
args_filter = (xdrproc_t) xdr_remote_domain_migrate_finish_args;
|
||||||
|
@ -30,6 +30,7 @@ static int remoteDispatchDomainInterfaceStats (struct qemud_server *server, stru
|
|||||||
static int remoteDispatchDomainLookupById (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_lookup_by_id_args *args, remote_domain_lookup_by_id_ret *ret);
|
static int remoteDispatchDomainLookupById (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_lookup_by_id_args *args, remote_domain_lookup_by_id_ret *ret);
|
||||||
static int remoteDispatchDomainLookupByName (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_lookup_by_name_args *args, remote_domain_lookup_by_name_ret *ret);
|
static int remoteDispatchDomainLookupByName (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_lookup_by_name_args *args, remote_domain_lookup_by_name_ret *ret);
|
||||||
static int remoteDispatchDomainLookupByUuid (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_lookup_by_uuid_args *args, remote_domain_lookup_by_uuid_ret *ret);
|
static int remoteDispatchDomainLookupByUuid (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_lookup_by_uuid_args *args, remote_domain_lookup_by_uuid_ret *ret);
|
||||||
|
static int remoteDispatchDomainMemoryPeek (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_memory_peek_args *args, remote_domain_memory_peek_ret *ret);
|
||||||
static int remoteDispatchDomainMigrateFinish (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_migrate_finish_args *args, remote_domain_migrate_finish_ret *ret);
|
static int remoteDispatchDomainMigrateFinish (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_migrate_finish_args *args, remote_domain_migrate_finish_ret *ret);
|
||||||
static int remoteDispatchDomainMigratePerform (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_migrate_perform_args *args, void *ret);
|
static int remoteDispatchDomainMigratePerform (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_migrate_perform_args *args, void *ret);
|
||||||
static int remoteDispatchDomainMigratePrepare (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_migrate_prepare_args *args, remote_domain_migrate_prepare_ret *ret);
|
static int remoteDispatchDomainMigratePrepare (struct qemud_server *server, struct qemud_client *client, remote_message_header *req, remote_domain_migrate_prepare_args *args, remote_domain_migrate_prepare_ret *ret);
|
||||||
|
@ -561,6 +561,31 @@ xdr_remote_domain_block_peek_ret (XDR *xdrs, remote_domain_block_peek_ret *objp)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool_t
|
||||||
|
xdr_remote_domain_memory_peek_args (XDR *xdrs, remote_domain_memory_peek_args *objp)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!xdr_remote_nonnull_domain (xdrs, &objp->dom))
|
||||||
|
return FALSE;
|
||||||
|
if (!xdr_u_quad_t (xdrs, &objp->offset))
|
||||||
|
return FALSE;
|
||||||
|
if (!xdr_u_int (xdrs, &objp->size))
|
||||||
|
return FALSE;
|
||||||
|
if (!xdr_u_int (xdrs, &objp->flags))
|
||||||
|
return FALSE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t
|
||||||
|
xdr_remote_domain_memory_peek_ret (XDR *xdrs, remote_domain_memory_peek_ret *objp)
|
||||||
|
{
|
||||||
|
char **objp_cpp0 = (char **) (void *) &objp->buffer.buffer_val;
|
||||||
|
|
||||||
|
if (!xdr_bytes (xdrs, objp_cpp0, (u_int *) &objp->buffer.buffer_len, REMOTE_DOMAIN_MEMORY_PEEK_BUFFER_MAX))
|
||||||
|
return FALSE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
bool_t
|
bool_t
|
||||||
xdr_remote_list_domains_args (XDR *xdrs, remote_list_domains_args *objp)
|
xdr_remote_list_domains_args (XDR *xdrs, remote_list_domains_args *objp)
|
||||||
{
|
{
|
||||||
|
@ -34,6 +34,7 @@ typedef remote_nonnull_string *remote_string;
|
|||||||
#define REMOTE_AUTH_SASL_DATA_MAX 65536
|
#define REMOTE_AUTH_SASL_DATA_MAX 65536
|
||||||
#define REMOTE_AUTH_TYPE_LIST_MAX 20
|
#define REMOTE_AUTH_TYPE_LIST_MAX 20
|
||||||
#define REMOTE_DOMAIN_BLOCK_PEEK_BUFFER_MAX 65536
|
#define REMOTE_DOMAIN_BLOCK_PEEK_BUFFER_MAX 65536
|
||||||
|
#define REMOTE_DOMAIN_MEMORY_PEEK_BUFFER_MAX 65536
|
||||||
|
|
||||||
typedef char remote_uuid[VIR_UUID_BUFLEN];
|
typedef char remote_uuid[VIR_UUID_BUFLEN];
|
||||||
|
|
||||||
@ -281,6 +282,22 @@ struct remote_domain_block_peek_ret {
|
|||||||
};
|
};
|
||||||
typedef struct remote_domain_block_peek_ret remote_domain_block_peek_ret;
|
typedef struct remote_domain_block_peek_ret remote_domain_block_peek_ret;
|
||||||
|
|
||||||
|
struct remote_domain_memory_peek_args {
|
||||||
|
remote_nonnull_domain dom;
|
||||||
|
u_quad_t offset;
|
||||||
|
u_int size;
|
||||||
|
u_int flags;
|
||||||
|
};
|
||||||
|
typedef struct remote_domain_memory_peek_args remote_domain_memory_peek_args;
|
||||||
|
|
||||||
|
struct remote_domain_memory_peek_ret {
|
||||||
|
struct {
|
||||||
|
u_int buffer_len;
|
||||||
|
char *buffer_val;
|
||||||
|
} buffer;
|
||||||
|
};
|
||||||
|
typedef struct remote_domain_memory_peek_ret remote_domain_memory_peek_ret;
|
||||||
|
|
||||||
struct remote_list_domains_args {
|
struct remote_list_domains_args {
|
||||||
int maxids;
|
int maxids;
|
||||||
};
|
};
|
||||||
@ -1157,6 +1174,7 @@ enum remote_procedure {
|
|||||||
REMOTE_PROC_NODE_GET_CELLS_FREE_MEMORY = 101,
|
REMOTE_PROC_NODE_GET_CELLS_FREE_MEMORY = 101,
|
||||||
REMOTE_PROC_NODE_GET_FREE_MEMORY = 102,
|
REMOTE_PROC_NODE_GET_FREE_MEMORY = 102,
|
||||||
REMOTE_PROC_DOMAIN_BLOCK_PEEK = 103,
|
REMOTE_PROC_DOMAIN_BLOCK_PEEK = 103,
|
||||||
|
REMOTE_PROC_DOMAIN_MEMORY_PEEK = 104,
|
||||||
};
|
};
|
||||||
typedef enum remote_procedure remote_procedure;
|
typedef enum remote_procedure remote_procedure;
|
||||||
|
|
||||||
@ -1227,6 +1245,8 @@ extern bool_t xdr_remote_domain_interface_stats_args (XDR *, remote_domain_inte
|
|||||||
extern bool_t xdr_remote_domain_interface_stats_ret (XDR *, remote_domain_interface_stats_ret*);
|
extern bool_t xdr_remote_domain_interface_stats_ret (XDR *, remote_domain_interface_stats_ret*);
|
||||||
extern bool_t xdr_remote_domain_block_peek_args (XDR *, remote_domain_block_peek_args*);
|
extern bool_t xdr_remote_domain_block_peek_args (XDR *, remote_domain_block_peek_args*);
|
||||||
extern bool_t xdr_remote_domain_block_peek_ret (XDR *, remote_domain_block_peek_ret*);
|
extern bool_t xdr_remote_domain_block_peek_ret (XDR *, remote_domain_block_peek_ret*);
|
||||||
|
extern bool_t xdr_remote_domain_memory_peek_args (XDR *, remote_domain_memory_peek_args*);
|
||||||
|
extern bool_t xdr_remote_domain_memory_peek_ret (XDR *, remote_domain_memory_peek_ret*);
|
||||||
extern bool_t xdr_remote_list_domains_args (XDR *, remote_list_domains_args*);
|
extern bool_t xdr_remote_list_domains_args (XDR *, remote_list_domains_args*);
|
||||||
extern bool_t xdr_remote_list_domains_ret (XDR *, remote_list_domains_ret*);
|
extern bool_t xdr_remote_list_domains_ret (XDR *, remote_list_domains_ret*);
|
||||||
extern bool_t xdr_remote_num_of_domains_ret (XDR *, remote_num_of_domains_ret*);
|
extern bool_t xdr_remote_num_of_domains_ret (XDR *, remote_num_of_domains_ret*);
|
||||||
@ -1404,6 +1424,8 @@ extern bool_t xdr_remote_domain_interface_stats_args ();
|
|||||||
extern bool_t xdr_remote_domain_interface_stats_ret ();
|
extern bool_t xdr_remote_domain_interface_stats_ret ();
|
||||||
extern bool_t xdr_remote_domain_block_peek_args ();
|
extern bool_t xdr_remote_domain_block_peek_args ();
|
||||||
extern bool_t xdr_remote_domain_block_peek_ret ();
|
extern bool_t xdr_remote_domain_block_peek_ret ();
|
||||||
|
extern bool_t xdr_remote_domain_memory_peek_args ();
|
||||||
|
extern bool_t xdr_remote_domain_memory_peek_ret ();
|
||||||
extern bool_t xdr_remote_list_domains_args ();
|
extern bool_t xdr_remote_list_domains_args ();
|
||||||
extern bool_t xdr_remote_list_domains_ret ();
|
extern bool_t xdr_remote_list_domains_ret ();
|
||||||
extern bool_t xdr_remote_num_of_domains_ret ();
|
extern bool_t xdr_remote_num_of_domains_ret ();
|
||||||
|
@ -96,12 +96,18 @@ const REMOTE_AUTH_SASL_DATA_MAX = 65536;
|
|||||||
/* Maximum number of auth types */
|
/* Maximum number of auth types */
|
||||||
const REMOTE_AUTH_TYPE_LIST_MAX = 20;
|
const REMOTE_AUTH_TYPE_LIST_MAX = 20;
|
||||||
|
|
||||||
/* Maximum length of a block or memory peek buffer message.
|
/* Maximum length of a block peek buffer message.
|
||||||
* Note applications need to be aware of this limit and issue multiple
|
* Note applications need to be aware of this limit and issue multiple
|
||||||
* requests for large amounts of data.
|
* requests for large amounts of data.
|
||||||
*/
|
*/
|
||||||
const REMOTE_DOMAIN_BLOCK_PEEK_BUFFER_MAX = 65536;
|
const REMOTE_DOMAIN_BLOCK_PEEK_BUFFER_MAX = 65536;
|
||||||
|
|
||||||
|
/* Maximum length of a memory peek buffer message.
|
||||||
|
* Note applications need to be aware of this limit and issue multiple
|
||||||
|
* requests for large amounts of data.
|
||||||
|
*/
|
||||||
|
const REMOTE_DOMAIN_MEMORY_PEEK_BUFFER_MAX = 65536;
|
||||||
|
|
||||||
/* UUID. VIR_UUID_BUFLEN definition comes from libvirt.h */
|
/* UUID. VIR_UUID_BUFLEN definition comes from libvirt.h */
|
||||||
typedef opaque remote_uuid[VIR_UUID_BUFLEN];
|
typedef opaque remote_uuid[VIR_UUID_BUFLEN];
|
||||||
|
|
||||||
@ -340,6 +346,17 @@ struct remote_domain_block_peek_ret {
|
|||||||
opaque buffer<REMOTE_DOMAIN_BLOCK_PEEK_BUFFER_MAX>;
|
opaque buffer<REMOTE_DOMAIN_BLOCK_PEEK_BUFFER_MAX>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct remote_domain_memory_peek_args {
|
||||||
|
remote_nonnull_domain dom;
|
||||||
|
unsigned hyper offset;
|
||||||
|
unsigned size;
|
||||||
|
unsigned flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct remote_domain_memory_peek_ret {
|
||||||
|
opaque buffer<REMOTE_DOMAIN_MEMORY_PEEK_BUFFER_MAX>;
|
||||||
|
};
|
||||||
|
|
||||||
struct remote_list_domains_args {
|
struct remote_list_domains_args {
|
||||||
int maxids;
|
int maxids;
|
||||||
};
|
};
|
||||||
@ -1056,7 +1073,8 @@ enum remote_procedure {
|
|||||||
REMOTE_PROC_NODE_GET_CELLS_FREE_MEMORY = 101,
|
REMOTE_PROC_NODE_GET_CELLS_FREE_MEMORY = 101,
|
||||||
REMOTE_PROC_NODE_GET_FREE_MEMORY = 102,
|
REMOTE_PROC_NODE_GET_FREE_MEMORY = 102,
|
||||||
|
|
||||||
REMOTE_PROC_DOMAIN_BLOCK_PEEK = 103
|
REMOTE_PROC_DOMAIN_BLOCK_PEEK = 103,
|
||||||
|
REMOTE_PROC_DOMAIN_MEMORY_PEEK = 104
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Custom RPC structure. */
|
/* Custom RPC structure. */
|
||||||
|
@ -149,4 +149,8 @@ else
|
|||||||
EXTRA_DIST += parthelper.c
|
EXTRA_DIST += parthelper.c
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# Create the /var/cache/libvirt directory when installing.
|
||||||
|
install-exec-local:
|
||||||
|
$(MKDIR_P) $(DESTDIR)@localstatedir@/cache/libvirt
|
||||||
|
|
||||||
CLEANFILES = *.gcov .libs/*.gcda .libs/*.gcno *.gcno *.gcda
|
CLEANFILES = *.gcov .libs/*.gcda .libs/*.gcno *.gcno *.gcda
|
||||||
|
@ -233,6 +233,13 @@ typedef int
|
|||||||
void *buffer,
|
void *buffer,
|
||||||
unsigned int flags);
|
unsigned int flags);
|
||||||
|
|
||||||
|
typedef int
|
||||||
|
(*virDrvDomainMemoryPeek)
|
||||||
|
(virDomainPtr domain,
|
||||||
|
unsigned long long start, size_t size,
|
||||||
|
void *buffer,
|
||||||
|
unsigned int flags);
|
||||||
|
|
||||||
typedef int
|
typedef int
|
||||||
(*virDrvDomainMigratePrepare)
|
(*virDrvDomainMigratePrepare)
|
||||||
(virConnectPtr dconn,
|
(virConnectPtr dconn,
|
||||||
@ -346,6 +353,7 @@ struct _virDriver {
|
|||||||
virDrvDomainBlockStats domainBlockStats;
|
virDrvDomainBlockStats domainBlockStats;
|
||||||
virDrvDomainInterfaceStats domainInterfaceStats;
|
virDrvDomainInterfaceStats domainInterfaceStats;
|
||||||
virDrvDomainBlockPeek domainBlockPeek;
|
virDrvDomainBlockPeek domainBlockPeek;
|
||||||
|
virDrvDomainMemoryPeek domainMemoryPeek;
|
||||||
virDrvNodeGetCellsFreeMemory nodeGetCellsFreeMemory;
|
virDrvNodeGetCellsFreeMemory nodeGetCellsFreeMemory;
|
||||||
virDrvNodeGetFreeMemory getFreeMemory;
|
virDrvNodeGetFreeMemory getFreeMemory;
|
||||||
};
|
};
|
||||||
|
@ -2619,6 +2619,10 @@ virDomainInterfaceStats (virDomainPtr dom, const char *path,
|
|||||||
*
|
*
|
||||||
* 'buffer' is the return buffer and must be at least 'size' bytes.
|
* 'buffer' is the return buffer and must be at least 'size' bytes.
|
||||||
*
|
*
|
||||||
|
* NB. The remote driver imposes a 64K byte limit on 'size'.
|
||||||
|
* For your program to be able to work reliably over a remote
|
||||||
|
* connection you should split large requests to <= 65536 bytes.
|
||||||
|
*
|
||||||
* Returns: 0 in case of success or -1 in case of failure.
|
* Returns: 0 in case of success or -1 in case of failure.
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
@ -2666,6 +2670,96 @@ virDomainBlockPeek (virDomainPtr dom,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* virDomainMemoryPeek:
|
||||||
|
* @dom: pointer to the domain object
|
||||||
|
* @start: start of memory to peek
|
||||||
|
* @size: size of memory to peek
|
||||||
|
* @buffer: return buffer (must be at least size bytes)
|
||||||
|
* @flags: flags, see below
|
||||||
|
*
|
||||||
|
* This function allows you to read the contents of a domain's
|
||||||
|
* memory.
|
||||||
|
*
|
||||||
|
* The memory which is read is controlled by the 'start', 'size'
|
||||||
|
* and 'flags' parameters.
|
||||||
|
*
|
||||||
|
* If 'flags' is VIR_MEMORY_VIRTUAL then the 'start' and 'size'
|
||||||
|
* parameters are interpreted as virtual memory addresses for
|
||||||
|
* whichever task happens to be running on the domain at the
|
||||||
|
* moment. Although this sounds haphazard it is in fact what
|
||||||
|
* you want in order to read Linux kernel state, because it
|
||||||
|
* ensures that pointers in the kernel image can be interpreted
|
||||||
|
* coherently.
|
||||||
|
*
|
||||||
|
* 'buffer' is the return buffer and must be at least 'size' bytes.
|
||||||
|
* 'size' may be 0 to test if the call would succeed.
|
||||||
|
*
|
||||||
|
* NB. The remote driver imposes a 64K byte limit on 'size'.
|
||||||
|
* For your program to be able to work reliably over a remote
|
||||||
|
* connection you should split large requests to <= 65536 bytes.
|
||||||
|
*
|
||||||
|
* Returns: 0 in case of success or -1 in case of failure.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
virDomainMemoryPeek (virDomainPtr dom,
|
||||||
|
unsigned long long start /* really 64 bits */,
|
||||||
|
size_t size,
|
||||||
|
void *buffer,
|
||||||
|
unsigned int flags)
|
||||||
|
{
|
||||||
|
virConnectPtr conn;
|
||||||
|
DEBUG ("domain=%p, start=%lld, size=%zi, buffer=%p, flags=%d",
|
||||||
|
dom, start, size, buffer, flags);
|
||||||
|
|
||||||
|
if (!VIR_IS_CONNECTED_DOMAIN (dom)) {
|
||||||
|
virLibDomainError (NULL, VIR_ERR_INVALID_DOMAIN, __FUNCTION__);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
conn = dom->conn;
|
||||||
|
|
||||||
|
/* Flags must be VIR_MEMORY_VIRTUAL at the moment.
|
||||||
|
*
|
||||||
|
* Note on access to physical memory: A VIR_MEMORY_PHYSICAL flag is
|
||||||
|
* a possibility. However it isn't really useful unless the caller
|
||||||
|
* can also access registers, particularly CR3 on x86 in order to
|
||||||
|
* get the Page Table Directory. Since registers are different on
|
||||||
|
* every architecture, that would imply another call to get the
|
||||||
|
* machine registers.
|
||||||
|
*
|
||||||
|
* The QEMU driver handles only VIR_MEMORY_VIRTUAL, mapping it
|
||||||
|
* to the qemu 'memsave' command which does the virtual to physical
|
||||||
|
* mapping inside qemu.
|
||||||
|
*
|
||||||
|
* At time of writing there is no Xen driver. However the Xen
|
||||||
|
* hypervisor only lets you map physical pages from other domains,
|
||||||
|
* and so the Xen driver would have to do the virtual to physical
|
||||||
|
* mapping by chasing 2, 3 or 4-level page tables from the PTD.
|
||||||
|
* There is example code in libxc (xc_translate_foreign_address)
|
||||||
|
* which does this, although we cannot copy this code directly
|
||||||
|
* because of incompatible licensing.
|
||||||
|
*/
|
||||||
|
if (flags != VIR_MEMORY_VIRTUAL) {
|
||||||
|
virLibDomainError (dom, VIR_ERR_INVALID_ARG,
|
||||||
|
_("flags parameter must be VIR_MEMORY_VIRTUAL"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allow size == 0 as an access test. */
|
||||||
|
if (size > 0 && !buffer) {
|
||||||
|
virLibDomainError (dom, VIR_ERR_INVALID_ARG,
|
||||||
|
_("buffer is NULL but size is non-zero"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn->driver->domainMemoryPeek)
|
||||||
|
return conn->driver->domainMemoryPeek (dom, start, size,
|
||||||
|
buffer, flags);
|
||||||
|
|
||||||
|
virLibDomainError (dom, VIR_ERR_NO_SUPPORT, __FUNCTION__);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/************************************************************************
|
/************************************************************************
|
||||||
* *
|
* *
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
virDomainBlockStats;
|
virDomainBlockStats;
|
||||||
virDomainInterfaceStats;
|
virDomainInterfaceStats;
|
||||||
virDomainBlockPeek;
|
virDomainBlockPeek;
|
||||||
|
virDomainMemoryPeek;
|
||||||
virDomainAttachDevice;
|
virDomainAttachDevice;
|
||||||
virDomainDetachDevice;
|
virDomainDetachDevice;
|
||||||
|
|
||||||
|
@ -66,6 +66,9 @@
|
|||||||
#include "capabilities.h"
|
#include "capabilities.h"
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
|
|
||||||
|
/* For storing short-lived temporary files. */
|
||||||
|
#define TEMPDIR LOCAL_STATE_DIR "/cache/libvirt"
|
||||||
|
|
||||||
static int qemudShutdown(void);
|
static int qemudShutdown(void);
|
||||||
|
|
||||||
/* qemudDebug statements should be changed to use this macro instead. */
|
/* qemudDebug statements should be changed to use this macro instead. */
|
||||||
@ -3221,6 +3224,68 @@ found:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
qemudDomainMemoryPeek (virDomainPtr dom,
|
||||||
|
unsigned long long offset, size_t size,
|
||||||
|
void *buffer,
|
||||||
|
unsigned int flags)
|
||||||
|
{
|
||||||
|
struct qemud_driver *driver = (struct qemud_driver *)dom->conn->privateData;
|
||||||
|
struct qemud_vm *vm = qemudFindVMByID (driver, dom->id);
|
||||||
|
char cmd[256], *info;
|
||||||
|
char tmp[] = TEMPDIR "/qemu.mem.XXXXXX";
|
||||||
|
int fd = -1, ret = -1;
|
||||||
|
|
||||||
|
if (flags != VIR_MEMORY_VIRTUAL) {
|
||||||
|
qemudReportError (dom->conn, dom, NULL, VIR_ERR_INVALID_ARG,
|
||||||
|
_("QEMU driver only supports virtual memory addrs"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vm) {
|
||||||
|
qemudReportError (dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN,
|
||||||
|
_("no domain with matching id %d"), dom->id);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!qemudIsActiveVM(vm)) {
|
||||||
|
qemudReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED,
|
||||||
|
"%s", _("domain is not running"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create a temporary filename. */
|
||||||
|
if ((fd = mkstemp (tmp)) == -1) {
|
||||||
|
qemudReportError (dom->conn, dom, NULL, VIR_ERR_SYSTEM_ERROR,
|
||||||
|
"%s", strerror (errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Issue the memsave command. */
|
||||||
|
snprintf (cmd, sizeof cmd, "memsave %llu %zi \"%s\"", offset, size, tmp);
|
||||||
|
if (qemudMonitorCommand (driver, vm, cmd, &info) < 0) {
|
||||||
|
qemudReportError (dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED,
|
||||||
|
"%s", _("'info blockstats' command failed"));
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG ("memsave reply: %s", info);
|
||||||
|
free (info);
|
||||||
|
|
||||||
|
/* Read the memory file into buffer. */
|
||||||
|
if (saferead (fd, buffer, size) == (ssize_t) -1) {
|
||||||
|
qemudReportError (dom->conn, dom, NULL, VIR_ERR_SYSTEM_ERROR,
|
||||||
|
"%s", strerror (errno));
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
done:
|
||||||
|
if (fd >= 0) close (fd);
|
||||||
|
unlink (tmp);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static virNetworkPtr qemudNetworkLookupByUUID(virConnectPtr conn ATTRIBUTE_UNUSED,
|
static virNetworkPtr qemudNetworkLookupByUUID(virConnectPtr conn ATTRIBUTE_UNUSED,
|
||||||
const unsigned char *uuid) {
|
const unsigned char *uuid) {
|
||||||
struct qemud_driver *driver = (struct qemud_driver *)conn->networkPrivateData;
|
struct qemud_driver *driver = (struct qemud_driver *)conn->networkPrivateData;
|
||||||
@ -3580,6 +3645,7 @@ static virDriver qemuDriver = {
|
|||||||
qemudDomainBlockStats, /* domainBlockStats */
|
qemudDomainBlockStats, /* domainBlockStats */
|
||||||
qemudDomainInterfaceStats, /* domainInterfaceStats */
|
qemudDomainInterfaceStats, /* domainInterfaceStats */
|
||||||
qemudDomainBlockPeek, /* domainBlockPeek */
|
qemudDomainBlockPeek, /* domainBlockPeek */
|
||||||
|
qemudDomainMemoryPeek, /* domainMemoryPeek */
|
||||||
#if HAVE_NUMACTL
|
#if HAVE_NUMACTL
|
||||||
qemudNodeGetCellsFreeMemory, /* nodeGetCellsFreeMemory */
|
qemudNodeGetCellsFreeMemory, /* nodeGetCellsFreeMemory */
|
||||||
qemudNodeGetFreeMemory, /* getFreeMemory */
|
qemudNodeGetFreeMemory, /* getFreeMemory */
|
||||||
|
@ -2416,6 +2416,50 @@ remoteDomainBlockPeek (virDomainPtr domain,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
remoteDomainMemoryPeek (virDomainPtr domain,
|
||||||
|
unsigned long long offset,
|
||||||
|
size_t size,
|
||||||
|
void *buffer,
|
||||||
|
unsigned int flags)
|
||||||
|
{
|
||||||
|
remote_domain_memory_peek_args args;
|
||||||
|
remote_domain_memory_peek_ret ret;
|
||||||
|
GET_PRIVATE (domain->conn, -1);
|
||||||
|
|
||||||
|
if (size > REMOTE_DOMAIN_MEMORY_PEEK_BUFFER_MAX) {
|
||||||
|
errorf (domain->conn, VIR_ERR_RPC,
|
||||||
|
_("memory peek request too large for remote protocol, %zi > %d"),
|
||||||
|
size, REMOTE_DOMAIN_MEMORY_PEEK_BUFFER_MAX);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
make_nonnull_domain (&args.dom, domain);
|
||||||
|
args.offset = offset;
|
||||||
|
args.size = size;
|
||||||
|
args.flags = flags;
|
||||||
|
|
||||||
|
memset (&ret, 0, sizeof ret);
|
||||||
|
if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_MEMORY_PEEK,
|
||||||
|
(xdrproc_t) xdr_remote_domain_memory_peek_args,
|
||||||
|
(char *) &args,
|
||||||
|
(xdrproc_t) xdr_remote_domain_memory_peek_ret,
|
||||||
|
(char *) &ret) == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (ret.buffer.buffer_len != size) {
|
||||||
|
errorf (domain->conn, VIR_ERR_RPC,
|
||||||
|
_("returned buffer is not same size as requested"));
|
||||||
|
free (ret.buffer.buffer_val);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy (buffer, ret.buffer.buffer_val, size);
|
||||||
|
free (ret.buffer.buffer_val);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*----------------------------------------------------------------------*/
|
/*----------------------------------------------------------------------*/
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -4824,6 +4868,7 @@ static virDriver driver = {
|
|||||||
.domainBlockStats = remoteDomainBlockStats,
|
.domainBlockStats = remoteDomainBlockStats,
|
||||||
.domainInterfaceStats = remoteDomainInterfaceStats,
|
.domainInterfaceStats = remoteDomainInterfaceStats,
|
||||||
.domainBlockPeek = remoteDomainBlockPeek,
|
.domainBlockPeek = remoteDomainBlockPeek,
|
||||||
|
.domainMemoryPeek = remoteDomainMemoryPeek,
|
||||||
.nodeGetCellsFreeMemory = remoteNodeGetCellsFreeMemory,
|
.nodeGetCellsFreeMemory = remoteNodeGetCellsFreeMemory,
|
||||||
.getFreeMemory = remoteNodeGetFreeMemory,
|
.getFreeMemory = remoteNodeGetFreeMemory,
|
||||||
};
|
};
|
||||||
|
@ -2061,6 +2061,7 @@ static virDriver testDriver = {
|
|||||||
NULL, /* domainBlockStats */
|
NULL, /* domainBlockStats */
|
||||||
NULL, /* domainInterfaceStats */
|
NULL, /* domainInterfaceStats */
|
||||||
NULL, /* domainBlockPeek */
|
NULL, /* domainBlockPeek */
|
||||||
|
NULL, /* domainMemoryPeek */
|
||||||
testNodeGetCellsFreeMemory, /* nodeGetCellsFreeMemory */
|
testNodeGetCellsFreeMemory, /* nodeGetCellsFreeMemory */
|
||||||
NULL, /* getFreeMemory */
|
NULL, /* getFreeMemory */
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user