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 +
VolumeBackup  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