qga: implement qmp_guest_get_memory_blocks() for Linux with sysfs
We can get guest's memory block information by using command "guest-get-memory-blocks", the returned value contains a list of memory block info, such as phys-index, online state, can-offline info. Signed-off-by: zhanghailiang <zhang.zhanghailiang@huawei.com> *replaced guest-triggerable assertion with an error msg Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
This commit is contained in:
parent
a065aaa920
commit
bd240fca42
@ -1992,9 +1992,240 @@ out:
|
||||
}
|
||||
}
|
||||
|
||||
static void ga_read_sysfs_file(int dirfd, const char *pathname, char *buf,
|
||||
int size, Error **errp)
|
||||
{
|
||||
int fd;
|
||||
int res;
|
||||
|
||||
errno = 0;
|
||||
fd = openat(dirfd, pathname, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname);
|
||||
return;
|
||||
}
|
||||
|
||||
res = pread(fd, buf, size, 0);
|
||||
if (res == -1) {
|
||||
error_setg_errno(errp, errno, "pread sysfs file \"%s\"", pathname);
|
||||
} else if (res == 0) {
|
||||
error_setg(errp, "pread sysfs file \"%s\": unexpected EOF", pathname);
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void ga_write_sysfs_file(int dirfd, const char *pathname,
|
||||
const char *buf, int size, Error **errp)
|
||||
{
|
||||
int fd;
|
||||
|
||||
errno = 0;
|
||||
fd = openat(dirfd, pathname, O_WRONLY);
|
||||
if (fd == -1) {
|
||||
error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pwrite(fd, buf, size, 0) == -1) {
|
||||
error_setg_errno(errp, errno, "pwrite sysfs file \"%s\"", pathname);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* Transfer online/offline status between @mem_blk and the guest system.
|
||||
*
|
||||
* On input either @errp or *@errp must be NULL.
|
||||
*
|
||||
* In system-to-@mem_blk direction, the following @mem_blk fields are accessed:
|
||||
* - R: mem_blk->phys_index
|
||||
* - W: mem_blk->online
|
||||
* - W: mem_blk->can_offline
|
||||
*
|
||||
* In @mem_blk-to-system direction, the following @mem_blk fields are accessed:
|
||||
* - R: mem_blk->phys_index
|
||||
* - R: mem_blk->online
|
||||
*- R: mem_blk->can_offline
|
||||
* Written members remain unmodified on error.
|
||||
*/
|
||||
static void transfer_memory_block(GuestMemoryBlock *mem_blk, bool sys2memblk,
|
||||
GuestMemoryBlockResponse *result,
|
||||
Error **errp)
|
||||
{
|
||||
char *dirpath;
|
||||
int dirfd;
|
||||
char *status;
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (!sys2memblk) {
|
||||
DIR *dp;
|
||||
|
||||
if (!result) {
|
||||
error_setg(errp, "Internal error, 'result' should not be NULL");
|
||||
return;
|
||||
}
|
||||
errno = 0;
|
||||
dp = opendir("/sys/devices/system/memory/");
|
||||
/* if there is no 'memory' directory in sysfs,
|
||||
* we think this VM does not support online/offline memory block,
|
||||
* any other solution?
|
||||
*/
|
||||
if (!dp && errno == ENOENT) {
|
||||
result->response =
|
||||
GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
|
||||
goto out1;
|
||||
}
|
||||
closedir(dp);
|
||||
}
|
||||
|
||||
dirpath = g_strdup_printf("/sys/devices/system/memory/memory%" PRId64 "/",
|
||||
mem_blk->phys_index);
|
||||
dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
|
||||
if (dirfd == -1) {
|
||||
if (sys2memblk) {
|
||||
error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
|
||||
} else {
|
||||
if (errno == ENOENT) {
|
||||
result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_NOT_FOUND;
|
||||
} else {
|
||||
result->response =
|
||||
GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
|
||||
}
|
||||
}
|
||||
g_free(dirpath);
|
||||
goto out1;
|
||||
}
|
||||
g_free(dirpath);
|
||||
|
||||
status = g_malloc0(10);
|
||||
ga_read_sysfs_file(dirfd, "state", status, 10, &local_err);
|
||||
if (local_err) {
|
||||
/* treat with sysfs file that not exist in old kernel */
|
||||
if (errno == ENOENT) {
|
||||
error_free(local_err);
|
||||
if (sys2memblk) {
|
||||
mem_blk->online = true;
|
||||
mem_blk->can_offline = false;
|
||||
} else if (!mem_blk->online) {
|
||||
result->response =
|
||||
GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
|
||||
}
|
||||
} else {
|
||||
if (sys2memblk) {
|
||||
error_propagate(errp, local_err);
|
||||
} else {
|
||||
result->response =
|
||||
GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
|
||||
}
|
||||
}
|
||||
goto out2;
|
||||
}
|
||||
|
||||
if (sys2memblk) {
|
||||
char removable = '0';
|
||||
|
||||
mem_blk->online = (strncmp(status, "online", 6) == 0);
|
||||
|
||||
ga_read_sysfs_file(dirfd, "removable", &removable, 1, &local_err);
|
||||
if (local_err) {
|
||||
/* if no 'removable' file, it does't support offline mem blk */
|
||||
if (errno == ENOENT) {
|
||||
error_free(local_err);
|
||||
mem_blk->can_offline = false;
|
||||
} else {
|
||||
error_propagate(errp, local_err);
|
||||
}
|
||||
} else {
|
||||
mem_blk->can_offline = (removable != '0');
|
||||
}
|
||||
} else {
|
||||
if (mem_blk->online != (strncmp(status, "online", 6) == 0)) {
|
||||
char *new_state = mem_blk->online ? g_strdup("online") :
|
||||
g_strdup("offline");
|
||||
|
||||
ga_write_sysfs_file(dirfd, "state", new_state, strlen(new_state),
|
||||
&local_err);
|
||||
g_free(new_state);
|
||||
if (local_err) {
|
||||
error_free(local_err);
|
||||
result->response =
|
||||
GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
|
||||
goto out2;
|
||||
}
|
||||
|
||||
result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_SUCCESS;
|
||||
result->has_error_code = false;
|
||||
} /* otherwise pretend successful re-(on|off)-lining */
|
||||
}
|
||||
g_free(status);
|
||||
close(dirfd);
|
||||
return;
|
||||
|
||||
out2:
|
||||
g_free(status);
|
||||
close(dirfd);
|
||||
out1:
|
||||
if (!sys2memblk) {
|
||||
result->has_error_code = true;
|
||||
result->error_code = errno;
|
||||
}
|
||||
}
|
||||
|
||||
GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
|
||||
{
|
||||
error_set(errp, QERR_UNSUPPORTED);
|
||||
GuestMemoryBlockList *head, **link;
|
||||
Error *local_err = NULL;
|
||||
struct dirent *de;
|
||||
DIR *dp;
|
||||
|
||||
head = NULL;
|
||||
link = &head;
|
||||
|
||||
dp = opendir("/sys/devices/system/memory/");
|
||||
if (!dp) {
|
||||
error_setg_errno(errp, errno, "Can't open directory"
|
||||
"\"/sys/devices/system/memory/\"\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Note: the phys_index of memory block may be discontinuous,
|
||||
* this is because a memblk is the unit of the Sparse Memory design, which
|
||||
* allows discontinuous memory ranges (ex. NUMA), so here we should
|
||||
* traverse the memory block directory.
|
||||
*/
|
||||
while ((de = readdir(dp)) != NULL) {
|
||||
GuestMemoryBlock *mem_blk;
|
||||
GuestMemoryBlockList *entry;
|
||||
|
||||
if ((strncmp(de->d_name, "memory", 6) != 0) ||
|
||||
!(de->d_type & DT_DIR)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mem_blk = g_malloc0(sizeof *mem_blk);
|
||||
/* The d_name is "memoryXXX", phys_index is block id, same as XXX */
|
||||
mem_blk->phys_index = strtoul(&de->d_name[6], NULL, 10);
|
||||
mem_blk->has_can_offline = true; /* lolspeak ftw */
|
||||
transfer_memory_block(mem_blk, true, NULL, &local_err);
|
||||
|
||||
entry = g_malloc0(sizeof *entry);
|
||||
entry->value = mem_blk;
|
||||
|
||||
*link = entry;
|
||||
link = &entry->next;
|
||||
}
|
||||
|
||||
closedir(dp);
|
||||
if (local_err == NULL) {
|
||||
/* there's no guest with zero memory blocks */
|
||||
if (head == NULL) {
|
||||
error_setg(errp, "guest reported zero memory blocks!");
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
qapi_free_GuestMemoryBlockList(head);
|
||||
error_propagate(errp, local_err);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user