From 5a2ec7e2456b634e7f9141d09f0caffde6543eec Mon Sep 17 00:00:00 2001
From: XUranus <2257238649wdx@gmail.com>
Date: Sat, 16 Mar 2024 17:42:17 +0800
Subject: [PATCH] add zero copy optimzation for linux image format copy
restoration
---
CMakeLists.txt | 4 +-
README.md | 8 +-
cli/vbackup.cpp | 46 ++++--
include/VolumeProtector.h | 1 +
include/common/VolumeUtils.h | 20 ++-
include/native/RawIO.h | 9 ++
include/native/linux/PosixRawIO.h | 2 +
include/task/VolumeZeroCopyRestoreTask.h | 61 +++++++
src/VolumeProtector.cpp | 16 +-
src/native/linux/LinuxMountUtils.cpp | 113 +++++++------
src/native/linux/PosixRawIO.cpp | 11 ++
src/task/VolumeZeroCopyRestoreTask.cpp | 198 +++++++++++++++++++++++
12 files changed, 407 insertions(+), 82 deletions(-)
create mode 100644 include/task/VolumeZeroCopyRestoreTask.h
create mode 100644 src/task/VolumeZeroCopyRestoreTask.cpp
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2e05ec4..6fa31ba 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -50,7 +50,7 @@ if(${CMAKE_HOST_WIN32})
VOLUMEPROTECTPROTECT_SOURCES
"src/*.cpp"
"src/common/*.cpp"
- "src/task*.cpp"
+ "src/task/*.cpp"
"src/native/*.cpp"
"src/native/win32/*.cpp"
)
@@ -60,7 +60,7 @@ else()
VOLUMEPROTECTPROTECT_SOURCES
"src/*.cpp"
"src/common/*.cpp"
- "src/task*.cpp"
+ "src/task/*.cpp"
"src/native/*.cpp"
"src/native/linux/*.cpp"
)
diff --git a/README.md b/README.md
index 75369eb..bd5daec 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,13 @@ Volume backup/restore library and cli tools for Windows and Linux
- [X] **FULL BACKUP** and **FOREVER INCREMENT BACKUP** support
- [X] `*.img`,`*.vhd`,`*.vhdx` copy format support
- - [X] volume copy mount support
- - [X] checkpoint support
+ - [X] Volume copy mount support
+ - [X] Checkpoint support
+ - [ ] Zero copy optimization
- [ ] Qt GUI
+ - [ ] Auto snapshot creation of LVM,BTRFS for Linux and VSS for Windows
+ - [ ] Auto filesystem type detection
+
diff --git a/cli/vbackup.cpp b/cli/vbackup.cpp
index 58fb9d9..229b8d7 100644
--- a/cli/vbackup.cpp
+++ b/cli/vbackup.cpp
@@ -37,9 +37,10 @@ static const char* g_helpMessage =
#endif
"-d | --data= \t specify copy data directory\n"
"-m | --meta= \t specify copy meta directory\n"
- "-k | --checkpoint= \t specify checkpoint directory"
+ "-k | --checkpoint= \t specify checkpoint directory\n"
"-p | --prevmeta= \t specify previous copy meta directory\n"
"-r | --restore \t used when performing restore operation\n"
+ "-z | --zerocopy \t enable zero copy during restore\n"
"-l | --loglevel= \t specify logger level [INFO, DEBUG]\n"
"-h | --help \t print help\n";
@@ -51,8 +52,9 @@ struct CliArgs {
std::string copyMetaDirPath;
std::string checkpointDirPath;
std::string prevCopyMetaDirPath;
- LoggerLevel logLevel { LoggerLevel::DEBUG };
+ LoggerLevel logLevel { LoggerLevel::INFO };
bool isRestore { false };
+ bool enableZeroCopy { false };
bool printHelp { false };
};
@@ -109,9 +111,9 @@ static CliArgs ParseCliArgs(int argc, const char** argv)
CliArgs cliAgrs;
GetOptionResult result = GetOption(
argv + 1, argc - 1,
- "v:n:f:d:m:k:p:h:r:l:",
+ "v:n:f:d:m:k:p:hzr:l:",
{"--volume=", "--name=", "--format=", "--data=", "--meta=", "--checkpoint=",
- "--prevmeta=", "--help", "--restore", "--loglevel="});
+ "--prevmeta=", "--help", "--zerocopy", "--restore", "--loglevel="});
for (const OptionResult opt: result.opts) {
if (opt.option == "v" || opt.option == "volume") {
cliAgrs.volumePath = opt.value;
@@ -129,6 +131,8 @@ static CliArgs ParseCliArgs(int argc, const char** argv)
cliAgrs.prevCopyMetaDirPath = opt.value;
} else if (opt.option == "r" || opt.option == "restore") {
cliAgrs.isRestore = true;
+ } else if (opt.option == "z" || opt.option == "zerocopy") {
+ cliAgrs.enableZeroCopy = true;
} else if (opt.option == "l" || opt.option == "loglevel") {
cliAgrs.logLevel = ParseLoggerLevel(opt.value);
} else if (opt.option == "h" || opt.option == "help") {
@@ -176,11 +180,23 @@ void PrintTaskErrorCodeMessage(ErrCodeType errorCode)
static bool ValidateCliArgs(const CliArgs& cliArgs)
{
- return !cliArgs.volumePath.empty()
- && !cliArgs.copyDataDirPath.empty()
- && !cliArgs.copyMetaDirPath.empty()
- && !cliArgs.checkpointDirPath.empty()
- && !cliArgs.copyName.empty();
+ if (cliArgs.volumePath.empty()) {
+ std::cerr << "Error: no volume path specified." << std::endl;
+ return false;
+ }
+ if (cliArgs.copyDataDirPath.empty()) {
+ std::cerr << "Error: no copy data path specified." << std::endl;
+ return false;
+ }
+ if (cliArgs.copyMetaDirPath.empty()) {
+ std::cerr << "Error: no copy meta path specified." << std::endl;
+ return false;
+ }
+ if (cliArgs.copyName.empty()) {
+ std::cerr << "Error: no volume copy name specified." << std::endl;
+ return false;
+ }
+ return true;
}
void InitLogger(const CliArgs& cliArgs)
@@ -192,9 +208,9 @@ void InitLogger(const CliArgs& cliArgs)
conf.archiveFilesNumMax = 10;
conf.fileName = "vbackup.log";
#ifdef _WIN32
- conf.logDirPath = R"(C:\LoggerTest)";
+ conf.logDirPath = R"(C:\)";
#else
- conf.logDirPath = "/tmp/LoggerTest";
+ conf.logDirPath = "/tmp";
#endif
if (!Logger::GetInstance()->Init(conf)) {
std::cerr << "Init logger failed" << std::endl;
@@ -213,12 +229,12 @@ static int ExecVolumeBackup(const CliArgs& cliArgs)
backupConfig.outputCopyDataDirPath = cliArgs.copyDataDirPath;
backupConfig.outputCopyMetaDirPath = cliArgs.copyMetaDirPath;
backupConfig.checkpointDirPath = cliArgs.checkpointDirPath;
+ backupConfig.enableCheckpoint = !cliArgs.checkpointDirPath.empty();
backupConfig.clearCheckpointsOnSucceed = true;
backupConfig.blockSize = DEFAULT_BLOCK_SIZE;
backupConfig.sessionSize = 3 * ONE_GB;
backupConfig.hasherNum = hasherWorkerNum;
backupConfig.hasherEnabled = true;
- backupConfig.enableCheckpoint = true;
if (backupConfig.prevCopyMetaDirPath.empty()) {
std::cout << "----- Perform Full Backup -----" << std::endl;
@@ -258,6 +274,12 @@ static int ExecVolumeRestore(const CliArgs& cliAgrs)
restoreConfig.copyDataDirPath = cliAgrs.copyDataDirPath;
restoreConfig.copyMetaDirPath = cliAgrs.copyMetaDirPath;
restoreConfig.checkpointDirPath = cliAgrs.checkpointDirPath;
+ restoreConfig.enableCheckpoint = !cliAgrs.checkpointDirPath.empty();
+ restoreConfig.enableZeroCopy = cliAgrs.enableZeroCopy;
+
+ if (restoreConfig.enableZeroCopy) {
+ std::cout << "using zero copy optimization." << std::endl;
+ }
std::shared_ptr
task = VolumeProtectTask::BuildRestoreTask(restoreConfig);
if (task == nullptr) {
diff --git a/include/VolumeProtector.h b/include/VolumeProtector.h
index 78bab3f..a5269b4 100644
--- a/include/VolumeProtector.h
+++ b/include/VolumeProtector.h
@@ -109,6 +109,7 @@ struct VOLUMEPROTECT_API VolumeRestoreConfig {
bool enableCheckpoint { true }; ///< start from checkpoint if exists
std::string checkpointDirPath; ///< directory path where checkpoint stores at
bool clearCheckpointsOnSucceed { true }; ///< if clear checkpoint files on succeed
+ bool enableZeroCopy { false }; ///< use zero copy optimization for CopyFormat::IMAGE restore
};
/**
diff --git a/include/common/VolumeUtils.h b/include/common/VolumeUtils.h
index bf63d3e..4444979 100644
--- a/include/common/VolumeUtils.h
+++ b/include/common/VolumeUtils.h
@@ -37,14 +37,24 @@ struct CopySegment {
};
struct VolumeCopyMeta {
+ /* basic meta */
std::string copyName;
- int backupType; // cast BackupType to int
- int copyFormat; // cast CopyFormat to int
- uint64_t volumeSize; // volume size in bytes
- uint32_t blockSize; // block size in bytes
- std::string volumePath;
+ int backupType; ///< cast BackupType to int
+ int copyFormat; ///< cast CopyFormat to int
+ uint64_t volumeSize; ///< volume size in bytes
+ uint32_t blockSize; ///< block size in bytes
std::vector segments;
+ std::string volumePath;
+ std::string label;
+ std::string uuid;
+
+ /* meta of the snapshot of the volume */
+ // TODO:: intergate fs uuid detection and snapshot auto creation in later version
+ std::string snapshotPath;
+ std::string snapshotLabel;
+ std::string snapshotUUID;
+
SERIALIZE_SECTION_BEGIN
SERIALIZE_FIELD(copyName, copyName);
SERIALIZE_FIELD(backupType, backupType);
diff --git a/include/native/RawIO.h b/include/native/RawIO.h
index ef06c3f..64047c0 100644
--- a/include/native/RawIO.h
+++ b/include/native/RawIO.h
@@ -13,6 +13,11 @@
#include "VolumeProtector.h"
#include
+#ifdef _WIN32
+using HandleType = void*;
+#else
+using HandleType = int;
+#endif
namespace volumeprotect {
/**
@@ -32,6 +37,8 @@ class RawDataReader {
virtual ErrCodeType Error() = 0;
+ virtual HandleType Handle() = 0;
+
virtual ~RawDataReader() = default;
};
@@ -49,6 +56,8 @@ class RawDataWriter {
virtual ErrCodeType Error() = 0;
+ virtual HandleType Handle() = 0;
+
virtual ~RawDataWriter() = default;
};
diff --git a/include/native/linux/PosixRawIO.h b/include/native/linux/PosixRawIO.h
index aaed8b3..f82ff72 100644
--- a/include/native/linux/PosixRawIO.h
+++ b/include/native/linux/PosixRawIO.h
@@ -27,6 +27,7 @@ class PosixRawDataReader : public RawDataReader {
~PosixRawDataReader();
bool Read(uint64_t offset, uint8_t* buffer, int length, ErrCodeType& errorCode) override;
bool Ok() override;
+ HandleType Handle() override;
ErrCodeType Error() override;
private:
@@ -42,6 +43,7 @@ class PosixRawDataWriter : public RawDataWriter {
~PosixRawDataWriter();
bool Write(uint64_t offset, uint8_t* buffer, int length, ErrCodeType& errorCode) override;
bool Ok() override;
+ HandleType Handle() override;
bool Flush() override;
ErrCodeType Error() override;
diff --git a/include/task/VolumeZeroCopyRestoreTask.h b/include/task/VolumeZeroCopyRestoreTask.h
new file mode 100644
index 0000000..01f80f1
--- /dev/null
+++ b/include/task/VolumeZeroCopyRestoreTask.h
@@ -0,0 +1,61 @@
+/**
+ * @file VolumeZeroCopyRestoreTask.h
+ * @brief Provide zero copy implement for CopyFormat::IMAGE copy restoration.
+ * @copyright Copyright 2023 XUranus. All rights reserved.
+ * @license This project is released under the Apache License.
+ * @author XUranus(2257238649wdx@gmail.com)
+ */
+
+#ifndef VOLUMEBACKUP_ZERO_COPY_RESTORE_TASK_HEADER
+#define VOLUMEBACKUP_ZERO_COPY_RESTORE_TASK_HEADER
+
+#include "VolumeProtector.h"
+#include "VolumeProtectTaskContext.h"
+#include "native/TaskResourceManager.h"
+#include "VolumeUtils.h"
+#include "native/RawIO.h"
+#include
+
+namespace volumeprotect {
+namespace task {
+
+/**
+ * @brief Control control volume restore procedure
+ */
+class VolumeZeroCopyRestoreTask : public VolumeProtectTask, public TaskStatisticTrait {
+public:
+ using SessionQueue = std::queue;
+
+ bool Start() override;
+
+ TaskStatistics GetStatistics() const override;
+
+ VolumeZeroCopyRestoreTask(const VolumeRestoreConfig& restoreConfig, const VolumeCopyMeta& volumeCopyMeta);
+
+ ~VolumeZeroCopyRestoreTask();
+
+private:
+ bool Prepare(); // split session and save meta
+
+ void ThreadFunc();
+
+ bool PerformZeroCopyRestore(
+ std::shared_ptr copyDataReader,
+ std::shared_ptr volumeDataWriter,
+ const VolumeTaskSharedConfig& sessionConfig);
+
+protected:
+ uint64_t m_volumeSize;
+ std::shared_ptr m_restoreConfig;
+ std::shared_ptr m_volumeCopyMeta;
+ std::queue m_sessionQueue;
+ std::thread m_thread;
+
+ std::shared_ptr m_resourceManager;
+ std::vector m_checkpointFiles;
+};
+
+}
+}
+
+#endif
\ No newline at end of file
diff --git a/src/VolumeProtector.cpp b/src/VolumeProtector.cpp
index fdd6d08..996564e 100644
--- a/src/VolumeProtector.cpp
+++ b/src/VolumeProtector.cpp
@@ -5,11 +5,13 @@
*/
#include "VolumeProtector.h"
-#include "VolumeBackupTask.h"
#include "common/VolumeProtectMacros.h"
+#include "VolumeBackupTask.h"
+#include "VolumeZeroCopyRestoreTask.h"
#include "VolumeRestoreTask.h"
#include "VolumeUtils.h"
#include "native/FileSystemAPI.h"
+#include
using namespace volumeprotect;
using namespace volumeprotect::common;
@@ -101,7 +103,7 @@ std::unique_ptr VolumeProtectTask::BuildBackupTask(const Volu
return nullptr;
}
- return std::unique_ptr(new VolumeBackupTask(finalBackupConfig, volumeSize));
+ return exstd::make_unique(finalBackupConfig, volumeSize);
}
std::unique_ptr VolumeProtectTask::BuildRestoreTask(const VolumeRestoreConfig& restoreConfig)
@@ -136,7 +138,15 @@ std::unique_ptr VolumeProtectTask::BuildRestoreTask(const Vol
return nullptr;
}
- return std::unique_ptr(new VolumeRestoreTask(restoreConfig, volumeCopyMeta));
+ if (restoreConfig.enableZeroCopy) {
+ if (static_cast(volumeCopyMeta.copyFormat) != CopyFormat::IMAGE) {
+ ERRLOG("zero copy only supported by CopyFormat::IMAGE copy");
+ return nullptr;
+ }
+ return exstd::make_unique(restoreConfig, volumeCopyMeta);
+ }
+
+ return exstd::make_unique(restoreConfig, volumeCopyMeta);
}
void StatefulTask::Abort()
diff --git a/src/native/linux/LinuxMountUtils.cpp b/src/native/linux/LinuxMountUtils.cpp
index f09d930..ceede68 100644
--- a/src/native/linux/LinuxMountUtils.cpp
+++ b/src/native/linux/LinuxMountUtils.cpp
@@ -24,58 +24,52 @@ namespace {
const std::string SYS_MOUNTS_ENTRY_PATH = "/proc/mounts";
}
-// #include
-// #include
-// #include
-// #include
-// #include
-// #include
-// #include
-// #include
-// #include
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+#define DEFAULT_LOOP_DEVICE "/dev/block/loop0"
+#define LOOPDEV_MAXLEN 64
-// #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
-// #define DEFAULT_LOOP_DEVICE "/dev/block/loop0"
-// #define LOOPDEV_MAXLEN 64
-// struct mount_opts {
-// const char str[16];
-// unsigned long rwmask;
-// unsigned long rwset;
-// unsigned long rwnoset;
-// };
-// struct extra_opts {
-// char *str;
-// char *end;
-// int used_size;
-// int alloc_size;
-// };
-// /*
-// * These options define the function of "mount(2)".
-// */
-// #define MS_TYPE (MS_REMOUNT|MS_BIND|MS_MOVE)
-// static const struct mount_opts options[] = {
-// /* name mask set noset */
-// { "async", MS_SYNCHRONOUS, 0, MS_SYNCHRONOUS },
-// { "atime", MS_NOATIME, 0, MS_NOATIME },
-// { "bind", MS_TYPE, MS_BIND, 0, },
-// { "dev", MS_NODEV, 0, MS_NODEV },
-// { "diratime", MS_NODIRATIME, 0, MS_NODIRATIME },
-// { "dirsync", MS_DIRSYNC, MS_DIRSYNC, 0 },
-// { "exec", MS_NOEXEC, 0, MS_NOEXEC },
-// { "move", MS_TYPE, MS_MOVE, 0 },
-// { "recurse", MS_REC, MS_REC, 0 },
-// { "rec", MS_REC, MS_REC, 0 },
-// { "remount", MS_TYPE, MS_REMOUNT, 0 },
-// { "ro", MS_RDONLY, MS_RDONLY, 0 },
-// { "rw", MS_RDONLY, 0, MS_RDONLY },
-// { "suid", MS_NOSUID, 0, MS_NOSUID },
-// { "sync", MS_SYNCHRONOUS, MS_SYNCHRONOUS, 0 },
-// { "verbose", MS_VERBOSE, MS_VERBOSE, 0 },
-// { "unbindable", MS_UNBINDABLE, MS_UNBINDABLE, 0 },
-// { "private", MS_PRIVATE, MS_PRIVATE, 0 },
-// { "slave", MS_SLAVE, MS_SLAVE, 0 },
-// { "shared", MS_SHARED, MS_SHARED, 0 },
-// };
+struct MountOptionMapEntry {
+ const char str[16];
+ unsigned long rwmask;
+ unsigned long rwset;
+ unsigned long rwnoset;
+};
+
+struct MountExtraOption {
+ char *str;
+ char *end;
+ int usedSize;
+ int allocSize;
+};
+
+/*
+ * These options define the function of "mount(2)".
+ */
+#define MS_TYPE (MS_REMOUNT | MS_BIND | MS_MOVE)
+
+static const struct MountOptionMapEntry options[] = {
+ /* name mask set noset */
+ { "async", MS_SYNCHRONOUS, 0, MS_SYNCHRONOUS },
+ { "atime", MS_NOATIME, 0, MS_NOATIME },
+ { "bind", MS_TYPE, MS_BIND, 0, },
+ { "dev", MS_NODEV, 0, MS_NODEV },
+ { "diratime", MS_NODIRATIME, 0, MS_NODIRATIME },
+ { "dirsync", MS_DIRSYNC, MS_DIRSYNC, 0 },
+ { "exec", MS_NOEXEC, 0, MS_NOEXEC },
+ { "move", MS_TYPE, MS_MOVE, 0 },
+ { "recurse", MS_REC, MS_REC, 0 },
+ { "rec", MS_REC, MS_REC, 0 },
+ { "remount", MS_TYPE, MS_REMOUNT, 0 },
+ { "ro", MS_RDONLY, MS_RDONLY, 0 },
+ { "rw", MS_RDONLY, 0, MS_RDONLY },
+ { "suid", MS_NOSUID, 0, MS_NOSUID },
+ { "sync", MS_SYNCHRONOUS, MS_SYNCHRONOUS, 0 },
+ { "verbose", MS_VERBOSE, MS_VERBOSE, 0 },
+ { "unbindable", MS_UNBINDABLE, MS_UNBINDABLE, 0 },
+ { "private", MS_PRIVATE, MS_PRIVATE, 0 },
+ { "slave", MS_SLAVE, MS_SLAVE, 0 },
+ { "shared", MS_SHARED, MS_SHARED, 0 },
+};
// static void add_extra_option(struct extra_opts *extra, char *s)
// {
@@ -83,22 +77,22 @@ namespace {
// int newlen;
// if (extra->str)
// len++; /* +1 for ',' */
-// newlen = extra->used_size + len;
-// if (newlen >= extra->alloc_size) {
+// newlen = extra->usedSize + len;
+// if (newlen >= extra->allocSize) {
// char *new;
// new = realloc(extra->str, newlen + 1); /* +1 for NUL */
// if (!new)
// return;
// extra->str = new;
-// extra->end = extra->str + extra->used_size;
-// extra->alloc_size = newlen + 1;
+// extra->end = extra->str + extra->usedSize;
+// extra->allocSize = newlen + 1;
// }
-// if (extra->used_size) {
+// if (extra->usedSize) {
// *extra->end = ',';
// extra->end++;
// }
// strcpy(extra->end, s);
-// extra->used_size += len;
+// extra->usedSize += len;
// }
// static unsigned long
// parse_mount_options(char *arg, unsigned long rwflag, struct extra_opts *extra, int* loop, char *loopdev)
@@ -306,7 +300,10 @@ bool linuxmountutil::Mount(
const std::string& mountOptions,
bool readOnly)
{
- unsigned long mountFlags = readOnly ? MS_RDONLY : 0; // TODO:: check mountOptions contains ro
+ unsigned long mountFlags = MS_VERBOSE;
+ if (readOnly) {
+ mountFlags |= MS_RDONLY; // TODO:: check mountOptions contains ro
+ }
if (::mount(devicePath.c_str(),
mountTargetPath.c_str(),
fsType.c_str(),
diff --git a/src/native/linux/PosixRawIO.cpp b/src/native/linux/PosixRawIO.cpp
index ba06bd4..bdaa9a0 100644
--- a/src/native/linux/PosixRawIO.cpp
+++ b/src/native/linux/PosixRawIO.cpp
@@ -4,6 +4,7 @@
* @author XUranus(2257238649wdx@gmail.com)
*/
+#include "RawIO.h"
#include "common/VolumeProtectMacros.h"
#ifdef POSIXAPI
@@ -59,6 +60,11 @@ ErrCodeType PosixRawDataReader::Error()
return static_cast(errno);
}
+HandleType PosixRawDataReader::Handle()
+{
+ return Ok() ? m_fd : -1;
+}
+
PosixRawDataReader::~PosixRawDataReader()
{
if (m_fd < 0) {
@@ -95,6 +101,11 @@ bool PosixRawDataWriter::Ok()
return m_fd > 0;
}
+HandleType PosixRawDataWriter::Handle()
+{
+ return Ok() ? m_fd : -1;
+}
+
bool PosixRawDataWriter::Flush()
{
if (!Ok()) {
diff --git a/src/task/VolumeZeroCopyRestoreTask.cpp b/src/task/VolumeZeroCopyRestoreTask.cpp
new file mode 100644
index 0000000..348db93
--- /dev/null
+++ b/src/task/VolumeZeroCopyRestoreTask.cpp
@@ -0,0 +1,198 @@
+/**
+ * @copyright Copyright 2023 XUranus. All rights reserved.
+ * @license This project is released under the Apache License.
+ * @author XUranus(2257238649wdx@gmail.com)
+ */
+
+#include "Logger.h"
+#include "VolumeProtector.h"
+#include "VolumeProtectTaskContext.h"
+#include "VolumeUtils.h"
+#include "VolumeZeroCopyRestoreTask.h"
+#include "native/RawIO.h"
+
+#ifdef __linux__
+#include
+#endif
+
+using namespace volumeprotect;
+using namespace volumeprotect::task;
+using namespace volumeprotect::common;
+using namespace volumeprotect::rawio;
+
+namespace {
+ constexpr auto TASK_CHECK_SLEEP_INTERVAL = std::chrono::seconds(1);
+}
+
+static std::vector GetCopyFilesFromCopyMeta(const VolumeCopyMeta& volumeCopyMeta)
+{
+ std::vector files;
+ for (const auto& segment : volumeCopyMeta.segments) {
+ files.push_back(segment.copyDataFile);
+ }
+ return files;
+}
+
+VolumeZeroCopyRestoreTask::VolumeZeroCopyRestoreTask(const VolumeRestoreConfig& restoreConfig, const VolumeCopyMeta& volumeCopyMeta)
+ : m_restoreConfig(std::make_shared(restoreConfig)),
+ m_volumeCopyMeta(std::make_shared(volumeCopyMeta)),
+ m_resourceManager(TaskResourceManager::BuildRestoreTaskResourceManager(RestoreTaskResourceManagerParams {
+ static_cast(volumeCopyMeta.copyFormat),
+ restoreConfig.copyDataDirPath,
+ volumeCopyMeta.copyName,
+ GetCopyFilesFromCopyMeta(volumeCopyMeta)
+ }))
+{
+ CopyFormat copyFormat = static_cast(m_volumeCopyMeta->copyFormat);
+ if (copyFormat != CopyFormat::IMAGE || !restoreConfig.enableZeroCopy) {
+ throw std::runtime_error("only image format supported for zero copy");
+ // support CopyFormat::IMAGE only
+ }
+}
+
+VolumeZeroCopyRestoreTask::~VolumeZeroCopyRestoreTask()
+{
+ DBGLOG("destroy volume zero copy restore task, wait main thread to join");
+ if (m_thread.joinable()) {
+ m_thread.join();
+ }
+ DBGLOG("reset zero copy restore resource manager");
+ m_resourceManager.reset();
+ DBGLOG("volume zero copy restore destroyed");
+}
+
+bool VolumeZeroCopyRestoreTask::Start()
+{
+ AssertTaskNotStarted();
+ if (!Prepare()) {
+ ERRLOG("prepare task failed");
+ m_status = TaskStatus::FAILED;
+ return false;
+ }
+ m_status = TaskStatus::RUNNING;
+ m_thread = std::thread(&VolumeZeroCopyRestoreTask::ThreadFunc, this);
+ return true;
+}
+
+TaskStatistics VolumeZeroCopyRestoreTask::GetStatistics() const
+{
+ std::lock_guard lock(m_statisticMutex);
+ return m_completedSessionStatistics + m_currentSessionStatistics;
+}
+
+// split session and write back
+bool VolumeZeroCopyRestoreTask::Prepare()
+{
+ std::string volumePath = m_restoreConfig->volumePath;
+
+ // 2. prepare zero copy restore resource
+ if (!m_resourceManager->PrepareCopyResource()) {
+ ERRLOG("failed to prepare copy resource for zero copy");
+ return false;
+ }
+
+ // 4. split session
+ uint64_t volumeSize = m_volumeCopyMeta->volumeSize;
+ CopyFormat copyFormat = static_cast(m_volumeCopyMeta->copyFormat);
+ for (const CopySegment& segment: m_volumeCopyMeta->segments) {
+ uint64_t sessionOffset = segment.offset;
+ uint64_t sessionSize = segment.length;
+ int sessionIndex = segment.index;
+ INFOLOG("Size = %llu sessionOffset %d sessionSize %d", volumeSize, segment.offset, sessionSize);
+ std::string copyFilePath = common::GetCopyDataFilePath(
+ m_restoreConfig->copyDataDirPath, m_volumeCopyMeta->copyName, copyFormat, sessionIndex);
+
+ VolumeTaskSharedConfig sessionConfig;
+ sessionConfig.copyFormat = static_cast(m_volumeCopyMeta->copyFormat);
+ sessionConfig.volumePath = volumePath;
+ sessionConfig.hasherEnabled = false;
+ sessionConfig.blockSize = m_volumeCopyMeta->blockSize;
+ sessionConfig.sessionOffset = sessionOffset;
+ sessionConfig.sessionSize = sessionSize;
+ sessionConfig.copyFilePath = copyFilePath;
+ sessionConfig.checkpointFilePath = "";
+ sessionConfig.checkpointEnabled = false;
+ sessionConfig.skipEmptyBlock = false;
+ m_sessionQueue.emplace(sessionConfig);
+ }
+ return true;
+}
+
+void VolumeZeroCopyRestoreTask::ThreadFunc()
+{
+ DBGLOG("start task main thread");
+ CopyFormat copyFormat = static_cast(m_volumeCopyMeta->copyFormat);
+ while (!m_sessionQueue.empty()) {
+ if (m_abort) {
+ m_status = TaskStatus::ABORTED;
+ return;
+ }
+ // pop a session from session queue to init a new session
+ VolumeTaskSharedConfig sessionConfig = m_sessionQueue.front();
+ m_sessionQueue.pop();
+ std::shared_ptr dataReader = rawio::OpenRawDataCopyReader(SessionCopyRawIOParam {
+ sessionConfig.copyFormat,
+ sessionConfig.copyFilePath,
+ sessionConfig.sessionOffset,
+ sessionConfig.sessionSize
+ });
+ std::shared_ptr dataWriter = rawio::OpenRawDataVolumeWriter(sessionConfig.volumePath);
+ // check reader writer valid
+ if (dataReader == nullptr || dataWriter == nullptr) {
+ ERRLOG("failed to build copy data reader or writer");
+ m_status = TaskStatus::FAILED;
+ return;
+ }
+ if (!dataReader->Ok() || !dataWriter->Ok()) {
+ ERRLOG("failed to init copy data reader, format = %d, copyfile = %s, error = %u, %u",
+ sessionConfig.copyFormat, sessionConfig.copyFilePath.c_str(), dataReader->Error(), dataWriter->Error());
+ m_status = TaskStatus::FAILED;
+ return;
+ }
+ if (!PerformZeroCopyRestore(dataReader, dataWriter, sessionConfig)) {
+ ERRLOG("session (%llu, %llu) failed during copy", sessionConfig.sessionOffset, sessionConfig.sessionSize);
+ m_status = TaskStatus::FAILED;
+ return;
+ }
+ }
+ DBGLOG("exit zero copy main thread, all session succeed");
+ m_status = TaskStatus::SUCCEED;
+ return;
+}
+
+bool VolumeZeroCopyRestoreTask::PerformZeroCopyRestore(
+ std::shared_ptr copyDataReader,
+ std::shared_ptr volumeDataWriter,
+ const VolumeTaskSharedConfig& sessionConfig)
+{
+ {
+ std::lock_guard lock(m_statisticMutex);
+ m_completedSessionStatistics = m_completedSessionStatistics + m_currentSessionStatistics;
+ memset(&m_currentSessionStatistics, 0, sizeof(TaskStatistics));
+ m_currentSessionStatistics.bytesToRead = sessionConfig.sessionSize;
+ m_currentSessionStatistics.bytesToWrite = sessionConfig.sessionSize;
+ }
+
+ uint64_t offset = sessionConfig.sessionOffset;
+ size_t len = sessionConfig.blockSize;
+ uint64_t sessionMax = sessionConfig.sessionOffset + sessionConfig.sessionSize;
+ INFOLOG("perform zero copy restore, offset %llu, len %ld, sessionMax %llu", offset, sessionMax);
+ while (offset < sessionMax) {
+ if (offset + len > sessionMax) {
+ len = sessionMax - offset;
+ }
+#ifdef __linux__
+ int ret = ::sendfile(volumeDataWriter->Handle(), copyDataReader->Handle(), reinterpret_cast(&offset), len);
+ DBGLOG("sendfile syscall return = %d, offset = %llu, len = %llu", ret, offset, len);
+ if (ret < 0) {
+ ERRLOG("sendfile (%llu, %llu) failed with errno %d", offset, len, errno);
+ return false;
+ }
+#endif
+ m_currentSessionStatistics.bytesRead += ret;
+ m_currentSessionStatistics.bytesWritten += ret;
+ }
+
+
+ return true;
+}
\ No newline at end of file