From patchwork Thu Oct 20 06:13:01 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Haozhong Zhang X-Patchwork-Id: 684465 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3szz7103Whz9s5g for ; Thu, 20 Oct 2016 17:14:51 +1100 (AEDT) Received: from localhost ([::1]:52630 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bx6cn-0007bU-Q7 for incoming@patchwork.ozlabs.org; Thu, 20 Oct 2016 02:14:45 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:49731) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bx6bw-00078L-HG for qemu-devel@nongnu.org; Thu, 20 Oct 2016 02:13:53 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1bx6bt-0006Ww-Ay for qemu-devel@nongnu.org; Thu, 20 Oct 2016 02:13:52 -0400 Received: from mga05.intel.com ([192.55.52.43]:20447) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1bx6bs-0006Wh-VZ for qemu-devel@nongnu.org; Thu, 20 Oct 2016 02:13:49 -0400 Received: from fmsmga002.fm.intel.com ([10.253.24.26]) by fmsmga105.fm.intel.com with ESMTP; 19 Oct 2016 23:13:48 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos; i="5.31,517,1473145200"; d="scan'208"; a="1073016329" Received: from hz-desktop.sh.intel.com (HELO localhost) ([10.239.159.148]) by fmsmga002.fm.intel.com with ESMTP; 19 Oct 2016 23:13:47 -0700 From: Haozhong Zhang To: qemu-devel@nongnu.org Date: Thu, 20 Oct 2016 14:13:01 +0800 Message-Id: <20161020061301.31372-1-haozhong.zhang@intel.com> X-Mailer: git-send-email 2.10.1 X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 192.55.52.43 Subject: [Qemu-devel] [PATCH] hostmem-file: add a property 'notrunc' to avoid data corruption X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Haozhong Zhang , Xiao Guangrong , Eduardo Habkost , Peter Crosthwaite , Paolo Bonzini , Igor Mammedov , Richard Henderson Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" If a file is used as the backend of memory-backend-file and its size is not identical to the property 'size', the file will be truncated. For a file used as the backend of vNVDIMM, its data is expected to be persistent and the truncation may corrupt the existing data. A property 'notrunc' is added to memory-backend-file to allow users to control the truncation. If 'notrunc' is on, QEMU will not truncate the backend file and require the property 'size' is not larger than the file size. If 'notrunc' is off, QEMU will behave as before. Signed-off-by: Haozhong Zhang --- backends/hostmem-file.c | 25 ++++++++++++++++- exec.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++-- include/exec/memory.h | 3 ++ include/exec/ram_addr.h | 3 +- memory.c | 4 ++- numa.c | 2 +- 6 files changed, 105 insertions(+), 6 deletions(-) diff --git a/backends/hostmem-file.c b/backends/hostmem-file.c index 42efb2f..c2df14c 100644 --- a/backends/hostmem-file.c +++ b/backends/hostmem-file.c @@ -32,6 +32,7 @@ struct HostMemoryBackendFile { HostMemoryBackend parent_obj; bool share; + bool notrunc; char *mem_path; }; @@ -57,7 +58,7 @@ file_backend_memory_alloc(HostMemoryBackend *backend, Error **errp) path = object_get_canonical_path(OBJECT(backend)); memory_region_init_ram_from_file(&backend->mr, OBJECT(backend), path, - backend->size, fb->share, + backend->size, fb->share, fb->notrunc, fb->mem_path, errp); g_free(path); } @@ -103,6 +104,25 @@ static void file_memory_backend_set_share(Object *o, bool value, Error **errp) fb->share = value; } +static bool file_memory_backend_get_notrunc(Object *o, Error **errp) +{ + HostMemoryBackendFile *fb = MEMORY_BACKEND_FILE(o); + + return fb->notrunc; +} + +static void file_memory_backend_set_notrunc(Object *o, bool value, Error **errp) +{ + HostMemoryBackend *backend = MEMORY_BACKEND(o); + HostMemoryBackendFile *fb = MEMORY_BACKEND_FILE(o); + + if (memory_region_size(&backend->mr)) { + error_setg(errp, "cannot change property value"); + return; + } + fb->notrunc = value; +} + static void file_backend_class_init(ObjectClass *oc, void *data) { @@ -116,6 +136,9 @@ file_backend_class_init(ObjectClass *oc, void *data) object_class_property_add_str(oc, "mem-path", get_mem_path, set_mem_path, &error_abort); + object_class_property_add_bool(oc, "notrunc", + file_memory_backend_get_notrunc, file_memory_backend_set_notrunc, + &error_abort); } static void file_backend_instance_finalize(Object *o) diff --git a/exec.c b/exec.c index e63c5a1..aacaee1 100644 --- a/exec.c +++ b/exec.c @@ -63,6 +63,11 @@ #include "qemu/mmap-alloc.h" #endif +#include +#ifdef CONFIG_LINUX +#include +#endif + //#define DEBUG_SUBPAGE #if !defined(CONFIG_USER_ONLY) @@ -91,6 +96,12 @@ static MemoryRegion io_mem_unassigned; */ #define RAM_RESIZEABLE (1 << 2) +/* If the backend of RAM is a file and the RAM size is not identical + * to the file size, do not truncate the file to the RAM size. This + * flag is used to avoid corrupting the existing data on the file. + */ +#define RAM_NOTRUNC (1 << 3) + #endif struct CPUTailQ cpus = QTAILQ_HEAD_INITIALIZER(cpus); @@ -1188,6 +1199,48 @@ void qemu_mutex_unlock_ramlist(void) } #ifdef __linux__ +static uint64_t get_file_size(const char *path, Error **errp) +{ + int fd; + struct stat st; + uint64_t size = 0; + Error *local_err = NULL; + + fd = qemu_open(path, O_RDONLY); + if (fd < 0) { + error_setg(&local_err, "cannot open file"); + goto out; + } + + if (stat(path, &st)) { + error_setg(&local_err, "cannot get file stat"); + goto out_fclose; + } + + switch (st.st_mode & S_IFMT) { + case S_IFREG: + size = st.st_size; + break; + + case S_IFBLK: + if (ioctl(fd, BLKGETSIZE64, &size)) { + error_setg(&local_err, "cannot get size of block device"); + size = 0; + } + break; + + default: + error_setg(&local_err, + "only block device on Linux and regular file are supported"); + } + + out_fclose: + close(fd); + out: + error_propagate(errp, local_err); + return size; +} + static void *file_ram_alloc(RAMBlock *block, ram_addr_t memory, const char *path, @@ -1271,7 +1324,7 @@ static void *file_ram_alloc(RAMBlock *block, * If anything goes wrong with it under other filesystems, * mmap will fail. */ - if (ftruncate(fd, memory)) { + if (!(block->flags & RAM_NOTRUNC) && ftruncate(fd, memory)) { perror("ftruncate"); } @@ -1597,7 +1650,8 @@ static void ram_block_add(RAMBlock *new_block, Error **errp) #ifdef __linux__ RAMBlock *qemu_ram_alloc_from_file(ram_addr_t size, MemoryRegion *mr, - bool share, const char *mem_path, + bool share, bool notrunc, + const char *mem_path, Error **errp) { RAMBlock *new_block; @@ -1619,12 +1673,28 @@ RAMBlock *qemu_ram_alloc_from_file(ram_addr_t size, MemoryRegion *mr, return NULL; } + if (notrunc) { + uint64_t file_size = get_file_size(mem_path, &local_err); + + if (local_err) { + error_propagate(errp, local_err); + return NULL; + } + + if (size > file_size) { + error_setg(errp, + "notrunc enabled, but size is larger than file size"); + return NULL; + } + } + size = HOST_PAGE_ALIGN(size); new_block = g_malloc0(sizeof(*new_block)); new_block->mr = mr; new_block->used_length = size; new_block->max_length = size; new_block->flags = share ? RAM_SHARED : 0; + new_block->flags |= notrunc ? RAM_NOTRUNC : 0; new_block->host = file_ram_alloc(new_block, size, mem_path, errp); if (!new_block->host) { diff --git a/include/exec/memory.h b/include/exec/memory.h index 10d7eac..c0116e1 100644 --- a/include/exec/memory.h +++ b/include/exec/memory.h @@ -418,6 +418,8 @@ void memory_region_init_resizeable_ram(MemoryRegion *mr, * @name: the name of the region. * @size: size of the region. * @share: %true if memory must be mmaped with the MAP_SHARED flag + * @notrunc: %true do not truncate the file @path to @size if the + * file size is not identical to @size * @path: the path in which to allocate the RAM. * @errp: pointer to Error*, to store an error if it happens. */ @@ -426,6 +428,7 @@ void memory_region_init_ram_from_file(MemoryRegion *mr, const char *name, uint64_t size, bool share, + bool notrunc, const char *path, Error **errp); #endif diff --git a/include/exec/ram_addr.h b/include/exec/ram_addr.h index 54d7108..4fd3ec8 100644 --- a/include/exec/ram_addr.h +++ b/include/exec/ram_addr.h @@ -96,7 +96,8 @@ void qemu_mutex_lock_ramlist(void); void qemu_mutex_unlock_ramlist(void); RAMBlock *qemu_ram_alloc_from_file(ram_addr_t size, MemoryRegion *mr, - bool share, const char *mem_path, + bool share, bool notrunc, + const char *mem_path, Error **errp); RAMBlock *qemu_ram_alloc_from_ptr(ram_addr_t size, void *host, MemoryRegion *mr, Error **errp); diff --git a/memory.c b/memory.c index 58f9269..5430412 100644 --- a/memory.c +++ b/memory.c @@ -1334,6 +1334,7 @@ void memory_region_init_ram_from_file(MemoryRegion *mr, const char *name, uint64_t size, bool share, + bool trunc, const char *path, Error **errp) { @@ -1341,7 +1342,8 @@ void memory_region_init_ram_from_file(MemoryRegion *mr, mr->ram = true; mr->terminates = true; mr->destructor = memory_region_destructor_ram; - mr->ram_block = qemu_ram_alloc_from_file(size, mr, share, path, errp); + mr->ram_block = qemu_ram_alloc_from_file(size, mr, share, trunc, path, + errp); mr->dirty_log_mask = tcg_enabled() ? (1 << DIRTY_MEMORY_CODE) : 0; } #endif diff --git a/numa.c b/numa.c index 9c09e45..6cf1fb5 100644 --- a/numa.c +++ b/numa.c @@ -412,7 +412,7 @@ static void allocate_system_memory_nonnuma(MemoryRegion *mr, Object *owner, #ifdef __linux__ Error *err = NULL; memory_region_init_ram_from_file(mr, owner, name, ram_size, false, - mem_path, &err); + false, mem_path, &err); if (err) { error_report_err(err); if (mem_prealloc) {