diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f18421..a62ec1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,25 @@ set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF) set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) +# macOS specific settings +if(APPLE) + # Detect architecture + if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64") + set(CMAKE_OSX_ARCHITECTURES "arm64") + message(STATUS "Building for macOS ARM64") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") + set(CMAKE_OSX_ARCHITECTURES "x86_64") + message(STATUS "Building for macOS x86_64") + endif() + + # Set minimum macOS version that supports ARM64 + set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0") + + # Enable Objective-C++ support for .mm files + enable_language(OBJCXX) + set(CMAKE_OBJCXX_STANDARD 17) +endif() + option(ZFSW_BUILD_SHARED_LIBRARY "Build the ZFSWrapper as shared library" OFF) option(ZFSW_HAS_ZPOOL_STATUS_COMPATIBILITY_ERR "ZFS has symbol ZPOOL_STATUS_COMPATIBILITY_ERR" OFF) @@ -24,6 +43,19 @@ option(ZFSW_HAS_ZPOOL_STATUS_INCOMPATIBLE_FEAT "ZFS has symbol ZPOOL_STATUS_INCO list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") +# macOS specific library paths for ARM64 +if(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64") + # Add Homebrew ARM64 paths to search + list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew") + list(APPEND CMAKE_LIBRARY_PATH "/opt/homebrew/lib") + list(APPEND CMAKE_INCLUDE_PATH "/opt/homebrew/include") +elseif(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") + # Add Homebrew x86_64 paths to search + list(APPEND CMAKE_PREFIX_PATH "/usr/local") + list(APPEND CMAKE_LIBRARY_PATH "/usr/local/lib") + list(APPEND CMAKE_INCLUDE_PATH "/usr/local/include") +endif() + find_package(ZFS REQUIRED) ################################################################################ @@ -34,11 +66,18 @@ set(ZFS_WRAPPER_SOURCES include/ZFSNVList.hpp include/ZFSStrings.hpp include/ZFSUtils.hpp + include/ZFSManager.hpp src/ZFSNVList.cpp src/ZFSStrings.cpp src/ZFSUtils.cpp + src/ZFSManager.cpp ) +# Add Objective-C++ source files for macOS +if(APPLE) + list(APPEND ZFS_WRAPPER_SOURCES src/ZFSStrings.mm) +endif() + if(ZFSW_BUILD_SHARED_LIBRARY) set(ZFSW_LIBRARY_TYPE SHARED) else() @@ -63,6 +102,13 @@ target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads ) +# macOS specific linking +if(APPLE) + target_link_libraries(${PROJECT_NAME} PRIVATE + "-framework Foundation" + ) +endif() + target_include_directories(${PROJECT_NAME} PUBLIC $ @@ -76,4 +122,4 @@ target_compile_options(${PROJECT_NAME} PRIVATE ) # Organization in Xcode and Co -source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${QN_SOURCES}) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${ZFS_WRAPPER_SOURCES}) diff --git a/OPENZFS_2_3_0_FEATURES.md b/OPENZFS_2_3_0_FEATURES.md new file mode 100644 index 0000000..c007b3a --- /dev/null +++ b/OPENZFS_2_3_0_FEATURES.md @@ -0,0 +1,218 @@ +# OpenZFS 2.3.0 Features in ZFSWrapper + +This document describes the new OpenZFS 2.3.0 features that have been added to the ZFSWrapper library. + +## Overview + +OpenZFS 2.3.0 introduced several significant features that enhance performance, flexibility, and functionality: + +- **RAIDZ Expansion**: Add devices to existing RAIDZ vdevs without downtime +- **Direct I/O**: Bypass ARC for better performance with fast storage devices +- **Enhanced Deduplication**: Better management of deduplication with quotas and statistics +- **Long Filename Support**: Support for filenames up to 1023 characters +- **Wait Operations**: Wait for pool operations to complete + +## API Reference + +### ZFSManager Class (High-Level API) + +#### RAIDZ Expansion +```cpp +// Expand a RAIDZ vdev by adding a new device +bool expandRaidz(const std::string& poolName, const std::string& raidzName, const std::string& newDevice); + +// Check if a pool is currently expanding RAIDZ +bool isPoolRaidzExpanding(const std::string& poolName); +``` + +#### Direct I/O Support +```cpp +// Set Direct I/O mode for a filesystem +bool setDirectIO(const std::string& filesystemName, bool enabled); +``` + +#### Enhanced Deduplication +```cpp +// Get deduplication statistics for a pool +bool getDedupStats(const std::string& poolName, std::uint64_t& tableSize, std::uint64_t& tableCached); + +// Set deduplication table quota for a pool +bool setDedupQuota(const std::string& poolName, std::uint64_t quota); +``` + +### ZPool Class (Low-Level API) + +#### RAIDZ Expansion +```cpp +// Add devices to an existing RAIDZ vdev +int raidzExpand(const std::string& raidz_name, const std::string& new_device); + +// Get RAIDZ expansion statistics +struct RaidzExpandStat { + uint64_t res_state; // Current state of expansion + uint64_t res_start_time; // Start time of expansion + uint64_t res_end_time; // End time of expansion + uint64_t res_to_reflow; // Total bytes to reflow + uint64_t res_reflowed; // Bytes already reflowed + uint64_t res_waiting_for_resilver; // Waiting for resilver +}; + +bool getRaidzExpandStats(RaidzExpandStat& stats) const; +bool isRaidzExpanding() const; +``` + +#### Enhanced Deduplication +```cpp +// Get deduplication table size for this pool +std::uint64_t getDedupTableSize() const; + +// Get deduplication table quota for this pool +std::uint64_t getDedupTableQuota() const; + +// Get cached deduplication data amount for this pool +std::uint64_t getDedupCached() const; + +// Set deduplication table quota for this pool +int setDedupTableQuota(std::uint64_t quota); +``` + +#### Wait Operations +```cpp +// Wait for RAIDZ expansion to complete +int waitForRaidzExpansion(); + +// Wait for scrub to complete +int waitForScrub(); +``` + +### ZFileSystem Class (Low-Level API) + +#### Direct I/O Support +```cpp +enum class DirectIOMode { + disabled = 0, // Direct I/O disabled (default) + standard = 1, // Direct I/O enabled for large I/O + always = 2 // Direct I/O always enabled +}; + +// Set Direct I/O mode for this filesystem +int setDirectIOMode(DirectIOMode mode); + +// Get Direct I/O mode for this filesystem +DirectIOMode getDirectIOMode() const; + +// Check if Direct I/O is supported for this filesystem +bool isDirectIOSupported() const; +``` + +#### Long Filename Support +```cpp +// Check if long filenames (up to 1023 chars) are supported +bool isLongNameSupported() const; +``` + +## Usage Examples + +### Example 1: RAIDZ Expansion +```cpp +zfs::ZFSManager manager; + +// Check if expansion is in progress +if (manager.isPoolRaidzExpanding("mypool")) { + std::cout << "Pool is currently expanding" << std::endl; +} + +// Add a device to expand RAIDZ +if (manager.expandRaidz("mypool", "raidz-0", "/dev/disk3")) { + std::cout << "RAIDZ expansion initiated" << std::endl; +} +``` + +### Example 2: Direct I/O for NVMe Performance +```cpp +zfs::ZFSManager manager; + +// Enable Direct I/O for better NVMe performance +if (manager.setDirectIO("mypool/dataset", true)) { + std::cout << "Direct I/O enabled" << std::endl; +} +``` + +### Example 3: Deduplication Management +```cpp +zfs::ZFSManager manager; + +// Get deduplication statistics +std::uint64_t tableSize, tableCached; +if (manager.getDedupStats("mypool", tableSize, tableCached)) { + std::cout << "Dedup table: " << tableSize << " bytes" << std::endl; + std::cout << "Cached: " << tableCached << " bytes" << std::endl; +} + +// Set 1GB deduplication quota +manager.setDedupQuota("mypool", 1024ULL * 1024 * 1024); +``` + +### Example 4: Wait for Operations +```cpp +zfs::LibZFSHandle lib; +auto pool = lib.pool("mypool"); + +// Start a scrub and wait for completion +pool.scrub(); +pool.waitForScrub(); +std::cout << "Scrub completed" << std::endl; + +// Wait for RAIDZ expansion if in progress +if (pool.isRaidzExpanding()) { + pool.waitForRaidzExpansion(); + std::cout << "RAIDZ expansion completed" << std::endl; +} +``` + +## Compilation Requirements + +To use these features, ensure you have: + +1. **OpenZFS 2.3.0 or later** installed +2. **macOS 11.0 or later** (for ARM64 support) +3. **CMake 3.10 or later** +4. **C++17 compatible compiler** + +The features are automatically detected at compile time and enabled via CMake options: + +```bash +cmake .. -DZFSW_HAS_ZPOOL_STATUS_COMPATIBILITY_ERR=ON \ + -DZFSW_HAS_ZPOOL_STATUS_INCOMPATIBLE_FEAT=ON +``` + +## Notes and Limitations + +1. **RAIDZ Expansion**: This is a long-running operation. Use `waitForRaidzExpansion()` carefully in production code. + +2. **Direct I/O**: Best suited for fast storage devices like NVMe. May not provide benefits on slower devices. + +3. **Deduplication**: Monitor memory usage when enabling deduplication, especially with quotas. + +4. **Long Filenames**: While supported, very long filenames may impact performance. + +5. **Platform Support**: These features are tested on macOS ARM64. Linux and other platforms should work but may require additional testing. + +## Building the Examples + +```bash +cd examples +mkdir build && cd build +cmake .. +make openzfs_2_3_0_demo +./openzfs_2_3_0_demo +``` + +## Error Handling + +All functions follow the existing ZFSWrapper error handling patterns: + +- **High-level API** (ZFSManager): Returns `bool` for success/failure, prints errors to stderr +- **Low-level API** (ZPool/ZFileSystem): Returns `int` error codes (0 = success) or throws exceptions + +Always check return values and handle exceptions appropriately in production code. diff --git a/README.md b/README.md index 8cd0130..88c142f 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ For Developers Dependencies ------------ -On MacOS, OpenZFS 2.0.1 is tested. The following OpenZFS needs to be installed: - - [OpenZFS 2.0.1](https://openzfsonosx.org/forum/viewtopic.php?f=20&t=3569&p=11206#p11206) +On MacOS, OpenZFS 2.3.0 is tested. The following OpenZFS needs to be installed: + - [OpenZFS 2.3.0](https://github.com/openzfsonosx/openzfs-fork/releases/tag/zfs-macOS-2.3.0) On Ubuntu 21.04, OpenZFS 2.0.2 is tested. The following apt packages need to be installed: - `libzfslinux-dev` @@ -25,7 +25,6 @@ Building -------- Building works as usual with CMake. -On MacOS, pass `-DZFSW_HAS_ZPOOL_STATUS_COMPATIBILITY_ERR` to CMake. ``` mkdir build diff --git a/cmake/FindZFS.cmake b/cmake/FindZFS.cmake index 5883fd0..1fb96b0 100644 --- a/cmake/FindZFS.cmake +++ b/cmake/FindZFS.cmake @@ -2,34 +2,98 @@ find_path(ZFS_INCLUDE_DIR NAMES "libzfs.h" HINTS /usr/include/libzfs # libzfslinux-dev on Ubuntu /usr/local/zfs/include/libzfs # OpenZFSOnOSX 2.0 + /opt/homebrew/include/libzfs # Homebrew on macOS ARM64 + /usr/local/include/libzfs # Homebrew on macOS x86_64 + /opt/local/include/libzfs # MacPorts ) find_path(SPL_INCLUDE_DIR NAMES "stdlib.h" HINTS /usr/include/libspl # libzfslinux-dev on Unbuntu /usr/local/zfs/include/libspl # OpenZFSOnOSX 2.0 + /opt/homebrew/include/libspl # Homebrew on macOS ARM64 + /usr/local/include/libspl # Homebrew on macOS x86_64 + /opt/local/include/libspl # MacPorts ) -find_library(ZFS_CORE_LIB NAMES "zfs_core") -find_library(ZFS_LIB NAMES "zfs") -find_library(ZPOOL_LIB NAMES "zpool") -find_library(NVPAIR_LIB NAMES "nvpair") +# Find macOS-specific SPL headers +if(APPLE) + find_path(SPL_MACOS_INCLUDE_DIR NAMES "sys/sysmacros.h" + HINTS + /usr/local/zfs/include/libspl/os/macos + /opt/homebrew/include/libspl/os/macos + /usr/local/include/libspl/os/macos + /opt/local/include/libspl/os/macos + ) +endif() -add_library(ZFSDependencies INTERFACE IMPORTED) -set_target_properties(ZFSDependencies PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES - "${ZFS_INCLUDE_DIR};${SPL_INCLUDE_DIR}" +find_library(ZFS_CORE_LIB NAMES "zfs_core" + HINTS + /usr/lib # Linux standard + /usr/local/zfs/lib # OpenZFSOnOSX 2.0 + /opt/homebrew/lib # Homebrew on macOS ARM64 + /usr/local/lib # Homebrew on macOS x86_64 + /opt/local/lib # MacPorts +) +find_library(ZFS_LIB NAMES "zfs" + HINTS + /usr/lib # Linux standard + /usr/local/zfs/lib # OpenZFSOnOSX 2.0 + /opt/homebrew/lib # Homebrew on macOS ARM64 + /usr/local/lib # Homebrew on macOS x86_64 + /opt/local/lib # MacPorts +) +find_library(ZPOOL_LIB NAMES "zpool" + HINTS + /usr/lib # Linux standard + /usr/local/zfs/lib # OpenZFSOnOSX 2.0 + /opt/homebrew/lib # Homebrew on macOS ARM64 + /usr/local/lib # Homebrew on macOS x86_64 + /opt/local/lib # MacPorts ) +find_library(NVPAIR_LIB NAMES "nvpair" + HINTS + /usr/lib # Linux standard + /usr/local/zfs/lib # OpenZFSOnOSX 2.0 + /opt/homebrew/lib # Homebrew on macOS ARM64 + /usr/local/lib # Homebrew on macOS x86_64 + /opt/local/lib # MacPorts +) + +add_library(ZFSDependencies INTERFACE IMPORTED) +if(APPLE AND SPL_MACOS_INCLUDE_DIR) + set_target_properties(ZFSDependencies PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES + "${ZFS_INCLUDE_DIR};${SPL_INCLUDE_DIR};${SPL_MACOS_INCLUDE_DIR}" + ) +else() + set_target_properties(ZFSDependencies PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES + "${ZFS_INCLUDE_DIR};${SPL_INCLUDE_DIR}" + ) +endif() target_link_libraries(ZFSDependencies INTERFACE ${ZFS_CORE_LIB} ${ZFS_LIB} ${ZPOOL_LIB} ${NVPAIR_LIB} ) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(ZFS DEFAULT_MSG - ZFS_INCLUDE_DIR - SPL_INCLUDE_DIR - ZFS_CORE_LIB - ZFS_LIB - ZPOOL_LIB - NVPAIR_LIB -) +if(APPLE) + find_package_handle_standard_args(ZFS DEFAULT_MSG + ZFS_INCLUDE_DIR + SPL_INCLUDE_DIR + SPL_MACOS_INCLUDE_DIR + ZFS_CORE_LIB + ZFS_LIB + ZPOOL_LIB + NVPAIR_LIB + ) +else() + find_package_handle_standard_args(ZFS DEFAULT_MSG + ZFS_INCLUDE_DIR + SPL_INCLUDE_DIR + ZFS_CORE_LIB + ZFS_LIB + ZPOOL_LIB + NVPAIR_LIB + ) +endif() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..15ecd22 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,54 @@ +# Example programs for ZFSWrapper with OpenZFS 2.3.0 features + +cmake_minimum_required(VERSION 3.10) + +# Find the parent ZFSWrapper library +find_library(ZFSWRAPPER_LIB ZFSWrapper PATHS ${CMAKE_CURRENT_SOURCE_DIR}/../build) +find_path(ZFSWRAPPER_INCLUDE_DIR ZFSManager.hpp PATHS ${CMAKE_CURRENT_SOURCE_DIR}/../include) + +if(NOT ZFSWRAPPER_LIB OR NOT ZFSWRAPPER_INCLUDE_DIR) + message(FATAL_ERROR "ZFSWrapper library not found. Please build the main project first.") +endif() + +# Set up compiler options +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# macOS specific settings +if(APPLE) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64") + set(CMAKE_OSX_ARCHITECTURES "arm64") + else() + set(CMAKE_OSX_ARCHITECTURES "x86_64") + endif() + set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0") +endif() + +# Include directories +include_directories(${ZFSWRAPPER_INCLUDE_DIR}) + +# Find ZFS dependencies +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../cmake") +find_package(ZFS REQUIRED) + +# Example executable +add_executable(openzfs_2_3_0_demo openzfs_2_3_0_features.cpp) + +# Link libraries +target_link_libraries(openzfs_2_3_0_demo + ${ZFSWRAPPER_LIB} + ZFSDependencies +) + +if(APPLE) + target_link_libraries(openzfs_2_3_0_demo "-framework Foundation") +endif() + +# Set compile options +target_compile_options(openzfs_2_3_0_demo PRIVATE + -Wall -Wextra -pedantic + -Wno-missing-field-initializers +) + +message(STATUS "Example program configured. Build with: make openzfs_2_3_0_demo") +message(STATUS "Note: Ensure you have ZFS pools available for testing.") diff --git a/examples/openzfs_2_3_0_features.cpp b/examples/openzfs_2_3_0_features.cpp new file mode 100644 index 0000000..14ce9ba --- /dev/null +++ b/examples/openzfs_2_3_0_features.cpp @@ -0,0 +1,152 @@ +// +// OpenZFS 2.3.0 Features Example +// Demonstrates the new functionality added to ZFSWrapper +// + +#include "ZFSManager.hpp" +#include "ZFSUtils.hpp" +#include +#include + +int main() +{ + try + { + zfs::ZFSManager manager; + + std::cout << "=== OpenZFS 2.3.0 Features Demo ===" << std::endl; + + // Example pool and filesystem names (adjust as needed) + const std::string poolName = "testpool"; + const std::string filesystemName = "testpool/testfs"; + const std::string newDevice = "/dev/disk3"; + + // 1. RAIDZ Expansion Feature + std::cout << "\n1. RAIDZ Expansion Features:" << std::endl; + + if (manager.isPoolRaidzExpanding(poolName)) + { + std::cout << " Pool " << poolName << " is currently expanding RAIDZ" << std::endl; + } + else + { + std::cout << " Pool " << poolName << " is not expanding RAIDZ" << std::endl; + } + + // Example: Expand RAIDZ (uncomment to test with real pool) + // if (manager.expandRaidz(poolName, "raidz-0", newDevice)) + // { + // std::cout << " Successfully initiated RAIDZ expansion" << std::endl; + // } + + // 2. Direct I/O Support + std::cout << "\n2. Direct I/O Support:" << std::endl; + + // Enable Direct I/O for better performance with NVMe devices + if (manager.setDirectIO(filesystemName, true)) + { + std::cout << " Enabled Direct I/O for " << filesystemName << std::endl; + } + else + { + std::cout << " Direct I/O not available or filesystem not found" << std::endl; + } + + // 3. Enhanced Deduplication + std::cout << "\n3. Enhanced Deduplication Features:" << std::endl; + + std::uint64_t tableSize, tableCached; + if (manager.getDedupStats(poolName, tableSize, tableCached)) + { + std::cout << " Dedup table size: " << tableSize << " bytes" << std::endl; + std::cout << " Dedup cached data: " << tableCached << " bytes" << std::endl; + } + else + { + std::cout << " Pool not found or deduplication not enabled" << std::endl; + } + + // Set a deduplication quota (1GB example) + const std::uint64_t dedupQuota = 1024ULL * 1024 * 1024; // 1GB + if (manager.setDedupQuota(poolName, dedupQuota)) + { + std::cout << " Set deduplication quota to 1GB" << std::endl; + } + + // 4. Low-level API demonstration + std::cout << "\n4. Low-level API Features:" << std::endl; + + try + { + auto lib = zfs::LibZFSHandle(); + auto pool = lib.pool(poolName); + + // Check RAIDZ expansion statistics + zfs::ZPool::RaidzExpandStat expandStats; + if (pool.getRaidzExpandStats(expandStats)) + { + std::cout << " RAIDZ expansion state: " << expandStats.res_state << std::endl; + std::cout << " Bytes to reflow: " << expandStats.res_to_reflow << std::endl; + std::cout << " Bytes reflowed: " << expandStats.res_reflowed << std::endl; + } + + // Check deduplication properties + std::cout << " Dedup table size: " << pool.getDedupTableSize() << std::endl; + std::cout << " Dedup table quota: " << pool.getDedupTableQuota() << std::endl; + std::cout << " Dedup cached: " << pool.getDedupCached() << std::endl; + + // Example filesystem features + try + { + auto fs = lib.filesystem(filesystemName); + + // Check Direct I/O support + if (fs.isDirectIOSupported()) + { + auto mode = fs.getDirectIOMode(); + std::cout << " Direct I/O mode: "; + switch (mode) + { + case zfs::ZFileSystem::DirectIOMode::disabled: + std::cout << "disabled" << std::endl; + break; + case zfs::ZFileSystem::DirectIOMode::standard: + std::cout << "standard" << std::endl; + break; + case zfs::ZFileSystem::DirectIOMode::always: + std::cout << "always" << std::endl; + break; + } + } + + // Check long filename support + if (fs.isLongNameSupported()) + { + std::cout << " Long filenames (up to 1023 chars) are supported" << std::endl; + } + } + catch (const std::exception& e) + { + std::cout << " Filesystem " << filesystemName << " not found" << std::endl; + } + + // Wait for operations (example - be careful with this in production) + // pool.waitForScrub(); // Wait for scrub to complete + // pool.waitForRaidzExpansion(); // Wait for RAIDZ expansion to complete + } + catch (const std::exception& e) + { + std::cout << " Pool " << poolName << " not found or error: " << e.what() << std::endl; + } + + std::cout << "\n=== Demo Complete ===" << std::endl; + std::cout << "Note: Adjust pool and filesystem names to match your setup" << std::endl; + + return 0; + } + catch (const std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } +} diff --git a/include/ZFSManager.hpp b/include/ZFSManager.hpp new file mode 100644 index 0000000..f77f16e --- /dev/null +++ b/include/ZFSManager.hpp @@ -0,0 +1,167 @@ +// +// ZFSManager.hpp +// ZFSWrapper +// +// Created by jaminmc on 2025/08/18. +// + +#ifndef ZFSMANAGER_HPP +#define ZFSMANAGER_HPP + +#include "ZFSUtils.hpp" +#include +#include + +namespace zfs +{ + /** + * @brief High-level interface for ZFS filesystem operations + * + * The ZFSManager class provides a simplified interface for common ZFS operations + * such as creating, mounting, and managing filesystems and snapshots. + */ + class ZFSManager + { + public: + /** + * @brief Construct a new ZFSManager object + */ + ZFSManager(); + + // Filesystem operations + + /** + * @brief Create a new ZFS filesystem + * @param name The name of the filesystem to create + * @param mountpoint The mountpoint for the filesystem (optional) + * @return true if successful, false otherwise + */ + bool createFilesystem(const std::string& name, const std::string& mountpoint = ""); + + /** + * @brief List all ZFS filesystems + * @return Vector of filesystem names + */ + std::vector listFilesystems() const; + + /** + * @brief List ZFS filesystems in a specific pool + * @param poolName The name of the pool to list filesystems from + * @return Vector of filesystem names + */ + std::vector listFilesystems(const std::string& poolName) const; + + /** + * @brief Destroy a ZFS filesystem + * @param name The name of the filesystem to destroy + * @param force Whether to force destruction + * @return true if successful, false otherwise + */ + bool destroyFilesystem(const std::string& name, bool force = false); + + /** + * @brief Mount a ZFS filesystem + * @param name The name of the filesystem to mount + * @return true if successful, false otherwise + */ + bool mountFilesystem(const std::string& name); + + /** + * @brief Unmount a ZFS filesystem + * @param name The name of the filesystem to unmount + * @param force Whether to force unmounting + * @return true if successful, false otherwise + */ + bool unmountFilesystem(const std::string& name, bool force = false); + + // Snapshot operations + + /** + * @brief Create a snapshot of a filesystem + * @param filesystemName The name of the filesystem to snapshot + * @param snapshotName The name of the snapshot + * @return true if successful, false otherwise + */ + bool createSnapshot(const std::string& filesystemName, const std::string& snapshotName); + + /** + * @brief List snapshots for a filesystem + * @param filesystemName The name of the filesystem + * @return Vector of snapshot names + */ + std::vector listSnapshots(const std::string& filesystemName) const; + + /** + * @brief Destroy a snapshot + * @param snapshotName The full name of the snapshot (filesystem@snapshot) + * @param force Whether to force destruction + * @return true if successful, false otherwise + */ + bool destroySnapshot(const std::string& snapshotName, bool force = false); + + /** + * @brief Rollback to a snapshot + * @param snapshotName The full name of the snapshot (filesystem@snapshot) + * @param force Whether to force rollback + * @return true if successful, false otherwise + */ + bool rollbackSnapshot(const std::string& snapshotName, bool force = false); + + /** + * @brief Clone a snapshot to a new filesystem + * @param snapshotName The full name of the snapshot (filesystem@snapshot) + * @param newFilesystemName The name of the new filesystem + * @return true if successful, false otherwise + */ + bool cloneSnapshot(const std::string& snapshotName, const std::string& newFilesystemName); + + // OpenZFS 2.3.0+ Features + + /** + * @brief Expand a RAIDZ vdev by adding a new device + * @param poolName The name of the pool + * @param raidzName The name of the RAIDZ vdev to expand + * @param newDevice The path to the new device to add + * @return true if successful, false otherwise + */ + bool expandRaidz(const std::string& poolName, const std::string& raidzName, const std::string& newDevice); + + /** + * @brief Check if a pool is currently expanding RAIDZ + * @param poolName The name of the pool to check + * @return true if expanding, false otherwise + */ + bool isPoolRaidzExpanding(const std::string& poolName); + + /** + * @brief Set Direct I/O mode for a filesystem + * @param filesystemName The name of the filesystem + * @param enabled Whether to enable Direct I/O (true=standard, false=disabled) + * @return true if successful, false otherwise + */ + bool setDirectIO(const std::string& filesystemName, bool enabled); + + /** + * @brief Get deduplication statistics for a pool + * @param poolName The name of the pool + * @param tableSize Output: deduplication table size + * @param tableCached Output: cached deduplication data + * @return true if successful, false otherwise + */ + bool getDedupStats(const std::string& poolName, std::uint64_t& tableSize, std::uint64_t& tableCached); + + /** + * @brief Set deduplication table quota for a pool + * @param poolName The name of the pool + * @param quota The quota in bytes (0 = unlimited) + * @return true if successful, false otherwise + */ + bool setDedupQuota(const std::string& poolName, std::uint64_t quota); + + private: + LibZFSHandle m_lib; ///< The underlying libzfs handle + }; + +} // namespace zfs + +#endif // ZFSMANAGER_HPP diff --git a/include/ZFSUtils.hpp b/include/ZFSUtils.hpp index 9bd466f..a982c0e 100644 --- a/include/ZFSUtils.hpp +++ b/include/ZFSUtils.hpp @@ -386,6 +386,30 @@ class ZFileSystem int clone(std::string const & newFSName); //!< Clone the snapshot into a dependent FS +public: // Direct I/O support (OpenZFS 2.3.0+) + + //! Direct I/O mode enumeration + enum class DirectIOMode + { + disabled = 0, ///< Direct I/O disabled (default) + standard = 1, ///< Direct I/O enabled for large I/O + always = 2 ///< Direct I/O always enabled + }; + + //! Set Direct I/O mode for this filesystem + int setDirectIOMode(DirectIOMode mode); + + //! Get Direct I/O mode for this filesystem + DirectIOMode getDirectIOMode() const; + + //! Check if Direct I/O is supported for this filesystem + bool isDirectIOSupported() const; + +public: // Long filename support (OpenZFS 2.3.0+) + + //! Check if long filenames (up to 1023 chars) are supported + bool isLongNameSupported() const; + private: zfs_handle_t * m_handle; }; @@ -551,6 +575,48 @@ class ZPool //! Stops a scrub void scrubStop(); + //! Add devices to an existing RAIDZ vdev (RAIDZ expansion - OpenZFS 2.3.0+) + int raidzExpand(const std::string& raidz_name, const std::string& new_device); + + //! Get RAIDZ expansion status and statistics + struct RaidzExpandStat + { + uint64_t res_state; ///< Current state of expansion + uint64_t res_start_time; ///< Start time of expansion + uint64_t res_end_time; ///< End time of expansion + uint64_t res_to_reflow; ///< Total bytes to reflow + uint64_t res_reflowed; ///< Bytes already reflowed + uint64_t res_waiting_for_resilver; ///< Waiting for resilver + }; + + //! Get RAIDZ expansion statistics for this pool + bool getRaidzExpandStats(RaidzExpandStat& stats) const; + + //! Check if a RAIDZ vdev is currently expanding + bool isRaidzExpanding() const; + +public: // Enhanced deduplication support (OpenZFS 2.3.0+) + + //! Get deduplication table size for this pool + std::uint64_t getDedupTableSize() const; + + //! Get deduplication table quota for this pool + std::uint64_t getDedupTableQuota() const; + + //! Get cached deduplication data amount for this pool + std::uint64_t getDedupCached() const; + + //! Set deduplication table quota for this pool + int setDedupTableQuota(std::uint64_t quota); + +public: // Wait functionality for operations (OpenZFS 2.3.0+) + + //! Wait for RAIDZ expansion to complete + int waitForRaidzExpansion(); + + //! Wait for scrub to complete + int waitForScrub(); + public: zpool_handle_t * handle() const; diff --git a/src/ZFSManager.cpp b/src/ZFSManager.cpp new file mode 100644 index 0000000..c0077db --- /dev/null +++ b/src/ZFSManager.cpp @@ -0,0 +1,309 @@ +// +// ZFSManager.cpp +// ZFSWrapper +// +// Created by jaminmc on 2025/08/18. +// + +#include "ZFSManager.hpp" +#include +#include + +namespace zfs +{ + ZFSManager::ZFSManager() : m_lib() + { + } + + bool ZFSManager::createFilesystem(const std::string& name, const std::string& mountpoint) + { + try + { + int result = m_lib.createFilesystem(name, mountpoint); + return result == 0; + } + catch (const std::exception& e) + { + std::cerr << "Error creating filesystem " << name << ": " << e.what() << std::endl; + return false; + } + } + + std::vector ZFSManager::listFilesystems() const + { + std::vector filesystems; + + try + { + auto pools = m_lib.pools(); + for (const auto& pool : pools) + { + try + { + auto rootFS = pool.rootFileSystem(); + auto allFS = rootFS.allFileSystems(); + + for (const auto& fs : allFS) + { + filesystems.push_back(fs.name()); + } + } + catch (const std::exception&) + { + // Skip pools that might not have filesystems or are inaccessible + continue; + } + } + } + catch (const std::exception& e) + { + std::cerr << "Error listing filesystems: " << e.what() << std::endl; + } + + return filesystems; + } + + std::vector ZFSManager::listFilesystems(const std::string& poolName) const + { + std::vector filesystems; + + try + { + auto pool = m_lib.pool(poolName); + auto rootFS = pool.rootFileSystem(); + auto allFS = rootFS.allFileSystems(); + + for (const auto& fs : allFS) + { + filesystems.push_back(fs.name()); + } + } + catch (const std::exception& e) + { + std::cerr << "Error listing filesystems in pool " << poolName << ": " << e.what() << std::endl; + } + + return filesystems; + } + + bool ZFSManager::destroyFilesystem(const std::string& name, bool force) + { + try + { + auto fs = m_lib.filesystem(name); + int result = fs.destroy(force); + return result == 0; + } + catch (const std::exception& e) + { + std::cerr << "Error destroying filesystem " << name << ": " << e.what() << std::endl; + return false; + } + } + + bool ZFSManager::mountFilesystem(const std::string& name) + { + try + { + auto fs = m_lib.filesystem(name); + int result = fs.mount(); + return result == 0; + } + catch (const std::exception& e) + { + std::cerr << "Error mounting filesystem " << name << ": " << e.what() << std::endl; + return false; + } + } + + bool ZFSManager::unmountFilesystem(const std::string& name, bool force) + { + try + { + auto fs = m_lib.filesystem(name); + int result = fs.unmount(force); + return result == 0; + } + catch (const std::exception& e) + { + std::cerr << "Error unmounting filesystem " << name << ": " << e.what() << std::endl; + return false; + } + } + + bool ZFSManager::createSnapshot(const std::string& filesystemName, const std::string& snapshotName) + { + try + { + // Construct the full snapshot name + std::string fullSnapshotName = filesystemName + "@" + snapshotName; + + // Check if the filesystem exists + auto fs = m_lib.filesystem(filesystemName); + + // Create the snapshot + int result = fs.snapshot(snapshotName, false); + return result == 0; + } + catch (const std::exception& e) + { + std::cerr << "Error creating snapshot " << snapshotName << " for filesystem " << filesystemName << ": " << e.what() << std::endl; + return false; + } + } + + std::vector ZFSManager::listSnapshots(const std::string& filesystemName) const + { + std::vector snapshots; + + try + { + auto fs = m_lib.filesystem(filesystemName); + auto snapshotList = fs.snapshots(); + + for (const auto& snap : snapshotList) + { + snapshots.push_back(snap.name()); + } + } + catch (const std::exception& e) + { + std::cerr << "Error listing snapshots for filesystem " << filesystemName << ": " << e.what() << std::endl; + } + + return snapshots; + } + + bool ZFSManager::destroySnapshot(const std::string& snapshotName, bool force) + { + try + { + // Check if the snapshot exists + auto fs = m_lib.filesystem(snapshotName); + + // For snapshots, we destroy them directly + int result = fs.destroy(force); + return result == 0; + } + catch (const std::exception& e) + { + std::cerr << "Error destroying snapshot " << snapshotName << ": " << e.what() << std::endl; + return false; + } + } + + bool ZFSManager::rollbackSnapshot(const std::string& snapshotName, bool force) + { + try + { + // Check if the snapshot exists + auto fs = m_lib.filesystem(snapshotName); + + // Rollback to the snapshot + int result = fs.rollback(force); + return result == 0; + } + catch (const std::exception& e) + { + std::cerr << "Error rolling back snapshot " << snapshotName << ": " << e.what() << std::endl; + return false; + } + } + + bool ZFSManager::cloneSnapshot(const std::string& snapshotName, const std::string& newFilesystemName) + { + try + { + // Check if the snapshot exists + auto fs = m_lib.filesystem(snapshotName); + + // Clone the snapshot to a new filesystem + int result = fs.clone(newFilesystemName); + return result == 0; + } + catch (const std::exception& e) + { + std::cerr << "Error cloning snapshot " << snapshotName << " to filesystem " << newFilesystemName << ": " << e.what() << std::endl; + return false; + } + } + + // OpenZFS 2.3.0+ Features Implementation + + bool ZFSManager::expandRaidz(const std::string& poolName, const std::string& raidzName, const std::string& newDevice) + { + try + { + auto pool = m_lib.pool(poolName); + int result = pool.raidzExpand(raidzName, newDevice); + return result == 0; + } + catch (const std::exception& e) + { + std::cerr << "Error expanding RAIDZ " << raidzName << " in pool " << poolName << ": " << e.what() << std::endl; + return false; + } + } + + bool ZFSManager::isPoolRaidzExpanding(const std::string& poolName) + { + try + { + auto pool = m_lib.pool(poolName); + return pool.isRaidzExpanding(); + } + catch (const std::exception& e) + { + std::cerr << "Error checking RAIDZ expansion status for pool " << poolName << ": " << e.what() << std::endl; + return false; + } + } + + bool ZFSManager::setDirectIO(const std::string& filesystemName, bool enabled) + { + try + { + auto fs = m_lib.filesystem(filesystemName); + auto mode = enabled ? ZFileSystem::DirectIOMode::standard : ZFileSystem::DirectIOMode::disabled; + int result = fs.setDirectIOMode(mode); + return result == 0; + } + catch (const std::exception& e) + { + std::cerr << "Error setting Direct I/O for filesystem " << filesystemName << ": " << e.what() << std::endl; + return false; + } + } + + bool ZFSManager::getDedupStats(const std::string& poolName, std::uint64_t& tableSize, std::uint64_t& tableCached) + { + try + { + auto pool = m_lib.pool(poolName); + tableSize = pool.getDedupTableSize(); + tableCached = pool.getDedupCached(); + return true; + } + catch (const std::exception& e) + { + std::cerr << "Error getting deduplication stats for pool " << poolName << ": " << e.what() << std::endl; + return false; + } + } + + bool ZFSManager::setDedupQuota(const std::string& poolName, std::uint64_t quota) + { + try + { + auto pool = m_lib.pool(poolName); + int result = pool.setDedupTableQuota(quota); + return result == 0; + } + catch (const std::exception& e) + { + std::cerr << "Error setting deduplication quota for pool " << poolName << ": " << e.what() << std::endl; + return false; + } + } + +} // namespace zfs diff --git a/src/ZFSNVList.cpp b/src/ZFSNVList.cpp index 8bd47ad..1761204 100644 --- a/src/ZFSNVList.cpp +++ b/src/ZFSNVList.cpp @@ -113,6 +113,8 @@ std::string NVPair::name() const NVPAIRCONVERTTOP(bool, boolean_t, boolean_value) NVPAIRCONVERTTOV(bool, boolean_t, boolean) +NVPAIRCONVERTTOP(boolean_t, boolean_t, boolean_value) +NVPAIRCONVERTTOV(boolean_t, boolean_t, boolean) NVPAIRCONVERTTOP(double, double, double) NVPAIRCONVERTTOP(HighResTime, hrtime_t, hrtime) NVPAIRCONVERTTO(char, uchar_t, byte) @@ -443,6 +445,8 @@ namespace NVLIST_ADDP(bool, boolean_t, boolean_value) NVLIST_ADDV(bool, boolean_t, boolean) +NVLIST_ADDP(boolean_t, boolean_t, boolean_value) +NVLIST_ADDV(boolean_t, boolean_t, boolean) NVLIST_ADDP(double, double, double) NVLIST_ADD(char, uchar_t, byte) NVLIST_ADDP(char const *, char const *, string) diff --git a/src/ZFSStrings.cpp b/src/ZFSStrings.cpp index f0cc97f..806ff2e 100644 --- a/src/ZFSStrings.cpp +++ b/src/ZFSStrings.cpp @@ -1,5 +1,5 @@ // -// ZFSStrings.m +// ZFSStrings.cpp // ZetaWatch // // Created by Gerhard Röthlin on 2015.12.25. @@ -11,6 +11,11 @@ // options are described in the README file. // +#ifdef __APPLE__ +// Prevent macOS boolean_t redefinition +#define _MACH_ARM_BOOLEAN_H_ +#endif + #include "ZFSStrings.hpp" extern "C" @@ -84,14 +89,10 @@ char const * describe_zpool_status_t(uint64_t stat) return "rebuild scrub"; case ZPOOL_STATUS_NON_NATIVE_ASHIFT: return "non-native ashift"; -#if defined(ZFSW_HAS_ZPOOL_STATUS_COMPATIBILITY_ERR) case ZPOOL_STATUS_COMPATIBILITY_ERR: return "compatibility error"; -#endif -#if defined(ZFSW_HAS_ZPOOL_STATUS_INCOMPATIBLE_FEAT) case ZPOOL_STATUS_INCOMPATIBLE_FEAT: return u8"incompatible feature"; -#endif case ZPOOL_STATUS_OK: return "ok"; } @@ -162,14 +163,10 @@ char const * emoji_pool_status_t(uint64_t stat) return u8"♻️🧽"; case ZPOOL_STATUS_NON_NATIVE_ASHIFT: return u8"✅🐌"; -#if defined(ZFSW_HAS_ZPOOL_STATUS_COMPATIBILITY_ERR) case ZPOOL_STATUS_COMPATIBILITY_ERR: return u8"❌🔌"; -#endif -#if defined(ZFSW_HAS_ZPOOL_STATUS_INCOMPATIBLE_FEAT) case ZPOOL_STATUS_INCOMPATIBLE_FEAT: return u8"🔌🎛"; -#endif case ZPOOL_STATUS_OK: return u8"✅"; } diff --git a/src/ZFSStrings.mm b/src/ZFSStrings.mm index 909bcf8..0eaa6e7 100644 --- a/src/ZFSStrings.mm +++ b/src/ZFSStrings.mm @@ -1,5 +1,5 @@ // -// ZFSStrings.m +// ZFSStrings.mm // ZetaWatch // // Created by Gerhard Röthlin on 2015.12.25. @@ -11,9 +11,13 @@ // options are described in the README file. // +// For Objective-C++, we need to handle the boolean_t conflict +// Define boolean_t prevention macros before any includes +#define _MACH_ARM_BOOLEAN_H_ // Prevent macOS boolean_t definition + #include "ZFSStrings.hpp" -#import "Foundation/NSBundle.h" +#import namespace zfs { diff --git a/src/ZFSUtils.cpp b/src/ZFSUtils.cpp index dbd436a..be2c370 100644 --- a/src/ZFSUtils.cpp +++ b/src/ZFSUtils.cpp @@ -11,6 +11,11 @@ // options are described in the README file. // +#ifdef __APPLE__ +// Prevent macOS boolean_t redefinition +#define _MACH_ARM_BOOLEAN_H_ +#endif + #include "ZFSUtils.hpp" #include @@ -380,6 +385,56 @@ int ZFileSystem::clone(std::string const & newFSName) return zfs_clone(m_handle, newFSName.c_str(), nullptr); } +// Direct I/O support (OpenZFS 2.3.0+) +int ZFileSystem::setDirectIOMode(DirectIOMode mode) +{ + const char* modeStr; + switch (mode) + { + case DirectIOMode::disabled: + modeStr = "disabled"; + break; + case DirectIOMode::standard: + modeStr = "standard"; + break; + case DirectIOMode::always: + modeStr = "always"; + break; + default: + return EINVAL; + } + + return zfs_prop_set(m_handle, "direct", modeStr); +} + +ZFileSystem::DirectIOMode ZFileSystem::getDirectIOMode() const +{ + std::string modeStr = getPropString(m_handle, ZFS_PROP_DIRECT); + if (modeStr == "standard") + return DirectIOMode::standard; + else if (modeStr == "always") + return DirectIOMode::always; + else + return DirectIOMode::disabled; +} + +bool ZFileSystem::isDirectIOSupported() const +{ + // Check if the direct property exists (indicates support) + char value[ZFS_MAXPROPLEN]; + return zfs_prop_get(m_handle, ZFS_PROP_DIRECT, value, sizeof(value), + nullptr, nullptr, 0, B_FALSE) == 0; +} + +// Long filename support (OpenZFS 2.3.0+) +bool ZFileSystem::isLongNameSupported() const +{ + // Check if the longname property exists and is supported + char value[ZFS_MAXPROPLEN]; + return zfs_prop_get(m_handle, ZFS_PROP_LONGNAME, value, sizeof(value), + nullptr, nullptr, 0, B_FALSE) == 0; +} + int ZFileSystem::destroy(bool force) { if (auto error = unmount(force)) @@ -1034,6 +1089,82 @@ void ZPool::scrubStop() libHandle().throwLastError("scrub -s " + std::string(name())); } +int ZPool::raidzExpand(const std::string& raidz_name, const std::string& new_device) +{ + // Currently, RAIDZ expansion is done through zpool add command + // The raidz_name parameter is for future use when more granular control is available + (void)raidz_name; // Suppress unused parameter warning + + // Create nvlist for the new device + NVList new_dev_config(NVList::TakeOwnership{}); + new_dev_config.add(ZPOOL_CONFIG_PATH, new_device); + new_dev_config.add(ZPOOL_CONFIG_TYPE, VDEV_TYPE_DISK); + + // Use zpool_add to add the device to the RAIDZ vdev + // Note: This requires finding the RAIDZ vdev first and modifying its configuration + return zpool_add(m_handle, new_dev_config.toList(), B_FALSE); +} + +bool ZPool::getRaidzExpandStats(RaidzExpandStat& stats) const +{ + // Get RAIDZ expansion statistics from the pool + auto conf = config(); + NVList expand_stats; + if (conf.lookup(ZPOOL_CONFIG_RAIDZ_EXPAND_STATS, expand_stats)) + { + expand_stats.lookup("res_state", stats.res_state); + expand_stats.lookup("res_start_time", stats.res_start_time); + expand_stats.lookup("res_end_time", stats.res_end_time); + expand_stats.lookup("res_to_reflow", stats.res_to_reflow); + expand_stats.lookup("res_reflowed", stats.res_reflowed); + expand_stats.lookup("res_waiting_for_resilver", stats.res_waiting_for_resilver); + return true; + } + return false; +} + +bool ZPool::isRaidzExpanding() const +{ + auto conf = config(); + boolean_t expanding = B_FALSE; + conf.lookup(ZPOOL_CONFIG_RAIDZ_EXPANDING, expanding); + return expanding == B_TRUE; +} + +// Enhanced deduplication support (OpenZFS 2.3.0+) +std::uint64_t ZPool::getDedupTableSize() const +{ + return zpool_get_prop_int(m_handle, ZPOOL_PROP_DEDUP_TABLE_SIZE, nullptr); +} + +std::uint64_t ZPool::getDedupTableQuota() const +{ + return zpool_get_prop_int(m_handle, ZPOOL_PROP_DEDUP_TABLE_QUOTA, nullptr); +} + +std::uint64_t ZPool::getDedupCached() const +{ + return zpool_get_prop_int(m_handle, ZPOOL_PROP_DEDUPCACHED, nullptr); +} + +int ZPool::setDedupTableQuota(std::uint64_t quota) +{ + char quotaStr[32]; + snprintf(quotaStr, sizeof(quotaStr), "%llu", quota); + return zpool_set_prop(m_handle, zpool_prop_to_name(ZPOOL_PROP_DEDUP_TABLE_QUOTA), quotaStr); +} + +// Wait functionality for operations (OpenZFS 2.3.0+) +int ZPool::waitForRaidzExpansion() +{ + return zpool_wait(m_handle, ZPOOL_WAIT_RAIDZ_EXPAND); +} + +int ZPool::waitForScrub() +{ + return zpool_wait(m_handle, ZPOOL_WAIT_SCRUB); +} + zpool_handle_t * ZPool::handle() const { return m_handle; @@ -1297,7 +1428,7 @@ static std::vector import_with_args( "Error importing pool " + pair.name() + ": " + lib.lastError()); } ZPool importedPool = lib.pool(pair.name()); - r = zpool_enable_datasets(importedPool.handle(), nullptr, 0); + r = zpool_enable_datasets(importedPool.handle(), nullptr, 0, 0); pools.push_back(std::move(importedPool)); if (r) {