From 133ab239566445018226cb7a7d770024d7c7e933 Mon Sep 17 00:00:00 2001 From: Lanzheng Liu Date: Fri, 28 Aug 2026 10:22:09 +0800 Subject: [PATCH 1/9] refactor: use PhotonLibOS cache implementations Signed-off-by: Lanzheng Liu --- .gitmodules | 4 - CMake/Finde2fs.cmake | 12 +- CMake/Findphoton.cmake | 44 +- CMakeLists.txt | 8 +- src/api_server.cpp | 2 +- src/exporter_server.h | 4 +- src/image_service.cpp | 24 +- src/image_service.h | 2 +- src/overlaybd/CMakeLists.txt | 4 +- src/overlaybd/base64.h | 101 ---- src/overlaybd/cache/CMakeLists.txt | 22 - src/overlaybd/cache/cache.cpp | 113 ---- src/overlaybd/cache/cache.h | 134 ----- src/overlaybd/cache/cached_fs.cpp | 446 -------------- .../cache/download_cache/CMakeLists.txt | 6 - .../cache/download_cache/download_cache.cpp | 372 ------------ src/overlaybd/cache/forwardcfs.h | 64 -- .../cache/full_file_cache/CMakeLists.txt | 6 - .../cache/full_file_cache/cache_pool.cpp | 315 ---------- .../cache/full_file_cache/cache_pool.h | 114 ---- .../cache/full_file_cache/cache_store.cpp | 202 ------- .../cache/full_file_cache/cache_store.h | 74 --- src/overlaybd/cache/ocf_cache/CMakeLists.txt | 21 - .../cache/ocf_cache/ease_bindings/ctx.cpp | 272 --------- .../cache/ocf_cache/ease_bindings/ctx.h | 77 --- .../ocf_cache/ease_bindings/env/ocf_env.cpp | 355 ----------- .../ocf_cache/ease_bindings/env/ocf_env.h | 504 ---------------- .../ease_bindings/env/ocf_env_headers.h | 17 - .../ease_bindings/env/ocf_env_list.h | 163 ----- .../ease_bindings/env/utils_mpool.cpp | 150 ----- .../ocf_cache/ease_bindings/env/utils_mpool.h | 86 --- .../ocf_cache/ease_bindings/provider.cpp | 327 ---------- .../cache/ocf_cache/ease_bindings/provider.h | 78 --- .../cache/ocf_cache/ease_bindings/queue.cpp | 70 --- .../cache/ocf_cache/ease_bindings/queue.h | 9 - .../cache/ocf_cache/ease_bindings/volume.cpp | 210 ------- .../cache/ocf_cache/ease_bindings/volume.h | 20 - src/overlaybd/cache/ocf_cache/include/ocf | 1 - src/overlaybd/cache/ocf_cache/ocf | 1 - src/overlaybd/cache/ocf_cache/ocf_cache.cpp | 295 --------- .../cache/ocf_cache/ocf_namespace.cpp | 178 ------ src/overlaybd/cache/ocf_cache/ocf_namespace.h | 47 -- .../cache/ocf_cache/test/CMakeLists.txt | 21 - src/overlaybd/cache/ocf_cache/test/flags.conf | 16 - .../cache/ocf_cache/test/ocf_perf_test.cpp | 508 ---------------- src/overlaybd/cache/policy/lru.h | 148 ----- src/overlaybd/cache/pool_store.h | 290 --------- src/overlaybd/cache/store.cpp | 427 -------------- src/overlaybd/cache/test/CMakeLists.txt | 14 - src/overlaybd/cache/test/cache_test.cpp | 558 ------------------ src/overlaybd/cache/test/random_generator.h | 58 -- src/overlaybd/gzindex/test/CMakeLists.txt | 2 +- src/overlaybd/gzindex/test/test.cpp | 6 +- .../{cache => }/gzip_cache/CMakeLists.txt | 7 +- .../{cache => }/gzip_cache/cached_fs.cpp | 26 +- .../{cache => }/gzip_cache/cached_fs.h | 0 src/overlaybd/registryfs/registryfs.cpp | 10 +- src/overlaybd/registryfs/registryfs_v2.cpp | 5 +- .../stream_convertor/stream_conv.cpp | 2 +- src/overlaybd/zfile/compressor.cpp | 52 +- src/test/image_service_test.cpp | 6 +- src/test/simple_credsrv_test.cpp | 2 +- 62 files changed, 142 insertions(+), 6970 deletions(-) delete mode 100644 src/overlaybd/base64.h delete mode 100644 src/overlaybd/cache/CMakeLists.txt delete mode 100644 src/overlaybd/cache/cache.cpp delete mode 100644 src/overlaybd/cache/cache.h delete mode 100644 src/overlaybd/cache/cached_fs.cpp delete mode 100644 src/overlaybd/cache/download_cache/CMakeLists.txt delete mode 100644 src/overlaybd/cache/download_cache/download_cache.cpp delete mode 100644 src/overlaybd/cache/forwardcfs.h delete mode 100644 src/overlaybd/cache/full_file_cache/CMakeLists.txt delete mode 100644 src/overlaybd/cache/full_file_cache/cache_pool.cpp delete mode 100644 src/overlaybd/cache/full_file_cache/cache_pool.h delete mode 100644 src/overlaybd/cache/full_file_cache/cache_store.cpp delete mode 100644 src/overlaybd/cache/full_file_cache/cache_store.h delete mode 100644 src/overlaybd/cache/ocf_cache/CMakeLists.txt delete mode 100644 src/overlaybd/cache/ocf_cache/ease_bindings/ctx.cpp delete mode 100644 src/overlaybd/cache/ocf_cache/ease_bindings/ctx.h delete mode 100644 src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env.cpp delete mode 100644 src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env.h delete mode 100644 src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env_headers.h delete mode 100644 src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env_list.h delete mode 100644 src/overlaybd/cache/ocf_cache/ease_bindings/env/utils_mpool.cpp delete mode 100644 src/overlaybd/cache/ocf_cache/ease_bindings/env/utils_mpool.h delete mode 100644 src/overlaybd/cache/ocf_cache/ease_bindings/provider.cpp delete mode 100644 src/overlaybd/cache/ocf_cache/ease_bindings/provider.h delete mode 100644 src/overlaybd/cache/ocf_cache/ease_bindings/queue.cpp delete mode 100644 src/overlaybd/cache/ocf_cache/ease_bindings/queue.h delete mode 100644 src/overlaybd/cache/ocf_cache/ease_bindings/volume.cpp delete mode 100644 src/overlaybd/cache/ocf_cache/ease_bindings/volume.h delete mode 120000 src/overlaybd/cache/ocf_cache/include/ocf delete mode 160000 src/overlaybd/cache/ocf_cache/ocf delete mode 100644 src/overlaybd/cache/ocf_cache/ocf_cache.cpp delete mode 100644 src/overlaybd/cache/ocf_cache/ocf_namespace.cpp delete mode 100644 src/overlaybd/cache/ocf_cache/ocf_namespace.h delete mode 100644 src/overlaybd/cache/ocf_cache/test/CMakeLists.txt delete mode 100644 src/overlaybd/cache/ocf_cache/test/flags.conf delete mode 100644 src/overlaybd/cache/ocf_cache/test/ocf_perf_test.cpp delete mode 100644 src/overlaybd/cache/policy/lru.h delete mode 100644 src/overlaybd/cache/pool_store.h delete mode 100644 src/overlaybd/cache/store.cpp delete mode 100644 src/overlaybd/cache/test/CMakeLists.txt delete mode 100644 src/overlaybd/cache/test/cache_test.cpp delete mode 100644 src/overlaybd/cache/test/random_generator.h rename src/overlaybd/{cache => }/gzip_cache/CMakeLists.txt (67%) rename src/overlaybd/{cache => }/gzip_cache/cached_fs.cpp (75%) rename src/overlaybd/{cache => }/gzip_cache/cached_fs.h (100%) diff --git a/.gitmodules b/.gitmodules index cc9c9aab..2fbf43c5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,3 @@ -[submodule "src/overlaybd/cache/ocf_cache/ocf"] - path = src/overlaybd/cache/ocf_cache/ocf - url = https://github.com/Open-CAS/ocf - branch = v21.6.4 [submodule "src/overlaybd/zfile/thirdparty/DML"] path = src/overlaybd/zfile/thirdparty/DML url = https://github.com/intel/DML.git diff --git a/CMake/Finde2fs.cmake b/CMake/Finde2fs.cmake index a36f51f3..5b71e0c1 100644 --- a/CMake/Finde2fs.cmake +++ b/CMake/Finde2fs.cmake @@ -1,3 +1,5 @@ +include(FindPackageHandleStandardArgs) + if(NOT ORIGIN_EXT2FS) message("Add and build standalone libext2fs") include(FetchContent) @@ -20,11 +22,17 @@ if(NOT ORIGIN_EXT2FS) # upstream build.sh hardcodes `CFLAGS="-fPIC -O3"` on the configure # line, so setting CFLAGS via the environment is ignored -- we patch # build.sh in place instead. `-std=gnu11` is supported by every - # compiler used across the release matrix (GCC 7+). + # compiler used across the release matrix (GCC 8+). add_custom_command( OUTPUT ${LIBEXT2FS_INSTALL_DIR}/lib + BYPRODUCTS + ${E2FS_INSTALL_LIB_DIR}/libext2fs.so + ${e2fsprogs_SOURCE_DIR}/build/lib/libcom_err.a + ${E2FS_RESIZE_DIR}/resize2fs.o + ${E2FS_RESIZE_DIR}/extent.o + ${E2FS_RESIZE_DIR}/resource_track.o WORKING_DIRECTORY ${e2fsprogs_SOURCE_DIR} - COMMAND chmod 755 build.sh && sed -i 's|CFLAGS="-fPIC -O3"|CFLAGS="-fPIC -O3 -std=gnu11"|' build.sh && ./build.sh + COMMAND chmod 755 build.sh && sed -i 's|CFLAGS="-fPIC -O3"|CFLAGS="-fPIC -O3 -std=gnu11"|' build.sh && ${CMAKE_COMMAND} -E env CC=${CMAKE_C_COMPILER} ./build.sh ) add_custom_target(libext2fs_build DEPENDS ${LIBEXT2FS_INSTALL_DIR}/lib) endif() diff --git a/CMake/Findphoton.cmake b/CMake/Findphoton.cmake index 1c196f75..b71c32a9 100644 --- a/CMake/Findphoton.cmake +++ b/CMake/Findphoton.cmake @@ -1,16 +1,19 @@ include(FetchContent) set(FETCHCONTENT_QUIET false) -set(PHOTON_ENABLE_EXTFS ON) +set(PHOTON_ENABLE_EXTFS ON CACHE BOOL "Build Photon extfs support" FORCE) +set(PHOTON_BUILD_OCF_CACHE ON CACHE BOOL "Build Photon OCF cache support" FORCE) if(NOT ORIGIN_EXT2FS) - set(PHOTON_ENABLE_RESIZE ON) + set(PHOTON_ENABLE_RESIZE ON CACHE BOOL "Build Photon extfs resize support" FORCE) add_definitions(-DPHOTON_ENABLE_RESIZE) +else() + set(PHOTON_ENABLE_RESIZE OFF CACHE BOOL "Build Photon extfs resize support" FORCE) endif() FetchContent_Declare( photon GIT_REPOSITORY https://github.com/alibaba/PhotonLibOS.git - GIT_TAG 0178d14499d8639759a81e32ad58da5226df5e9b + GIT_TAG v0.9.6 ) if(BUILD_TESTING) @@ -21,6 +24,41 @@ else() FetchContent_MakeAvailable(photon) endif() +# Photon v0.9.5 exposes the OCF headers as , while the OCF checkout +# contains them directly in its inc/ directory. When Photon is consumed via +# FetchContent that include layout is not created, so make it explicit for the +# embedded OCF target. +if(TARGET ocf_lib AND NOT TARGET photon_cache_lib) + FetchContent_GetProperties(ocf_lib SOURCE_DIR PHOTON_OCF_SOURCE_DIR) + set(PHOTON_OCF_INCLUDE_DIR ${CMAKE_BINARY_DIR}/photon-ocf-include) + file(MAKE_DIRECTORY ${PHOTON_OCF_INCLUDE_DIR}) + file(REMOVE ${PHOTON_OCF_INCLUDE_DIR}/ocf) + file(CREATE_LINK ${PHOTON_OCF_SOURCE_DIR}/inc ${PHOTON_OCF_INCLUDE_DIR}/ocf SYMBOLIC) + target_include_directories(ocf_lib PUBLIC ${PHOTON_OCF_INCLUDE_DIR}) + + # The OCF cache target declares its environment adapter but omits the + # implementations from its source list when embedded as a dependency. + target_sources(ocf_cache_lib PRIVATE + ${photon_SOURCE_DIR}/fs/cache/ocf_cache/photon_bindings/env/ocf_env.cpp + ${photon_SOURCE_DIR}/fs/cache/ocf_cache/photon_bindings/env/utils_mpool.cpp + ) + + # These archives have circular static dependencies. Keep the rescan group + # on the consumer side so that cache users link reliably on GNU ld. + add_library(photon_cache_lib INTERFACE) + target_link_libraries(photon_cache_lib INTERFACE + "-Wl,--start-group" + "$" + "$" + "$" + "-Wl,--end-group" + ) + # Photon users do not all depend on OverlayBD's gzip-cache target. Export + # the OCF rescan group from photon_static itself so tools such as + # overlaybd-commit receive the required OCF environment implementation. + target_link_libraries(photon_static INTERFACE photon_cache_lib) +endif() + if (BUILD_CURL_FROM_SOURCE) find_package(OpenSSL REQUIRED) find_package(CURL REQUIRED) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf5852a7..98a753e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,13 +23,6 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/CMake") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fpic -Wall -Werror=sign-compare") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpic -Wall -Werror=sign-compare -DOVERLAYBD_VER=${OBD_VER}") -# GCC 13+ (default on Azure Linux 4.0 with GCC 15) no longer transitively -# includes from other standard headers. Force-include it for every -# C++ translation unit so that fixed-width integer types (uint64_t, etc.) used -# in third-party headers (e.g. photon's fs/path.h at the pinned commit) resolve -# without needing to upgrade those pins. C sources (e.g. OCF) are unaffected. -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include cstdint") - if ((CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64) OR (CMAKE_SYSTEM_PROCESSOR STREQUAL arm64)) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv8-a+crc -fsigned-char -fno-stack-protector -fomit-frame-pointer") endif () @@ -54,6 +47,7 @@ option(BUILD_CURL_FROM_SOURCE "Compile static libcurl" off) option(BUILD_STREAM_CONVERTOR "Build the stream convertor" on) option(ORIGIN_EXT2FS "Use original libext2fs" off) +find_package(e2fs REQUIRED) find_package(photon REQUIRED) find_package(tcmu REQUIRED) diff --git a/src/api_server.cpp b/src/api_server.cpp index 37aaa8d6..1e585350 100644 --- a/src/api_server.cpp +++ b/src/api_server.cpp @@ -152,7 +152,7 @@ struct ApiServer { host.resize(pos); } tcpserver = photon::net::new_tcp_socket_server(); - tcpserver->setsockopt(SOL_SOCKET, SO_REUSEPORT, 1); + tcpserver->setsockopt(SOL_SOCKET, SO_REUSEPORT, 1); if(tcpserver->bind(url.port(), photon::net::IPAddr(host.c_str())) < 0) LOG_ERRNO_RETURN(0, -1, "Failed to bind api server port `", url.port()); if(tcpserver->listen() < 0) diff --git a/src/exporter_server.h b/src/exporter_server.h index 80d0b6e1..50670b1b 100644 --- a/src/exporter_server.h +++ b/src/exporter_server.h @@ -53,7 +53,7 @@ struct ExporterServer { ExporterServer(ImageConfigNS::GlobalConfig &config, OverlayBDMetric *metrics) { tcpserver = photon::net::new_tcp_socket_server(); - tcpserver->setsockopt(SOL_SOCKET, SO_REUSEPORT, 1); + tcpserver->setsockopt(SOL_SOCKET, SO_REUSEPORT, 1); if (tcpserver->bind(config.exporterConfig().port()) < 0) LOG_ERRNO_RETURN(0, , "Failed to bind exporter port `", config.exporterConfig().port()); @@ -72,4 +72,4 @@ struct ExporterServer { delete tcpserver; delete httpserver; } -}; \ No newline at end of file +}; diff --git a/src/image_service.cpp b/src/image_service.cpp index 43c3b9f4..97c4cb75 100644 --- a/src/image_service.cpp +++ b/src/image_service.cpp @@ -25,11 +25,11 @@ #include #include #include +#include #include -#include "overlaybd/cache/cache.h" +#include #include "overlaybd/registryfs/registryfs.h" #include "overlaybd/zfile/zfile.h" -#include "overlaybd/base64.h" #include #include #include @@ -108,7 +108,11 @@ int parse_auths(const ConfigUtils::Document &auths, const std::string &remote_pa continue; if (iter.value.HasMember("auth")) { - auto token = base64_decode(iter.value["auth"].GetString()); + std::string token; + if (!photon::net::Base64Decode(iter.value["auth"].GetString(), token)) { + LOG_ERROR("invalid base64 auth for: `", addr); + continue; + } auto p = token.find(":"); if (p == std::string::npos) { LOG_ERROR("invalid base64 auth, no ':' found: `", token); @@ -455,10 +459,6 @@ int ImageService::init() { global_fs.srcfs = global_fs.underlay_registryfs; } - if (global_conf.enableThread() == true && cache_type == "file") { - LOG_ERROR_RETURN(0, -1, "multi-thread has not been valid for file cache"); - } - global_fs.io_alloc = new IOAlloc; if (cache_type == "file") { @@ -468,7 +468,7 @@ int ImageService::init() { LOG_ERROR_RETURN(0, -1, "new_localfs_adaptor for ` failed", cache_dir.c_str()); } // file cache will delete its src_fs automatically when destructed - global_fs.cached_fs = FileSystem::new_full_file_cached_fs( + global_fs.cached_fs = photon::fs::new_full_file_cached_fs( global_fs.srcfs, registry_cache_fs, refill_size, cache_size_GB, 10000000, (uint64_t)1048576 * 1024, global_fs.io_alloc, 0, {nullptr, &cache_fn_trans_sha256}); @@ -498,10 +498,12 @@ int ImageService::init() { } global_fs.media_file = media_file; - global_fs.cached_fs = FileSystem::new_ocf_cached_fs(global_fs.srcfs, namespace_fs, block_size, refill_size, - media_file, reload_media, global_fs.io_alloc); + global_fs.cached_fs = photon::fs::new_ocf_cached_fs( + global_fs.srcfs, namespace_fs, block_size, refill_size, media_file, reload_media, + global_fs.io_alloc); } else if (cache_type == "download") { - global_fs.cached_fs = FileSystem::new_download_cached_fs(global_fs.srcfs, 4096, refill_size, global_fs.io_alloc); + global_fs.cached_fs = photon::fs::new_persistent_cached_fs( + global_fs.srcfs, 4096, refill_size, global_fs.io_alloc); } else { LOG_ERROR_RETURN(0, -1, "cache type invalid"); } diff --git a/src/image_service.h b/src/image_service.h index a2278e23..deeb1424 100644 --- a/src/image_service.h +++ b/src/image_service.h @@ -19,7 +19,7 @@ #include #include "config.h" #include "exporter_server.h" -#include "overlaybd/cache/gzip_cache/cached_fs.h" +#include "overlaybd/gzip_cache/cached_fs.h" #include #include #include diff --git a/src/overlaybd/CMakeLists.txt b/src/overlaybd/CMakeLists.txt index af095b79..1efc09ce 100644 --- a/src/overlaybd/CMakeLists.txt +++ b/src/overlaybd/CMakeLists.txt @@ -2,7 +2,7 @@ add_subdirectory(registryfs) add_subdirectory(lsmt) add_subdirectory(zfile) add_subdirectory(zstd) -add_subdirectory(cache) +add_subdirectory(gzip_cache) add_subdirectory(tar) add_subdirectory(gzip) add_subdirectory(gzindex) @@ -21,7 +21,7 @@ target_link_libraries(overlaybd_lib INTERFACE lsmt_lib zfile_lib zstd_lib - cache_lib + gzip_cache_lib tar_lib gzip_lib gzindex_lib diff --git a/src/overlaybd/base64.h b/src/overlaybd/base64.h deleted file mode 100644 index b32a0258..00000000 --- a/src/overlaybd/base64.h +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once - -#ifndef _BASE64_H_ -#define _BASE64_H_ - -#include -#include -typedef unsigned char BYTE; - -static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - -static inline bool is_base64(BYTE c) { - return (isalnum(c) || (c == '+') || (c == '/')); -} - -static inline std::string base64_encode(BYTE const *buf, unsigned int bufLen) { - std::string ret; - int i = 0; - int j = 0; - BYTE char_array_3[3]; - BYTE char_array_4[4]; - - while (bufLen--) { - char_array_3[i++] = *(buf++); - if (i == 3) { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for (i = 0; (i < 4); i++) - ret += base64_chars[char_array_4[i]]; - i = 0; - } - } - - if (i) { - for (j = i; j < 3; j++) - char_array_3[j] = '\0'; - - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for (j = 0; (j < i + 1); j++) - ret += base64_chars[char_array_4[j]]; - - while ((i++ < 3)) - ret += '='; - } - - return ret; -} - -static inline std::string base64_decode(std::string const &encoded_string) { - int in_len = encoded_string.size(); - int i = 0; - int j = 0; - int in_ = 0; - BYTE char_array_4[4], char_array_3[3]; - std::string ret = ""; - - while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { - char_array_4[i++] = encoded_string[in_]; - in_++; - if (i == 4) { - for (i = 0; i < 4; i++) - char_array_4[i] = base64_chars.find(char_array_4[i]); - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (i = 0; (i < 3); i++) - ret += char_array_3[i]; - i = 0; - } - } - - if (i) { - for (j = i; j < 4; j++) - char_array_4[j] = 0; - - for (j = 0; j < 4; j++) - char_array_4[j] = base64_chars.find(char_array_4[j]); - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (j = 0; (j < i - 1); j++) - ret += char_array_3[j]; - } - - return ret; -} - -#endif diff --git a/src/overlaybd/cache/CMakeLists.txt b/src/overlaybd/cache/CMakeLists.txt deleted file mode 100644 index 760567be..00000000 --- a/src/overlaybd/cache/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -add_subdirectory(full_file_cache) -add_subdirectory(ocf_cache) -add_subdirectory(download_cache) -add_subdirectory(gzip_cache) - -file(GLOB SRC_CACHE "*.cpp") - -add_library(cache_lib STATIC ${SRC_CACHE}) -target_link_libraries(cache_lib - photon_static - full_file_cache_lib - ocf_cache_lib - download_cache_lib - gzip_cache_lib -) -target_include_directories(cache_lib PUBLIC - ${PHOTON_INCLUDE_DIR} -) - -if(BUILD_TESTING) - add_subdirectory(test) -endif() diff --git a/src/overlaybd/cache/cache.cpp b/src/overlaybd/cache/cache.cpp deleted file mode 100644 index 46c2ba09..00000000 --- a/src/overlaybd/cache/cache.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - Copyright The Overlaybd Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -#include "cache.h" -#include -#include -#include -#include -#include -#include - -#include "full_file_cache/cache_pool.h" - -namespace FileSystem { -using namespace photon::fs; -ICachedFileSystem *new_full_file_cached_fs(IFileSystem *srcFs, IFileSystem *mediaFs, - uint64_t refillUnit, uint64_t capacityInGB, - uint64_t periodInUs, uint64_t diskAvailInBytes, - IOAlloc *allocator, int quotaDirLevel, - CacheFnTransFunc fn_trans_func) { - if (refillUnit % 4096 != 0 || !is_power_of_2(refillUnit)) { - LOG_ERROR_RETURN(EINVAL, nullptr, "refill Unit need to be aligned to 4KB and power of 2") - } - if (!allocator) { - allocator = new IOAlloc; - } - Cache::FileCachePool *pool = nullptr; - pool = - new ::Cache::FileCachePool(mediaFs, capacityInGB, periodInUs, diskAvailInBytes, refillUnit); - pool->Init(); - return new_cached_fs(srcFs, pool, 4096, allocator, fn_trans_func); -} - -using OC = ObjectCache; -ICachePool::ICachePool(uint32_t pool_size, uint32_t max_refilling, uint32_t refilling_threshold) - : m_stores(new OC(10UL * 1000 * 1000)), m_max_refilling(max_refilling), - m_refilling_threshold(refilling_threshold) { - if (pool_size != 0) { - m_thread_pool = photon::new_thread_pool(pool_size, 128 * 1024UL); - m_vcpu = photon::get_vcpu(); - }; -} - -#define cast(x) static_cast(x) -ICachePool::~ICachePool() { - stores_clear(); - delete cast(m_stores); -} - -void ICachePool::stores_clear() { - if (m_thread_pool) { - auto pool = static_cast(m_thread_pool); - m_thread_pool = nullptr; - photon::delete_thread_pool(pool); - } - cast(m_stores)->clear(); -} - -ICacheStore *ICachePool::open(std::string_view filename, int flags, mode_t mode) { - char store_name[4096]; - std::string x(filename); - auto len = this->fn_trans_func(filename, store_name, sizeof(store_name)); - std::string_view store_sv = len ? std::string_view(store_name, len) : filename; - auto ctor = [&]() -> ICacheStore * { - auto cache_store = this->do_open(store_sv, flags, mode); - if (nullptr == cache_store) { - LOG_ERRNO_RETURN(0, nullptr, "fileCachePool_ open file failed, name : `", - filename.data()); - } - auto it = cast(m_stores)->find(store_sv); - std::string_view map_key = (*it)->key(); - cache_store->set_store_key(map_key); - cache_store->set_src_name(filename); - cache_store->set_pool(this); - struct stat st; - SET_STRUCT_STAT(&st); - st.st_size = -1; - if (cache_store->fstat(&st) == 0) { - cache_store->set_cached_size(st.st_size); - cache_store->set_actual_size(st.st_size); - } - cache_store->set_open_flags(flags); - return cache_store; - }; - auto store = cast(m_stores)->acquire(store_sv, ctor); - if (store) { - auto cnt = store->ref_.fetch_add(1, std::memory_order_relaxed); - if (cnt) - cast(m_stores)->release(store_sv); - } - return store; -} - -void ICachePool::set_trans_func(CacheFnTransFunc fn_trans_func) { - this->fn_trans_func = fn_trans_func; -} - -int ICachePool::store_release(ICacheStore *store) { - return cast(m_stores)->release(store->get_store_key()); -} -} // namespace FileSystem diff --git a/src/overlaybd/cache/cache.h b/src/overlaybd/cache/cache.h deleted file mode 100644 index 06b8b38b..00000000 --- a/src/overlaybd/cache/cache.h +++ /dev/null @@ -1,134 +0,0 @@ -/* - Copyright The Overlaybd Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -#pragma once -#include -#include -#include -#include -#include -#include "pool_store.h" - -#define O_WRITE_THROUGH 0x01000000 // write backing store and cache -#define O_WRITE_AROUND 0x02000000 // write backing store only, default -#define O_WRITE_BACK 0x04000000 // write cache and async flush to backing store, not support yet -#define O_CACHE_ONLY 0x08000000 // write cache only -#define O_DIRECT_LOCAL 0x20000000 // read local -#define O_MMAP_READ 0x00800000 // mmap like read - -#define RW_V2_HIGH_PRIORITY 0x00000001 // preadv2/pwritev2 high priority cache data -#define RW_V2_PROMOTE 0x00000002 // preadv2 promote flag -#define RW_V2_CACHE_ONLY 0x00000004 // preadv2 cache only flag -#define RW_V2_TO_BUFFER_WITHOUT_SYNC \ - 0x00000010 // pwritev2 to buffered accessor file's buffer without sync -#define RW_V2_MEMORY_ONLY 0x00000020 // pwritev2 memory cache only - -#define IS_STRUCT_STAT_SETTED(x) ((*(uint64_t *)x) == 0xF19A336DB7CA28E7ull) -#define SET_STRUCT_STAT(x) ((*(uint64_t *)x) = 0xF19A336DB7CA28E7ull) - -const int IOCTL_GET_PAGE_SIZE = 161; - -namespace Cache { -namespace Block { -struct Options; -} -} // namespace Cache -struct IOAlloc; -namespace FileSystem { -class ICachedFileSystem : public photon::fs::IFileSystem { -public: - // get the source file system - UNIMPLEMENTED_POINTER(IFileSystem *get_source()); - - // set the source file system - UNIMPLEMENTED(int set_source(IFileSystem *src)); - - UNIMPLEMENTED_POINTER(ICachePool *get_pool()); - - UNIMPLEMENTED(int set_pool(ICachePool *pool)); -}; - -class ICachedFile : public photon::fs::IFile { -public: - // get the source file system - UNIMPLEMENTED_POINTER(IFile *get_source()); - - // set the source file system, and enable `auto_refill` - UNIMPLEMENTED(int set_source(IFile *src)); - - UNIMPLEMENTED_POINTER(ICacheStore *get_store()); - - // client refill for an ICachedFile (without a source!) - // is implemented as pwrite(), usually aligned - ssize_t refill(const void *buf, size_t count, off_t offset) { - return pwrite(buf, count, offset); - } - ssize_t refill(const struct iovec *iov, int iovcnt, off_t offset) { - return pwritev(iov, iovcnt, offset); - } - ssize_t refill(struct iovec *iov, int iovcnt, off_t offset) { - return pwritev(iov, iovcnt, offset); - } - - // refilling a range without providing data, is treated as prefetching - ssize_t refill(off_t offset, size_t count) { - return fadvise(offset, count, POSIX_FADV_WILLNEED); - } - - // query cached extents is implemented as fiemap() - UNIMPLEMENTED(int query(off_t offset, size_t count)) - - // eviction is implemented as trim() - ssize_t evict(off_t offset, size_t count) { - return trim(offset, count); - } - - int vioctl(int request, va_list args) override { - auto src = get_source(); - if (src) - return src->vioctl(request, args); - return -1; - } -}; - -extern "C" { -ICachedFileSystem *new_cached_fs(photon::fs::IFileSystem *src, ICachePool *pool, uint64_t pageSize, - IOAlloc *allocator, CacheFnTransFunc fn_trans_func = nullptr); - -ICachedFile *new_cached_file(ICacheStore *store, uint64_t pageSize, photon::fs::IFileSystem *fs); - -ICachedFileSystem *new_full_file_cached_fs(photon::fs::IFileSystem *srcFs, - photon::fs::IFileSystem *media_fs, uint64_t refillUnit, - uint64_t capacityInGB, uint64_t periodInUs, - uint64_t diskAvailInBytes, IOAlloc *allocator, - int quotaDirLevel, - CacheFnTransFunc fn_trans_func = nullptr); - -/** - * @param blk_size The proper size for cache metadata and IO efficiency. Large writes to cache media - * will be split into blk_size. Reads and small writes are not affected. - * @param prefetch_unit Controls the expand prefetch size from src file. 0 means to disable this - * feature. - */ -photon::fs::IFileSystem *new_ocf_cached_fs(photon::fs::IFileSystem *src_fs, - photon::fs::IFileSystem *namespace_fs, size_t blk_size, - size_t prefetch_unit, photon::fs::IFile *media_file, - bool reload_media, IOAlloc *io_alloc); - -photon::fs::IFileSystem *new_download_cached_fs(photon::fs::IFileSystem *src_fs, size_t blk_size, - size_t refill_size, IOAlloc *io_alloc); -} // extern "C" - -} // namespace FileSystem diff --git a/src/overlaybd/cache/cached_fs.cpp b/src/overlaybd/cache/cached_fs.cpp deleted file mode 100644 index 6d775f9b..00000000 --- a/src/overlaybd/cache/cached_fs.cpp +++ /dev/null @@ -1,446 +0,0 @@ -/* - Copyright The Overlaybd Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "cache.h" -#include "photon/common/alog.h" -#include "photon/common/io-alloc.h" -#include "photon/common/iovector.h" -#include "photon/common/string_view.h" -#include "photon/fs/filesystem.h" -#include "photon/fs/range-split.h" -#include "pool_store.h" - -namespace Cache { - -using namespace FileSystem; -using namespace photon::fs; - -const uint64_t kMaxPrefetchSize = 32 * 1024 * 1024; - -class CachedFs : public ICachedFileSystem, public IFileSystemXAttr { -public: - CachedFs(IFileSystem *srcFs, ICachePool *fileCachePool, size_t pageSize, IOAlloc *allocator, - CacheFnTransFunc fn_trans_func) - : srcFs_(srcFs), fileCachePool_(fileCachePool), pageSize_(pageSize), allocator_(allocator), - xattrFs_(dynamic_cast(srcFs)) { - fileCachePool_->set_trans_func(fn_trans_func); - } - - ~CachedFs() { - delete fileCachePool_; - } - - IFile *open(const char *pathname, int flags, mode_t mode) override { - int cflags = (flags & (O_WRITE_THROUGH | O_CACHE_ONLY | O_WRITE_BACK)); - auto cache_store = fileCachePool_->open(pathname, O_RDWR | O_CREAT | cflags, 0644); - if (nullptr == cache_store) { - LOG_ERRNO_RETURN(0, nullptr, "fileCachePool_ open file failed, name : `", pathname) - } - - cache_store->set_src_fs(srcFs_); - cache_store->set_page_size(pageSize_); - cache_store->set_allocator(allocator_); - auto ret = new_cached_file(cache_store, pageSize_, this); - if (ret == nullptr) { // if create file is failed - // cache_store must be release, or will leak - cache_store->release(); - } - return ret; - } - - IFile *open(const char *pathname, int flags) override { - return open(pathname, flags, 0); // mode and flags are meaningless in RoCacheFS::open(2)(3) - } - - int mkdir(const char *pathname, mode_t mode) override { - return srcFs_ ? srcFs_->mkdir(pathname, mode) : -1; - } - - int rmdir(const char *pathname) override { - return srcFs_ ? srcFs_->rmdir(pathname) : -1; - } - - ssize_t readlink(const char *path, char *buf, size_t bufsiz) override { - return srcFs_ ? srcFs_->readlink(path, buf, bufsiz) : -1; - } - - int rename(const char *oldname, const char *newname) override { - return fileCachePool_->rename(oldname, newname); - } - - int unlink(const char *filename) override { - auto cache_store = fileCachePool_->open(filename, O_RDONLY, 0); - if (cache_store != nullptr) { - cache_store->set_cached_size(0); - cache_store->set_actual_size(0); - cache_store->release(); - } - auto ret = fileCachePool_->evict(filename); - return srcFs_ ? srcFs_->unlink(filename) : ret; - } - - int statfs(const char *path, struct statfs *buf) override { - return srcFs_ ? srcFs_->statfs(path, buf) : -1; - } - int statvfs(const char *path, struct statvfs *buf) override { - return srcFs_ ? srcFs_->statvfs(path, buf) : -1; - } - int stat(const char *path, struct stat *buf) override { - return srcFs_ ? srcFs_->stat(path, buf) : -1; - } - int lstat(const char *path, struct stat *buf) override { - return srcFs_ ? srcFs_->lstat(path, buf) : -1; - } - - int access(const char *pathname, int mode) override { - if (srcFs_) - return srcFs_->access(pathname, mode); - auto cache_store = fileCachePool_->open(pathname, O_RDONLY, 0); - if (cache_store == nullptr) - return -1; - cache_store->release(); - return 0; - } - - DIR *opendir(const char *name) override { - return srcFs_ ? srcFs_->opendir(name) : nullptr; - } - - IFileSystem *get_source() override { - return srcFs_; - } - - int set_source(IFileSystem *src) override { - srcFs_ = src; - return 0; - } - - ICachePool *get_pool() override { - return fileCachePool_; - } - - int set_pool(ICachePool *pool) override { - fileCachePool_ = pool; - return 0; - } - - ssize_t getxattr(const char *path, const char *name, void *value, size_t size) override { - return xattrFs_ ? xattrFs_->getxattr(path, name, value, size) : -1; - } - - virtual ssize_t lgetxattr(const char *path, const char *name, void *value, - size_t size) override { - return xattrFs_ ? xattrFs_->lgetxattr(path, name, value, size) : -1; - } - - ssize_t listxattr(const char *path, char *list, size_t size) override { - return xattrFs_ ? xattrFs_->listxattr(path, list, size) : -1; - } - - ssize_t llistxattr(const char *path, char *list, size_t size) override { - return xattrFs_ ? xattrFs_->llistxattr(path, list, size) : -1; - } - - int setxattr(const char *path, const char *name, const void *value, size_t size, - int flags) override { - return xattrFs_ ? xattrFs_->setxattr(path, name, value, size, flags) : -1; - } - - int lsetxattr(const char *path, const char *name, const void *value, size_t size, - int flags) override { - return xattrFs_ ? xattrFs_->lsetxattr(path, name, value, size, flags) : -1; - } - - int removexattr(const char *path, const char *name) override { - return xattrFs_ ? xattrFs_->removexattr(path, name) : -1; - } - - int lremovexattr(const char *path, const char *name) override { - return xattrFs_ ? xattrFs_->lremovexattr(path, name) : -1; - } - - UNIMPLEMENTED_POINTER(IFile *creat(const char *pathname, mode_t mode)); - UNIMPLEMENTED(int symlink(const char *oldname, const char *newname)); - UNIMPLEMENTED(int link(const char *oldname, const char *newname)); - UNIMPLEMENTED(int chmod(const char *pathname, mode_t mode)); - UNIMPLEMENTED(int chown(const char *pathname, uid_t owner, gid_t group)); - UNIMPLEMENTED(int lchown(const char *pathname, uid_t owner, gid_t group)); - UNIMPLEMENTED(int truncate(const char *path, off_t length)); - UNIMPLEMENTED(int utime(const char *path, const struct utimbuf *file_times)); - UNIMPLEMENTED(int utimes(const char *path, const struct timeval times[2])); - UNIMPLEMENTED(int lutimes(const char *path, const struct timeval times[2])); - UNIMPLEMENTED(int mknod(const char *path, mode_t mode, dev_t dev)); - UNIMPLEMENTED(int syncfs()); - -private: - IFileSystem *srcFs_; // owned by extern - ICachePool *fileCachePool_; // owned by current class - size_t pageSize_; - - IOAlloc *allocator_; - IFileSystemXAttr *xattrFs_; -}; - -/* - * the procedures of pread are as follows: - * 1. check that the cache is hit(contain unaligned block). - * 2. if hit, just read from cache. - * 3. if not, merge all holes into one read request(offset, size), - * then read missing data from source of file and write it into cache, - * after that read cache' data into user's buffer. - */ -class CachedFile : public ICachedFile, public IFileXAttr { -public: - CachedFile(ICacheStore *cache_store, size_t pageSize, IFileSystem *fs) - : cache_store_(cache_store), pageSize_(pageSize), fs_(fs) { - } - - ~CachedFile() { - cache_store_->release(); - } - - IFileSystem *filesystem() override { - return fs_; - } - - ssize_t pread(void *buf, size_t count, off_t offset) override { - struct iovec v { - buf, count - }; - return preadv(&v, 1, offset); - } - - ssize_t preadv(const struct iovec *iov, int iovcnt, off_t offset) override { - return preadv2(iov, iovcnt, offset, 0); - } - - ssize_t preadv2(const struct iovec *iov, int iovcnt, off_t offset, int flags) override { - return cache_store_->preadv2(iov, iovcnt, offset, flags); - } - - // pwrite* need to be aligned to 4KB for avoiding write padding. - ssize_t pwrite(const void *buf, size_t count, off_t offset) override { - struct iovec v { - const_cast(buf), count - }; - return pwritev(&v, 1, offset); - } - - ssize_t pwritev(const struct iovec *iov, int iovcnt, off_t offset) override { - return pwritev2(iov, iovcnt, offset, 0); - } - - ssize_t pwritev2(const struct iovec *iov, int iovcnt, off_t offset, int flags) override { - return cache_store_->pwritev2(iov, iovcnt, offset, flags); - } - - int fstat(struct stat *buf) override { - DEFER({ buf->st_ino = cache_store_->get_handle(); }); - auto size = cache_store_->get_actual_size(); - if (size % pageSize_ != 0) { - buf->st_size = size; - return 0; - } - IFile *src_file = nullptr; - if (cache_store_->open_src_file(&src_file) != 0) - return -1; - if (src_file) - return src_file->fstat(buf); - return cache_store_->fstat(buf); - } - - int close() override { - return 0; - } - - ssize_t read(void *buf, size_t count) override { - struct iovec v { - buf, count - }; - return readv(&v, 1); - } - - ssize_t readv(const struct iovec *iov, int iovcnt) override { - auto ret = preadv(iov, iovcnt, readOffset_); - if (ret > 0) { - readOffset_ += ret; - } - return ret; - } - - ssize_t write(const void *buf, size_t count) override { - struct iovec v { - const_cast(buf), count - }; - return writev(&v, 1); - } - - ssize_t writev(const struct iovec *iov, int iovcnt) override { - auto ret = pwritev(iov, iovcnt, writeOffset_); - if (ret > 0) { - writeOffset_ += ret; - } - return ret; - } - - int query(off_t offset, size_t count) override { - auto ret = cache_store_->queryRefillRange(offset, count); - if (ret.first < 0) - return -1; - return ret.second; - } - - // offset and len must be aligned 4k, otherwise it's useless. - // !!! need ensure no other read operation, otherwise read may read hole data(zero). - int fallocate(int mode, off_t offset, off_t len) override { - if (len == -1) { - return cache_store_->evict(offset, len); - } - range_split rs(offset, len, pageSize_); - auto aligned_offset = rs.aligned_begin_offset(); - auto aligned_len = rs.aligned_length(); - LOG_DEBUG(VALUE(offset), VALUE(len), VALUE(aligned_offset), VALUE(aligned_len)); - return cache_store_->evict(aligned_offset, aligned_len); - } - - int fadvise(off_t offset, off_t len, int advice) override { - if (advice == POSIX_FADV_WILLNEED) { - int ret = prefetch(len, offset, 0); - if (ret < 0) { - LOG_ERROR_RETURN(0, -1, "prefetch read failed"); - } - return 0; - } - LOG_ERRNO_RETURN(ENOSYS, -1, "advice ` is not implemented", advice); - } - - IFile *get_source() override { - IFile *src = nullptr; - if (cache_store_->open_src_file(&src) != 0) - return nullptr; - return src; - } - - inline void get_source_filexattr() { - if (!source_filexattr_) { - auto sfile = get_source(); - source_filexattr_ = dynamic_cast(sfile); - } - } - - // set the source file system, and enable `auto_refill` - int set_source(IFile *src) override { - cache_store_->set_src_file(src); - return 0; - } - - ICacheStore *get_store() override { - return cache_store_; - } - - int ftruncate(off_t length) override { - cache_store_->set_cached_size(length); - cache_store_->set_actual_size(length); - return 0; - } - - std::string_view get_pathname() { - return get_store()->get_src_name(); - } - - ssize_t fgetxattr(const char *name, void *value, size_t size) override { - get_source_filexattr(); - return source_filexattr_ ? source_filexattr_->fgetxattr(name, value, size) : -1; - } - - ssize_t flistxattr(char *list, size_t size) override { - get_source_filexattr(); - return source_filexattr_ ? source_filexattr_->flistxattr(list, size) : -1; - } - - int fsetxattr(const char *name, const void *value, size_t size, int flags) override { - get_source_filexattr(); - return source_filexattr_ ? source_filexattr_->fsetxattr(name, value, size, flags) : -1; - } - - int fremovexattr(const char *name) override { - get_source_filexattr(); - return source_filexattr_ ? source_filexattr_->fremovexattr(name) : -1; - } - - UNIMPLEMENTED(off_t lseek(off_t offset, int whence)); - UNIMPLEMENTED(int fsync()); - UNIMPLEMENTED(int fdatasync()); - UNIMPLEMENTED(int fchmod(mode_t mode)); - UNIMPLEMENTED(int fchown(uid_t owner, gid_t group)); - UNIMPLEMENTED(int fiemap(photon::fs::fiemap *map)); - -protected: - ssize_t prefetch(size_t count, off_t offset, int flags) { - uint64_t end = photon::sat_add(offset, count); - if (offset % pageSize_ != 0) { - offset = offset / pageSize_ * pageSize_; - } - if (end % pageSize_ != 0) { - end = photon::sat_add(end, pageSize_ - 1) / pageSize_ * pageSize_; - } - - uint64_t remain = end - offset; - ssize_t read = 0; - while (remain > 0) { - off_t min = std::min(kMaxPrefetchSize, remain); - remain -= min; - auto ret = cache_store_->try_refill_range(offset, static_cast(min)); - if (ret < 0) { - LOG_ERRNO_RETURN(0, -1, - "try_refill_range failed, ret : `, len : `, offset : `, flags : `", - ret, min, offset, flags); - } - read += ret; - // read end of file. - if (ret < min) { - return read; - } - offset += ret; - } - return read; - } - - ICacheStore *cache_store_; - size_t pageSize_; - IFileSystem *fs_; - - off_t readOffset_ = 0; - off_t writeOffset_ = 0; - IFileXAttr *source_filexattr_ = nullptr; -}; - -} // namespace Cache - -namespace FileSystem { -using namespace photon::fs; -ICachedFileSystem *new_cached_fs(IFileSystem *src, ICachePool *pool, uint64_t pageSize, - IOAlloc *allocator, CacheFnTransFunc fn_trans_func) { - if (!allocator) { - allocator = new IOAlloc; - } - return new ::Cache::CachedFs(src, pool, pageSize, allocator, fn_trans_func); -} - -ICachedFile *new_cached_file(ICacheStore *store, uint64_t pageSize, IFileSystem *fs) { - return new ::Cache::CachedFile(store, pageSize, fs); -} -} // namespace FileSystem diff --git a/src/overlaybd/cache/download_cache/CMakeLists.txt b/src/overlaybd/cache/download_cache/CMakeLists.txt deleted file mode 100644 index 40fcd3f0..00000000 --- a/src/overlaybd/cache/download_cache/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -file(GLOB SRC_DOWNLOADCACHE "*.cpp") - -add_library(download_cache_lib STATIC ${SRC_DOWNLOADCACHE}) -target_include_directories(download_cache_lib PUBLIC - ${PHOTON_INCLUDE_DIR} -) diff --git a/src/overlaybd/cache/download_cache/download_cache.cpp b/src/overlaybd/cache/download_cache/download_cache.cpp deleted file mode 100644 index ab800379..00000000 --- a/src/overlaybd/cache/download_cache/download_cache.cpp +++ /dev/null @@ -1,372 +0,0 @@ -/* - Copyright The Overlaybd Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -#include "../cache.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define SET_LOCAL_DIR 118 -#define SET_SIZE 119 - -namespace Cache { - -using namespace FileSystem; -using namespace photon::fs; - -class DownloadCacheFs; - -class DownloadCacheStore : public ForwardFile_Ownership { -public: - DownloadCacheStore(IFile *file, DownloadCacheFs *fs) - : ForwardFile_Ownership(file, true), m_fs(fs) { - } - - using ForwardFile_Ownership::ftruncate; - using ForwardFile_Ownership::preadv; - using ForwardFile_Ownership::pwritev; - - int fallocate(int mode, off_t offset, off_t len) override { - ScopedRangeLock lock(m_range_lock, offset, len); - return m_file->fallocate(mode, offset, len); - } - int ftruncate(off_t length) override { - // set when initialization, no need lock (length too large for range lock) - return m_file->ftruncate(length); - } - - std::pair query_refill_range(off_t offset, size_t size); - - int try_lock_wait(uint64_t offset, uint64_t length) { - return m_range_lock.try_lock_wait(offset, length); - } - void lock(uint64_t offset, uint64_t length) { - m_range_lock.lock(offset, length); - } - void unlock(uint64_t offset, uint64_t length) { - m_range_lock.unlock(offset, length); - } - -private: - RangeLock m_range_lock; - DownloadCacheFs *m_fs; -}; - -class DownloadCacheFile : public VirtualFile { -public: - DownloadCacheFile(IFile *file, const char *pathname, DownloadCacheFs *fs) - : m_file(file), m_name(pathname), m_fs(fs) { - } - ~DownloadCacheFile(); - - ssize_t preadv(const struct iovec *iov, int iovcnt, off_t offset) override; - int fstat(struct stat *buf) override; - int vioctl(int request, va_list args) override; - // used for evict - int fallocate(int mode, off_t offset, off_t len); - - UNIMPLEMENTED_POINTER(IFileSystem *filesystem() override); - UNIMPLEMENTED(off_t lseek(off_t offset, int whence) override); - UNIMPLEMENTED(int fsync() override); - UNIMPLEMENTED(int fdatasync() override); - UNIMPLEMENTED(int fchmod(mode_t mode) override); - UNIMPLEMENTED(int fchown(uid_t owner, gid_t group) override); - UNIMPLEMENTED(int ftruncate(off_t length) override); - UNIMPLEMENTED(int close() override); - -private: - std::string m_local_path; - photon::fs::IFile *m_file = nullptr; - DownloadCacheStore *m_local_file = nullptr; - size_t m_size = 0; - bool ready = false; - std::string m_name; - DownloadCacheFs *m_fs; -}; - -class DownloadCacheFs : public IFileSystem { -public: - DownloadCacheFs(IFileSystem *fs, size_t bs, size_t rs, IOAlloc *io_alloc) - : m_src_fs(fs), block_size(bs), refill_size(rs), io_alloc(io_alloc), - m_file_pool(1 * 1000 * 1000) { - LOG_INFO("new DownloadCacheFs"); - } - ~DownloadCacheFs() { - LOG_INFO("delete DownloadCacheFs"); - } - virtual IFile *open(const char *pathname, int flags) override { - auto src_file = m_src_fs->open(pathname, flags); - if (src_file == nullptr) { - LOG_ERRNO_RETURN(0, nullptr, "failed to open src: `", pathname); - } - return new DownloadCacheFile(src_file, pathname, this); - } - IFile *open(const char *pathname, int flags, mode_t mode) { - return open(pathname, flags, 0644); - } - - UNIMPLEMENTED(int stat(const char *pathname, struct stat *buf) override); - UNIMPLEMENTED(int lstat(const char *path, struct stat *buf) override); - UNIMPLEMENTED_POINTER(IFile *creat(const char *, mode_t) override); - UNIMPLEMENTED(int mkdir(const char *, mode_t) override); - UNIMPLEMENTED(int rmdir(const char *) override); - UNIMPLEMENTED(int link(const char *, const char *) override); - UNIMPLEMENTED(int symlink(const char *, const char *) override); - UNIMPLEMENTED(ssize_t readlink(const char *, char *, size_t) override); - UNIMPLEMENTED(int rename(const char *, const char *) override); - UNIMPLEMENTED(int chmod(const char *, mode_t) override); - UNIMPLEMENTED(int chown(const char *, uid_t, gid_t) override); - UNIMPLEMENTED(int statfs(const char *path, struct statfs *buf) override); - UNIMPLEMENTED(int statvfs(const char *path, struct statvfs *buf) override); - UNIMPLEMENTED(int access(const char *pathname, int mode) override); - UNIMPLEMENTED(int truncate(const char *path, off_t length) override); - UNIMPLEMENTED(int syncfs() override); - UNIMPLEMENTED(int unlink(const char *filename) override); - UNIMPLEMENTED(int lchown(const char *pathname, uid_t owner, gid_t group) override); - UNIMPLEMENTED_POINTER(DIR *opendir(const char *) override); - UNIMPLEMENTED(int utime(const char *path, const struct utimbuf *file_times) override); - UNIMPLEMENTED(int utimes(const char *path, const struct timeval times[2]) override); - UNIMPLEMENTED(int lutimes(const char *path, const struct timeval times[2]) override); - UNIMPLEMENTED(int mknod(const char *path, mode_t mode, dev_t dev) override); - - size_t block_size; - size_t refill_size; - IOAlloc *io_alloc; - ObjectCache m_file_pool; - -private: - photon::fs::IFileSystem *m_src_fs; -}; - -DownloadCacheFile::~DownloadCacheFile() { - safe_delete(m_file); - m_fs->m_file_pool.release(m_local_path); -} - -ssize_t DownloadCacheFile::preadv(const struct iovec *iov, int iovcnt, off_t offset) { - ssize_t ret = 0; - if (!ready) { - LOG_WARN("local path or size not set, read from source ", VALUE(offset)); - SCOPE_AUDIT("download", AU_FILEOP(m_name, offset, ret)); - ret = m_file->preadv(iov, iovcnt, offset); - return ret; - } - - iovector_view view((iovec *)iov, iovcnt); - size_t count = view.sum(); - if (count == 0) { - return 0; - } - - std::pair q; - - off_t align_left = align_down(offset, m_fs->block_size); - off_t align_right = align_up(offset + count, m_fs->block_size); - -again: - - m_local_file->lock(align_left, align_right - align_left); - { - DEFER({m_local_file->unlock(align_left, align_right - align_left);}); - q = m_local_file->query_refill_range(offset, count); - if (q.second == 0) { // no need to refill - return m_local_file->preadv(iov, iovcnt, offset); - } - } - - // refill - uint64_t r_offset = q.first; - uint64_t r_count = q.second; - if (r_offset + r_count > m_size) { - r_count = m_size - r_offset; - } - - IOVector buffer(*m_fs->io_alloc); - - auto lr = m_local_file->try_lock_wait(r_offset, r_count); - if (lr < 0) { - goto again; - } - DEFER({ m_local_file->unlock(r_offset, r_count); }); - - auto alloc = buffer.push_back(r_count); - if (alloc < r_count) { - LOG_ERROR("memory allocate failed, refill size:`, alloc:`", r_count, alloc); - SCOPE_AUDIT("download", AU_FILEOP(m_name, offset, ret)); - ret = m_file->preadv(iov, iovcnt, offset); - return ret; - } - - ssize_t read = 0; - { - SCOPE_AUDIT("download", AU_FILEOP(m_name, r_offset, read)); - read = m_file->preadv(buffer.iovec(), buffer.iovcnt(), r_offset); - } - - if (read != (ssize_t)r_count) { - LOG_ERRNO_RETURN(0, -1, "src file read failed, read: `, expect: `, size: `, offset: `", - read, r_count, m_size, r_offset); - } - - auto write = m_local_file->pwritev(buffer.iovec(), buffer.iovcnt(), r_offset); - if (write != (ssize_t)r_count) { - LOG_ERRNO_RETURN(0, -1, "local file write failed, write: `, expect: `, size: `, offset: `", - write, r_count, m_size, r_offset); - } - - return m_local_file->preadv(iov, iovcnt, offset); -} - -int DownloadCacheFile::fstat(struct stat *buf) { - return m_file->fstat(buf); -} - -int DownloadCacheFile::vioctl(int request, va_list args) { - if (request == SET_LOCAL_DIR) { - auto dir = va_arg(args, std::string); - if (m_local_file != nullptr) { - LOG_DEBUG("dir already set, ignore"); - return 0; - } - m_local_path = dir + "/.download"; - - m_local_file = m_fs->m_file_pool.acquire(m_local_path, [&]() -> DownloadCacheStore * { - auto f = open_localfile_adaptor(m_local_path.c_str(), O_RDWR | O_CREAT, 0644, 0); - if (f == nullptr) { - LOG_ERRNO_RETURN(0, nullptr, "failed to open local file ", VALUE(m_local_path)); - } - return new DownloadCacheStore(f, m_fs); - }); - - if (m_local_file != nullptr && m_size > 0) { - m_local_file->ftruncate(m_size); - ready = true; - } - return 0; - } else if (request == SET_SIZE) { - if (m_size > 0) { - LOG_DEBUG("size already set, ignore"); - return 0; - } - m_size = va_arg(args, size_t); - if (m_local_file != nullptr) { - m_local_file->ftruncate(m_size); - ready = true; - } - return 0; - } else { - return m_file->vioctl(request, args); - } -} - -int DownloadCacheFile::fallocate(int mode, off_t offset, off_t len) { - if (m_local_file == nullptr) { - LOG_WARN("local path or size not set, ignore"); - return 0; - } - if (len == -1) { - len = m_size - offset; - } - range_split_power2 rs(offset, len, m_fs->block_size); - auto aligned_offset = rs.aligned_begin_offset(); - auto aligned_len = rs.aligned_length(); - LOG_DEBUG("fallocate offset: `, len: `, aligned offset: `, aligned len: `", offset, len, - aligned_offset, aligned_len); - - return m_local_file->trim(aligned_offset, aligned_len); -} - -std::pair DownloadCacheStore::query_refill_range(off_t offset, size_t size) { - off_t align_left = align_down(offset, m_fs->block_size); - off_t align_right = align_up(offset + size, m_fs->block_size); - auto req_offset = align_left; - auto req_size = static_cast(align_right - align_left); - - struct photon::fs::fiemap_t<4096> fie(req_offset, req_size); - fie.fm_mapped_extents = 0; - - if (req_size > 0) { // fiemap cannot handle size zero. - auto ok = m_file->fiemap(&fie); - if (ok != 0) { - LOG_ERRNO_RETURN(0, std::make_pair(-1, 0), - "media fiemap failed : `, offset : `, size : `", ok, req_offset, - req_size); - } - if (fie.fm_mapped_extents >= 4096) { - LOG_ERROR_RETURN(EINVAL, std::make_pair(-1, 0), "read size is too big : `", req_size); - } - } - - uint64_t hole_start = req_offset; - uint64_t hole_end = req_offset + req_size; - - for (auto i = fie.fm_mapped_extents - 1; i < fie.fm_mapped_extents; i--) { - auto &extent = fie.fm_extents[i]; - if ((extent.fe_flags == FIEMAP_EXTENT_UNKNOWN) || - (extent.fe_flags == FIEMAP_EXTENT_UNWRITTEN)) - continue; - if (extent.fe_logical < hole_end) { - if (extent.fe_logical_end() >= hole_end) { - hole_end = extent.fe_logical; - } else - break; - } - } - - for (uint32_t i = 0; i < fie.fm_mapped_extents; i++) { - auto &extent = fie.fm_extents[i]; - if ((extent.fe_flags == FIEMAP_EXTENT_UNKNOWN) || - (extent.fe_flags == FIEMAP_EXTENT_UNWRITTEN)) - continue; - if (extent.fe_logical_end() > hole_start) { - if (extent.fe_logical <= hole_start) { - hole_start = extent.fe_logical_end(); - } else - break; - } - } - - if (hole_start >= hole_end) - return std::make_pair(0, 0); - // miss - auto left = align_down(hole_start, m_fs->refill_size); - auto right = align_up(hole_end, m_fs->refill_size); - return std::make_pair(left, right - left); -} - -} // namespace Cache - -namespace FileSystem { -using namespace photon::fs; - -IFileSystem *new_download_cached_fs(photon::fs::IFileSystem *src_fs, size_t blk_size, - size_t refill_size, IOAlloc *io_alloc) { - if (io_alloc == nullptr) { - io_alloc = new IOAlloc; - } - return new ::Cache::DownloadCacheFs(src_fs, blk_size, refill_size, io_alloc); -} -} // namespace FileSystem diff --git a/src/overlaybd/cache/forwardcfs.h b/src/overlaybd/cache/forwardcfs.h deleted file mode 100644 index e5cf520f..00000000 --- a/src/overlaybd/cache/forwardcfs.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - Copyright The Overlaybd Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -#pragma once -#include "cache.h" -#include "photon/fs/forwardfs.h" - -namespace FileSystem { -template -class ForwardCachedFileBase : public IForwardCachedFile { -protected: - using Base = IForwardCachedFile; - using Base::Base; - virtual photon::fs::IFile *get_source() override { - return Base::m_file->get_source(); - } - virtual int set_source(photon::fs::IFile *src) override { - return Base::m_file->set_source(src); - } - virtual ICacheStore *get_store() override { - return Base::m_file->get_store(); - } - virtual int query(off_t offset, size_t count) override { - return Base::m_file->query(offset, count); - } -}; -using ForwardCachedFile = ForwardCachedFileBase>; -using ForwardCachedFile_Ownership = - ForwardCachedFileBase>; - -template -class ForwardCachedFSBase : public IForwardCachedFS { -protected: - using Base = IForwardCachedFS; - using Base::Base; - virtual photon::fs::IFileSystem *get_source() override { - return Base::m_fs->get_source(); - } - virtual int set_source(photon::fs::IFileSystem *src) override { - return Base::m_fs->set_source(src); - } - virtual ICachePool *get_pool() override { - return Base::m_fs->get_pool(); - } - virtual int set_pool(ICachePool *pool) override { - return Base::m_fs->set_pool(pool); - } -}; -using ForwardCachedFS = ForwardCachedFSBase>; -using ForwardCachedFS_Ownership = - ForwardCachedFSBase>; -} // namespace FileSystem diff --git a/src/overlaybd/cache/full_file_cache/CMakeLists.txt b/src/overlaybd/cache/full_file_cache/CMakeLists.txt deleted file mode 100644 index 8eacb764..00000000 --- a/src/overlaybd/cache/full_file_cache/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -file(GLOB SRC_FULLFILECACHE "*.cpp") - -add_library(full_file_cache_lib STATIC ${SRC_FULLFILECACHE}) -target_include_directories(full_file_cache_lib PUBLIC - ${PHOTON_INCLUDE_DIR} -) diff --git a/src/overlaybd/cache/full_file_cache/cache_pool.cpp b/src/overlaybd/cache/full_file_cache/cache_pool.cpp deleted file mode 100644 index 006d860f..00000000 --- a/src/overlaybd/cache/full_file_cache/cache_pool.cpp +++ /dev/null @@ -1,315 +0,0 @@ -/* - Copyright The Overlaybd Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "cache_pool.h" -#include -#include -#include -#include -#include -#include "cache_store.h" -#include -#include -#include -#include -#include - -namespace Cache { - -using namespace FileSystem; -using namespace photon::fs; - -const uint64_t kGB = 1024 * 1024 * 1024; -const uint64_t kMaxFreeSpace = 50 * kGB; -const int64_t kEvictionMark = 5ll * kGB; - -FileCachePool::FileCachePool(IFileSystem *mediaFs, uint64_t capacityInGB, uint64_t periodInUs, - uint64_t diskAvailInBytes, uint64_t refillUnit) - : ICachePool(0), mediaFs_(mediaFs), capacityInGB_(capacityInGB), periodInUs_(periodInUs), - diskAvailInBytes_(diskAvailInBytes), refillUnit_(refillUnit), totalUsed_(0), timer_(nullptr), - running_(false), exit_(false), isFull_(false) { - int64_t capacityInBytes = capacityInGB_ * kGB; - waterMark_ = calcWaterMark(capacityInBytes, kMaxFreeSpace); - // keep this relation : waterMark < riskMark < capacity - riskMark_ = std::max(capacityInBytes - kEvictionMark, - (static_cast(waterMark_) + capacityInBytes) >> 1); -} - -FileCachePool::~FileCachePool() { - exit_ = true; - if (timer_) { - while (running_) { - photon::thread_usleep(1); - } - delete timer_; - } - this->stores_clear(); - delete mediaFs_; -} - -void FileCachePool::Init() { - traverseDir("/"); - timer_ = new photon::Timer(periodInUs_, {this, FileCachePool::timerHandler}, true, - 8UL * 1024 * 1024); -} - -ICacheStore *FileCachePool::do_open(std::string_view pathname, int flags, mode_t mode) { - auto localFile = openMedia(pathname, flags, mode); - if (!localFile) { - return nullptr; - } - - auto find = fileIndex_.find(pathname); - if (find == fileIndex_.end()) { - auto lruIter = lru_.push_front(fileIndex_.end()); - std::unique_ptr entry(new LruEntry{lruIter, 1, 0}); - find = fileIndex_.emplace(pathname, std::move(entry)).first; - lru_.front() = find; - } else { - lru_.access(find->second->lruIter); - find->second->openCount++; - } - - return new FileCacheStore(this, localFile, refillUnit_, find); -} - -IFile *FileCachePool::openMedia(std::string_view name, int flags, int mode) { - if (name.empty() || name[0] != '/') { - LOG_ERROR_RETURN(EINVAL, nullptr, "pathname is invalid, path : `", name); - } - - auto base_directory = Path(name.data()).dirname(); - auto ret = mkdir_recursive(base_directory, mediaFs_); - if (ret) { - LOG_ERRNO_RETURN(0, nullptr, "mkdir failed, path : `", name); - } - - auto localFile = mediaFs_->open(name.data(), flags, mode); - if (nullptr == localFile) { - LOG_ERRNO_RETURN(0, nullptr, "cache store open failed, pathname : `, flags : `, mode : `", - name, flags, mode); - } - return localFile; -} - -int FileCachePool::set_quota(std::string_view pathname, size_t quota) { - errno = ENOSYS; - return -1; -} - -int FileCachePool::stat(CacheStat *stat, std::string_view pathname) { - errno = ENOSYS; - return -1; -} - -int FileCachePool::evict(std::string_view filename) { - errno = ENOSYS; - return -1; -} - -int FileCachePool::evict(size_t size) { - errno = ENOSYS; - return -1; -} - -int FileCachePool::rename(std::string_view oldname, std::string_view newname) { - errno = ENOSYS; - return -1; -} - -bool FileCachePool::isFull() { - return isFull_; -} - -void FileCachePool::removeOpenFile(FileNameMap::iterator iter) { - iter->second->openCount--; -} - -void FileCachePool::forceRecycle() { - timerHandler(this); -} - -void FileCachePool::updateLru(FileNameMap::iterator iter) { - lru_.access(iter->second->lruIter); -} - -// currently, we exist duplicate pwrite -uint64_t FileCachePool::updateSpace(FileNameMap::iterator iter, uint64_t size) { - auto lruEntry = iter->second.get(); - uint64_t diff = 0; - if (size > lruEntry->size) { - diff = size - lruEntry->size; - totalUsed_ += diff; - } - lruEntry->size = size; - if (totalUsed_ >= riskMark_) { - LOG_WARN("pwrite is so heavy, totalUsed:`,riskMark:` || lruEntry->size = `", totalUsed_, - riskMark_, lruEntry->size); - isFull_ = true; - forceRecycle(); - if (lruEntry->size == 0) - diff = 0; // in some extream condition , - // forceRecycle maybe truncate current file to 0 - } - return diff; -} - -uint64_t FileCachePool::timerHandler(void *data) { - auto cur = static_cast(data); - if (cur->running_) { - return 0; - } - cur->running_ = true; - DEFER(cur->running_ = false;); - cur->eviction(); - return 0; -} - -void FileCachePool::eviction() { - uint64_t evictByDisk = 0; - uint64_t evictByCache = 0; - uint64_t fsCapacity = 0; - - DEFER(isFull_ = false); - struct statvfs stFs = {}; - auto err = mediaFs_->statvfs("/", &stFs); - if (err) { - LOG_ERROR("statvfs failed, ret : `, error code : `", err, ERRNO()); - return; - } else { - fsCapacity = stFs.f_frsize * stFs.f_blocks; - uint64_t diskAvailInBytes = stFs.f_bavail * stFs.f_frsize; - if (diskAvailInBytes < diskAvailInBytes_) { - evictByDisk = diskAvailInBytes_ - diskAvailInBytes; - } else if (fsCapacity <= waterMark_) { // we occupy the whole disk - return; - } - } - - if (totalUsed_ >= static_cast(waterMark_)) { - evictByCache = totalUsed_ - waterMark_; - } - - auto actualEvict = std::min( - static_cast(std::max(evictByCache, evictByDisk)), - totalUsed_ - ); - - if (actualEvict <= 0) { - return; - } - - isFull_ = true; - - if (!lru_.empty() && !exit_) { - LOG_AUDIT("eviction", VALUE(actualEvict), VALUE(evictByCache), VALUE(evictByDisk), VALUE(totalUsed_)); - } - - while (actualEvict > 0 && !lru_.empty() && !exit_) { - auto fileIter = lru_.back(); - const auto &fileName = fileIter->first; - auto lruEntry = fileIter->second.get(); - auto fileSize = lruEntry->size; - if (lruEntry->openCount == 0) { - lru_.mark_key_cleared(fileIter->second->lruIter); - } else { - lru_.access(fileIter->second->lruIter); - } - // as soon as possible truncate and unlink - if (0 == fileSize) { - if (0 == fileIter->second->openCount) { - afterFtrucate(fileIter); - } - photon::thread_yield(); - continue; - } - - { - photon::scoped_rwlock rl(lruEntry->rw_lock_, photon::WLOCK); - err = mediaFs_->truncate(fileName.data(), 0); - lruEntry->truncate_done = false; - } - - if (err) { - ERRNO e; - LOG_ERROR("truncate(0) failed, name : `, ret : `, error code : `", fileName, err, e); - // truncate to 0 failed means unable to free the file, it should not consider as a part - // of cache. Deal as it already release. - // The only exception is errno EINTR, means truncate interrupted by signal, should try - // again - if (e.no == EINTR) { - photon::thread_yield(); - continue; - } - } - afterFtrucate(fileIter); - actualEvict -= fileSize; - photon::thread_yield(); - } -} - -uint64_t FileCachePool::calcWaterMark(uint64_t capacity, uint64_t maxFreeSpace) { - return std::max(static_cast(capacity * kWaterMarkRatio * 0.01), - capacity > maxFreeSpace ? capacity - maxFreeSpace : 0); -} - -bool FileCachePool::afterFtrucate(FileNameMap::iterator iter) { - auto lruEntry = iter->second.get(); - totalUsed_ -= static_cast(lruEntry->size); - lruEntry->size = 0; - if (totalUsed_ < 0) { - totalUsed_ = 0; - } - if (0 == iter->second->openCount) { - auto err = mediaFs_->unlink(iter->first.data()); - ERRNO e; - LOG_ERROR("unlink failed, name : `, ret : `, error code : `", iter->first, err, e); - // unlik failed may caused by multiple reasons - // only EBUSY should may be able to trying to unlink again - // other reason should never try to clean it. - if (err && (e.no == EBUSY)) { - return false; - } - lru_.remove(iter->second->lruIter); - fileIndex_.erase(iter); - } - return true; -} - -int FileCachePool::traverseDir(const std::string &root) { - for (auto file : enumerable(Walker(mediaFs_, root))) { - insertFile(file); - } - return 0; -} - -int FileCachePool::insertFile(std::string_view file) { - struct stat st = {}; - auto ret = mediaFs_->stat(file.data(), &st); - if (ret) { - LOG_ERRNO_RETURN(0, -1, "stat failed, name : `", file.data()); - } - auto fileSize = st.st_blocks * kDiskBlockSize; - - auto lruIter = lru_.push_front(fileIndex_.end()); - auto entry = std::unique_ptr(new LruEntry{lruIter, 0, fileSize}); - auto iter = fileIndex_.emplace(file, std::move(entry)).first; - lru_.front() = iter; - totalUsed_ += fileSize; - return 0; -} - -} // namespace Cache diff --git a/src/overlaybd/cache/full_file_cache/cache_pool.h b/src/overlaybd/cache/full_file_cache/cache_pool.h deleted file mode 100644 index a8eb1123..00000000 --- a/src/overlaybd/cache/full_file_cache/cache_pool.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - Copyright The Overlaybd Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include "../policy/lru.h" -#include "../pool_store.h" - -#include - -namespace Cache { - -class FileCachePool : public FileSystem::ICachePool { -public: - FileCachePool(photon::fs::IFileSystem *mediaFs, uint64_t capacityInGB, uint64_t periodInUs, - uint64_t diskAvailInBytes, uint64_t refillUnit); - ~FileCachePool(); - - static const uint64_t kDiskBlockSize = 512; // stat(2) - static const uint64_t kDeleteDelayInUs = 1000; - static const uint32_t kWaterMarkRatio = 90; - - void Init(); - - // pathname must begin with '/' - FileSystem::ICacheStore *do_open(std::string_view pathname, int flags, mode_t mode) override; - - int set_quota(std::string_view pathname, size_t quota) override; - int stat(FileSystem::CacheStat *stat, - std::string_view pathname = std::string_view(nullptr, 0)) override; - - int evict(std::string_view filename) override; - int evict(size_t size = 0) override; - int rename(std::string_view oldname, std::string_view newname) override; - - struct LruEntry { - LruEntry(uint32_t lruIt, int openCnt, uint64_t fileSize) - : lruIter(lruIt), openCount(openCnt), size(fileSize), truncate_done(false) { - } - ~LruEntry() = default; - uint32_t lruIter; - int openCount; - uint64_t size; - photon::rwlock rw_lock_; - bool truncate_done; - }; - - // Normally, fileIndex(std::map) always keep growing, so its iterators always - // keep valid, iterator will be erased when file be unlinked in period of eviction, - // but on that time corresponding CachedFile had been destructed, so nobody hold - // erased iterator. - typedef map_string_key> FileNameMap; - - bool isFull(); - void removeOpenFile(FileNameMap::iterator iter); - void forceRecycle(); - void updateLru(FileNameMap::iterator iter); - uint64_t updateSpace(FileNameMap::iterator iter, uint64_t size); - -protected: - photon::fs::IFile *openMedia(std::string_view name, int flags, int mode); - - static uint64_t timerHandler(void *data); - virtual void eviction(); - uint64_t calcWaterMark(uint64_t capacity, uint64_t maxFreeSpace); - - photon::fs::IFileSystem *mediaFs_; // owned by current class - uint64_t capacityInGB_; - uint64_t periodInUs_; - uint64_t diskAvailInBytes_; - size_t refillUnit_; - int64_t totalUsed_; - int64_t riskMark_; - uint64_t waterMark_; - - photon::Timer *timer_; - bool running_; - bool exit_; - - bool isFull_; - - virtual bool afterFtrucate(FileNameMap::iterator iter); - - int traverseDir(const std::string &root); - virtual int insertFile(std::string_view file); - - typedef FileSystem::LRU LRUContainer; - LRUContainer lru_; - // filename -> lruEntry - FileNameMap fileIndex_; -}; - -} // namespace Cache diff --git a/src/overlaybd/cache/full_file_cache/cache_store.cpp b/src/overlaybd/cache/full_file_cache/cache_store.cpp deleted file mode 100644 index 269e9756..00000000 --- a/src/overlaybd/cache/full_file_cache/cache_store.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/* - Copyright The Overlaybd Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "cache_store.h" -#include "sys/statvfs.h" -#include -#include -#include -#include -#include -#include -#include -#include "cache_pool.h" - -using namespace FileSystem; -using namespace photon::fs; - -namespace Cache { - -const uint64_t kDiskBlockSize = 512; // stat(2) -constexpr int kFieExtentSize = 1000; -const int kBlockSize = 4 * 1024; - -FileCacheStore::FileCacheStore(FileSystem::ICachePool *cachePool, IFile *localFile, - size_t refillUnit, FileIterator iterator) - : cachePool_(static_cast(cachePool)), localFile_(localFile), - refillUnit_(refillUnit), iterator_(iterator) { -} - -FileCacheStore::~FileCacheStore() { - delete localFile_; // will close file - cachePool_->removeOpenFile(iterator_); -} - -ICacheStore::try_preadv_result FileCacheStore::try_preadv2(const struct iovec *iov, int iovcnt, - off_t offset, int flags) { - auto lruEntry = static_cast(iterator_->second.get()); - photon::scoped_rwlock rl(lruEntry->rw_lock_, photon::RLOCK); - return this->ICacheStore::try_preadv2(iov, iovcnt, offset, flags); -} - -ssize_t FileCacheStore::do_preadv2(const struct iovec *iov, int iovcnt, off_t offset, int flags) { - // TODO(suoshi.yf): maybe a new interface for updating lru is better for avoiding - // multiple cacheStore preadvs but cacheFile preadv only once - ssize_t ret; - cachePool_->updateLru(iterator_); - SCOPE_AUDIT_THRESHOLD(1UL * 1000, "file:read", AU_FILEOP("", offset, ret)); - ret = localFile_->preadv(iov, iovcnt, offset); - return ret; -} - -ssize_t FileCacheStore::do_pwritev(const struct iovec *iov, int iovcnt, off_t offset) { - ssize_t ret; - iovector_view view((iovec *)iov, iovcnt); - auto lruEntry = static_cast(iterator_->second.get()); - photon::scoped_rwlock rl(lruEntry->rw_lock_, photon::RLOCK); - if (!lruEntry->truncate_done) { - // May repeated ftruncate() here, but it doesn't matter - ret = localFile_->ftruncate(actual_size_); - if (ret) { - LOG_ERRNO_RETURN(0, -1, "failed to truncate media file: ", VALUE(ret)); - } - lruEntry->truncate_done = true; - } - ScopedRangeLock lock(rangeLock_, offset, view.sum()); - SCOPE_AUDIT_THRESHOLD(10UL * 1000, "file:write", AU_FILEOP("", offset, ret)); - ret = localFile_->pwritev(iov, iovcnt, offset); - return ret; -} - -ssize_t FileCacheStore::do_pwritev2(const struct iovec *iov, int iovcnt, off_t offset, int flags) { - if (cacheIsFull()) { - errno = ENOSPC; - return -1; - } - - auto ret = do_pwritev(iov, iovcnt, offset); - if (ret < 0 && ENOSPC == errno) { - cachePool_->forceRecycle(); - } - - if (ret > 0) { - struct stat st = {}; - auto err = localFile_->fstat(&st); - if (err) { - LOG_ERRNO_RETURN(0, ret, "fstat failed") - } - cachePool_->updateLru(iterator_); - cachePool_->updateSpace(iterator_, kDiskBlockSize * st.st_blocks); - } - return ret; -} - -std::pair FileCacheStore::queryRefillRange(off_t offset, size_t size) { - ScopedRangeLock lock(rangeLock_, offset, size); - off_t alignLeft = align_down(offset, kBlockSize); - off_t alignRight = align_up(offset + size, kBlockSize); - ReadRequest request{alignLeft, static_cast(alignRight - alignLeft)}; - struct fiemap_t fie(request.offset, request.size); - fie.fm_mapped_extents = 0; - - if (request.size > 0) { // fiemap cannot handle size zero. - auto ok = localFile_->fiemap(&fie); - if (ok != 0) { - LOG_ERRNO_RETURN(0, std::make_pair(-1, 0), - "media fiemap failed : `, offset : `, size : `", ok, request.offset, - request.size); - } - if (fie.fm_mapped_extents >= kFieExtentSize) { // TODO: qisheng.ds - // it's supposed to be solved by - // get fie extents twice, - // but just do not concern much - // about it rightnow. - LOG_ERROR_RETURN(EINVAL, std::make_pair(-1, 0), "read size is too big : `", - request.size); - } - } - - uint64_t holeStart = request.offset; - uint64_t holeEnd = request.offset + request.size; - - for (ssize_t i = (ssize_t)(fie.fm_mapped_extents) - 1; i >= 0; i--) { - auto &extent = fie.fm_extents[i]; - if ((extent.fe_flags == FIEMAP_EXTENT_UNKNOWN) || - (extent.fe_flags == FIEMAP_EXTENT_UNWRITTEN)) - continue; - if (extent.fe_logical < holeEnd) { - if (extent.fe_logical_end() >= holeEnd) { - holeEnd = extent.fe_logical; - } else - break; - } - } - - for (uint32_t i = 0; i < fie.fm_mapped_extents; i++) { - auto &extent = fie.fm_extents[i]; - if ((extent.fe_flags == FIEMAP_EXTENT_UNKNOWN) || - (extent.fe_flags == FIEMAP_EXTENT_UNWRITTEN)) - continue; - if (extent.fe_logical_end() > holeStart) { - if (extent.fe_logical <= holeStart) { - holeStart = extent.fe_logical_end(); - } else - break; - } - } - - if (holeStart >= holeEnd) - return std::make_pair(0, 0); - // CacheMiss - auto left = align_down(holeStart, refillUnit_); - auto right = align_up(holeEnd, refillUnit_); - return std::make_pair(left, right - left); -} - -int FileCacheStore::set_quota(size_t quota) { - errno = ENOSYS; - return -1; -} - -int FileCacheStore::stat(CacheStat *stat) { - errno = ENOSYS; - return -1; -} - -int FileCacheStore::evict(off_t offset, size_t count) { - if (static_cast(-1) == count) { - return localFile_->ftruncate(offset); - } else { -#ifndef FALLOC_FL_KEEP_SIZE -#define FALLOC_FL_KEEP_SIZE 0x01 /* default is extend size */ -#endif -#ifndef FALLOC_FL_PUNCH_HOLE -#define FALLOC_FL_PUNCH_HOLE 0x02 /* de-allocates range */ -#endif - int mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE; - return localFile_->fallocate(mode, offset, count); - } -} - -int FileCacheStore::fstat(struct stat *buf) { - return localFile_->fstat(buf); -} - -bool FileCacheStore::cacheIsFull() { - return cachePool_->isFull(); -} - -} // namespace Cache diff --git a/src/overlaybd/cache/full_file_cache/cache_store.h b/src/overlaybd/cache/full_file_cache/cache_store.h deleted file mode 100644 index 7c426e40..00000000 --- a/src/overlaybd/cache/full_file_cache/cache_store.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - Copyright The Overlaybd Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -#pragma once - -#include -#include -#include -#include "cache_pool.h" - -namespace Cache { - -class FileCachePool; - -class FileCacheStore : public FileSystem::ICacheStore { -public: - typedef FileCachePool::FileNameMap::iterator FileIterator; - FileCacheStore(FileSystem::ICachePool *cachePool, photon::fs::IFile *localFile, - size_t refillUnit, FileIterator iterator); - ~FileCacheStore(); - - try_preadv_result try_preadv2(const struct iovec *iov, int iovcnt, off_t offset, int flags) override; - - ssize_t do_preadv2(const struct iovec *iov, int iovcnt, off_t offset, int flags) override; - - ssize_t do_pwritev2(const struct iovec *iov, int iovcnt, off_t offset, int flags) override; - - int set_quota(size_t quota) override; - int stat(FileSystem::CacheStat *stat) override; - int evict(off_t offset, size_t count = -1) override; - - std::pair queryRefillRange(off_t offset, size_t size) override; - - int fstat(struct stat *buf) override; - -protected: - bool cacheIsFull(); - - struct ReadRequest { - off_t offset; - size_t size; - }; - - // merge from first extent to last extent(or encounter hole), - // because fiemap could return multiple continuous extents even though no any hole. - std::pair getFirstMergedExtents(struct fiemap *fie); - - std::pair getLastMergedExtents(struct fiemap *fie); - - std::pair getHoleFromCacheHitResult(off_t offset, size_t alignSize, - struct fiemap *fie); - - FileCachePool *cachePool_; // owned by extern class - photon::fs::IFile *localFile_; // owned by current class - size_t refillUnit_; - FileIterator iterator_; - RangeLock rangeLock_; - - ssize_t do_pwritev(const struct iovec *iov, int iovcnt, off_t offset); -}; - -} // namespace Cache diff --git a/src/overlaybd/cache/ocf_cache/CMakeLists.txt b/src/overlaybd/cache/ocf_cache/CMakeLists.txt deleted file mode 100644 index 9bdca075..00000000 --- a/src/overlaybd/cache/ocf_cache/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -if(BUILD_TESTING) - add_subdirectory(test) -endif() - -# ocf_env_lib -file(GLOB_RECURSE src_ocf_env ease_bindings/env/*.cpp) -add_library(ocf_env_lib STATIC ${src_ocf_env}) -target_include_directories(ocf_env_lib PUBLIC include/ ${PHOTON_INCLUDE_DIR}) - -# ocf_lib -file(GLOB_RECURSE src_ocf ocf/src/*.c) -add_library(ocf_lib STATIC ${src_ocf}) -target_include_directories(ocf_lib PUBLIC include/ ease_bindings/env/) -target_link_libraries(ocf_lib ocf_env_lib z) -target_compile_options(ocf_lib PRIVATE -Wno-sign-compare) - -# ocf_cache_lib -file(GLOB src_ocf_cache ocf_cache.cpp ocf_namespace.cpp ease_bindings/*.cpp) -add_library(ocf_cache_lib STATIC ${src_ocf_cache}) -target_include_directories(ocf_cache_lib PUBLIC include/ ease_bindings/env/ ${PHOTON_INCLUDE_DIR}) -target_link_libraries(ocf_cache_lib ocf_lib photon_static) \ No newline at end of file diff --git a/src/overlaybd/cache/ocf_cache/ease_bindings/ctx.cpp b/src/overlaybd/cache/ocf_cache/ease_bindings/ctx.cpp deleted file mode 100644 index 5444a95e..00000000 --- a/src/overlaybd/cache/ocf_cache/ease_bindings/ctx.cpp +++ /dev/null @@ -1,272 +0,0 @@ -#include "ctx.h" - -#include -#include -#include - -extern "C" { -#include "env/ocf_env.h" -} - -IOAlloc *g_io_alloc = nullptr; - -static size_t iovec_to_buf(iovec *iov, size_t iovcnt, void *buf, size_t size, size_t offset) { - size_t i, len, done = 0; - for (i = 0; i < iovcnt; i++) { - if (offset >= iov[i].iov_len) { - offset -= iov[i].iov_len; - continue; - } - if (iov[i].iov_base == nullptr) { - continue; - } - if (done >= size) { - break; - } - len = MIN(size - done, iov[i].iov_len - offset); - memcpy(buf, (uint8_t *)iov[i].iov_base + offset, len); - buf = (uint8_t *)buf + len; - done += len; - offset = 0; - } - return done; -} - -static size_t buf_to_iovec(const void *buf, size_t size, iovec *iov, size_t iovcnt, size_t offset) { - size_t i, len, done = 0; - for (i = 0; i < iovcnt; i++) { - if (offset >= iov[i].iov_len) { - offset -= iov[i].iov_len; - continue; - } - if (iov[i].iov_base == nullptr) { - continue; - } - if (done >= size) { - break; - } - len = MIN(size - done, iov[i].iov_len - offset); - memcpy((uint8_t *)iov[i].iov_base + offset, buf, len); - buf = (uint8_t *)buf + len; - done += len; - offset = 0; - } - return done; -} - -static size_t iovset(iovec *iov, size_t iovcnt, int byte, size_t size, size_t offset) { - size_t i, len, done = 0; - for (i = 0; i < iovcnt; i++) { - if (offset >= iov[i].iov_len) { - offset -= iov[i].iov_len; - continue; - } - if (iov[i].iov_base == nullptr) { - continue; - } - if (done >= size) { - break; - } - len = MIN(size - done, iov[i].iov_len - offset); - memset((uint8_t *)iov[i].iov_base + offset, byte, len); - done += len; - offset = 0; - } - return done; -} - -/* Context config */ - -static ctx_data_t *ctx_data_alloc(uint32_t pages) { - size_t buf_size = PAGE_SIZE * pages; - auto data = new ease_ocf_io_data(); - data->iovs = (iovec *)g_io_alloc->alloc(sizeof(iovec)); - if (data->iovs == nullptr) { - delete data; - LOG_ERRNO_RETURN(ENOMEM, nullptr, "OCF: failed to allocate iov"); - } - - void *buf = g_io_alloc->alloc(buf_size); - if (buf == nullptr) { - LOG_ERRNO_RETURN(ENOMEM, nullptr, "OCF: failed to allocate memory, size `", - PAGE_SIZE * pages); - } - - data->iovs[0].iov_base = buf; - data->iovs[0].iov_len = buf_size; - data->iovcnt = 1; - data->size = buf_size; - - LOG_DEBUG("OCF: ctx data alloc: buf `, size ", buf, buf_size); - return data; -} - -static void ctx_data_free(ctx_data_t *ctx_data) { - if (ctx_data == nullptr) { - return; - } - auto data = (ease_ocf_io_data *)ctx_data; - for (int i = 0; i < data->iovcnt; i++) { - if (data->iovs[i].iov_base) { - LOG_DEBUG("OCF: ctx data free: buf `", data->iovs[i].iov_base); - g_io_alloc->dealloc(data->iovs[i].iov_base); - data->iovs[i].iov_base = nullptr; - } - } - if (data->iovs) { - g_io_alloc->dealloc(data->iovs); - data->iovs = nullptr; - } - delete data; -} - -static int ctx_data_mlock(ctx_data_t *ctx_data) { - return 0; -} - -static void ctx_data_munlock(ctx_data_t *ctx_data) { -} - -static uint32_t ctx_data_read(void *dst, ctx_data_t *src, uint32_t size) { - auto data = (ease_ocf_io_data *)src; - LOG_DEBUG("OCF: ctx data read: buf `, seek_offset `", data->iovs[0].iov_base, data->seek); - uint32_t n_read = iovec_to_buf(data->iovs, data->iovcnt, dst, size, data->seek); - data->seek += n_read; - return n_read; -} - -static uint32_t ctx_data_write(ctx_data_t *dst, const void *src, uint32_t size) { - auto data = (ease_ocf_io_data *)dst; - LOG_DEBUG("OCF: ctx data write: buf `, seek_offset `", data->iovs[0].iov_base, data->seek); - uint32_t n_written = buf_to_iovec(src, size, data->iovs, data->iovcnt, data->seek); - data->seek += n_written; - return n_written; -} - -static uint32_t ctx_data_zero(ctx_data_t *dst, uint32_t size) { - auto data = (ease_ocf_io_data *)dst; - LOG_DEBUG("OCF: ctx data zero: buf `, seek_offset `", data->iovs[0].iov_base, data->seek); - uint32_t n_bytes = iovset(data->iovs, data->iovcnt, 0, size, data->seek); - data->seek += n_bytes; - return n_bytes; -} - -static uint32_t ctx_data_seek(ctx_data_t *dst, ctx_data_seek_t seek, uint32_t offset) { - auto data = (ease_ocf_io_data *)dst; - uint32_t off = 0; - LOG_DEBUG("OCF: ctx data seek: buf `, current seek_offset `, new seek `, new seek_offset `", - data->iovs[0].iov_base, data->seek, seek, offset); - switch (seek) { - case ctx_data_seek_begin: - off = MIN(offset, data->size); - data->seek = off; - break; - case ctx_data_seek_current: - off = MIN(offset, data->size - data->seek); - data->seek += off; - break; - } - return off; -} - -inline static uint64_t ctx_data_copy(ctx_data_t *dst, ctx_data_t *src, uint64_t to, uint64_t from, - uint64_t bytes) { - auto data_dst = (ease_ocf_io_data *)dst; - auto data_src = (ease_ocf_io_data *)src; - bytes = MIN(bytes, data_src->size - from); - bytes = MIN(bytes, data_dst->size - to); - LOG_DEBUG( - "OCF: ctx data copy: src buf `, src seek_offset `, dst buf `, dst seek_offset `, copy ` bytes", - data_src->iovs[0].iov_base, data_src->seek, data_dst->iovs[0].iov_base, data_dst->seek, - bytes); - - uint32_t need_copy = bytes; - uint32_t iter_iov = 0, iter_offset = 0, n = 0; - - while (from || bytes) { - if (data_src->iovs[iter_iov].iov_len == iter_offset) { - iter_iov++; - iter_offset = 0; - continue; - } - if (from) { - n = MIN(from, data_src->iovs[iter_iov].iov_len); - from -= n; - } else { - n = MIN(bytes, data_src->iovs[iter_iov].iov_len); - buf_to_iovec((uint8_t *)data_src->iovs[iter_iov].iov_base + iter_offset, n, - data_dst->iovs, data_dst->iovcnt, to); - bytes -= n; - to += n; - } - iter_offset += n; - } - return need_copy; -} - -static void ctx_data_secure_erase(ctx_data_t *ctx_data) { -} - -static int ctx_cleaner_init(ocf_cleaner_t c) { - return 0; -} - -static void ctx_cleaner_kick(ocf_cleaner_t c) { -} - -static void ctx_cleaner_stop(ocf_cleaner_t c) { -} - -static int ctx_logger_print(ocf_logger_t logger, ocf_logger_lvl_t lvl, const char *fmt, - va_list args) { - char buf[512]; - int ret = vsprintf(buf, fmt, args); - DEFER(LOG_DEBUG(buf)); - if (ret > 0 && ret <= (int)sizeof(buf) && buf[ret - 1] == '\n') { - buf[ret - 1] = 0; - return ret - 1; - } else { - return ret; - } -} - -static const ocf_ctx_config ctx_cfg = { - .name = "Context Config", - .ops = - ocf_ctx_ops{ - .data = - ocf_data_ops{ - .alloc = ctx_data_alloc, - .free = ctx_data_free, - .mlock = ctx_data_mlock, - .munlock = ctx_data_munlock, - .read = ctx_data_read, - .write = ctx_data_write, - .zero = ctx_data_zero, - .seek = ctx_data_seek, - .copy = ctx_data_copy, - .secure_erase = ctx_data_secure_erase, - }, - - .cleaner = - ocf_cleaner_ops{ - .init = ctx_cleaner_init, - .kick = ctx_cleaner_kick, - .stop = ctx_cleaner_stop, - }, - - .logger = - ocf_logger_ops{ - .open = nullptr, - .close = nullptr, - .print = ctx_logger_print, - .print_rl = nullptr, - .dump_stack = nullptr, - }, - }, - .logger_priv = nullptr, -}; - -const ocf_ctx_config *get_context_config() { - return &ctx_cfg; -} diff --git a/src/overlaybd/cache/ocf_cache/ease_bindings/ctx.h b/src/overlaybd/cache/ocf_cache/ease_bindings/ctx.h deleted file mode 100644 index b31afbe4..00000000 --- a/src/overlaybd/cache/ocf_cache/ease_bindings/ctx.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include "../ocf_namespace.h" - -extern "C" { -#include -} - -#define ROUND_UP(N, S) ((((N) + (S)-1) / (S)) * (S)) -#define ROUND_DOWN(N, S) ((N) & ~((S)-1)) - -class ease_ocf_provider; - -/* Src file context */ -struct OcfSrcFileCtx { - OcfSrcFileCtx(photon::fs::IFile *src_file_, const OcfNamespace::NsInfo &ns_info_, - ease_ocf_provider *provider_, const estring &path_) - : src_file(src_file_), ns_info(ns_info_), provider(provider_), path(path_) { - } - - ~OcfSrcFileCtx() { - delete src_file; - } - - photon::fs::IFile *src_file; - OcfNamespace::NsInfo ns_info; - ease_ocf_provider *provider; - estring path; -}; - -/* IO data */ -struct ease_ocf_io_data { - ease_ocf_io_data() = default; - - ease_ocf_io_data(iovec *iovs_, int iovcnt_, uint32_t size_, off_t blk_addr_, - OcfSrcFileCtx *ctx_, bool prefetch_) - : iovs(iovs_), iovcnt(iovcnt_), size(size_), blk_addr(blk_addr_), ctx(ctx_), - prefetch(prefetch_) { - } - - /* basic IO related */ - iovec *iovs = nullptr; - int iovcnt = 0; - uint32_t size = 0; - uint32_t seek = 0; - - /* ease related */ - off_t blk_addr = -1; - OcfSrcFileCtx *ctx = nullptr; - int err_no = 0; - photon::semaphore sem; - bool prefetch = false; -}; - -struct ease_ocf_config { - /* Initial cache configuration */ - ocf_mngt_cache_config cache; - - /* Cache device config */ - ocf_mngt_cache_device_config device; - - /* Core initial config */ - ocf_mngt_core_config core; -}; - -struct ease_ocf_queue { - ocf_queue_t mngt_queue; - ocf_queue_t io_queue; -}; - -/* Context config */ -const ocf_ctx_config *get_context_config(); diff --git a/src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env.cpp b/src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env.cpp deleted file mode 100644 index 5eb33bb1..00000000 --- a/src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env.cpp +++ /dev/null @@ -1,355 +0,0 @@ -#include -#include - -#include -#include - -extern "C" { -#include "ocf_env.h" -} - -/* ALLOCATOR */ -struct _env_allocator { - /*!< Memory pool ID unique name */ - char *name; - - /*!< Size of specific item of memory pool */ - uint32_t item_size; - - /*!< Number of currently allocated items in pool */ - env_atomic count; -}; - -struct _env_allocator_item { - uint32_t flags; - uint32_t cpu; - char data[]; -}; - -void *env_allocator_new(env_allocator *allocator) -{ - auto item = (_env_allocator_item *)calloc(1, allocator->item_size); - - if (item) { - item->cpu = 0; - env_atomic_inc(&allocator->count); - } - - return &item->data; -} - -env_allocator *env_allocator_create(uint32_t size, const char *fmt_name, ...) -{ - char name[OCF_ALLOCATOR_NAME_MAX] = { '\0' }; - int result, error = -1; - va_list args; - - auto allocator = (env_allocator *) calloc(1, sizeof(env_allocator)); - if (!allocator) { - error = __LINE__; - goto err; - } - - allocator->item_size = size + sizeof(struct _env_allocator_item); - - /* Format allocator name */ - va_start(args, fmt_name); - result = vsnprintf(name, sizeof(name), fmt_name, args); - va_end(args); - - if ((result > 0) && (result < (int)sizeof(name))) { - allocator->name = strdup(name); - - if (!allocator->name) { - error = __LINE__; - goto err; - } - } else { - /* Formated string name exceed max allowed size of name */ - error = __LINE__; - goto err; - } - - return allocator; - -err: - printf("Cannot create memory allocator, ERROR %d", error); - env_allocator_destroy(allocator); - - return NULL; -} - -void env_allocator_del(env_allocator *allocator, void *obj) -{ - _env_allocator_item *item = - container_of(obj, _env_allocator_item, data); - - env_atomic_dec(&allocator->count); - - free(item); -} - -void env_allocator_destroy(env_allocator *allocator) -{ - if (allocator) { - if (env_atomic_read(&allocator->count)) { - printf("Not all objects deallocated\n"); - ENV_WARN(true, OCF_PREFIX_SHORT" Cleanup problem\n"); - } - - free(allocator->name); - free(allocator); - } -} - -/* DEBUGING */ -#define ENV_TRACE_DEPTH 16 - -void env_stack_trace(void) -{ - void *trace[ENV_TRACE_DEPTH]; - char **messages = NULL; - int i, size; - - size = backtrace(trace, ENV_TRACE_DEPTH); - messages = backtrace_symbols(trace, size); - printf("[stack trace]>>>\n"); - for (i = 0; i < size; ++i) - printf("%s\n", messages[i]); - printf("<<<[stack trace]\n"); - free(messages); -} - -/* CRC */ -uint32_t env_crc32(uint32_t crc, uint8_t const *data, size_t len) -{ - return crc32(crc, data, len); -} - -/* EXECUTION CONTEXTS */ -#ifdef EXECUTION_CONTEXTS - -pthread_mutex_t *exec_context_mutex; - -static void __attribute__((constructor)) init_execution_context(void) -{ - unsigned count = env_get_execution_context_count(); - unsigned i; - - ENV_BUG_ON(count == 0); - exec_context_mutex = (pthread_mutex_t *)malloc(count * sizeof(exec_context_mutex[0])); - ENV_BUG_ON(exec_context_mutex == NULL); - for (i = 0; i < count; i++) - ENV_BUG_ON(pthread_mutex_init(&exec_context_mutex[i], NULL)); -} - -static void __attribute__((destructor)) deinit_execution_context(void) -{ - unsigned count = env_get_execution_context_count(); - unsigned i; - - ENV_BUG_ON(count == 0); - ENV_BUG_ON(exec_context_mutex == NULL); - - for (i = 0; i < count; i++) - ENV_BUG_ON(pthread_mutex_destroy(&exec_context_mutex[i])); - free(exec_context_mutex); -} - -/* get_execuction_context must assure that after the call finishes, the caller - * will not get preempted from current execution context. For userspace env - * we simulate this behavior by acquiring per execution context mutex. As a - * result the caller might actually get preempted, but no other thread will - * execute in this context by the time the caller puts current execution ctx. */ -unsigned env_get_execution_context(void) -{ - unsigned cpu; - - cpu = sched_getcpu(); - cpu = (cpu == -1U) ? 0 : cpu; - - ENV_BUG_ON(pthread_mutex_lock(&exec_context_mutex[cpu])); - - return cpu; -} - -void env_put_execution_context(unsigned ctx) -{ - pthread_mutex_unlock(&exec_context_mutex[ctx]); -} - -unsigned env_get_execution_context_count(void) -{ - int num = sysconf(_SC_NPROCESSORS_ONLN); - - return (num == -1) ? 0 : num; -} - -#endif - -void env_rwlock_init(env_rwlock *l) { - l->lock = new photon::rwlock; -} - -void env_rwlock_read_lock(env_rwlock *l) { - auto lock = (photon::rwlock*) l->lock; - lock->lock(photon::RLOCK); -} - -void env_rwlock_read_unlock(env_rwlock *l) { - auto lock = (photon::rwlock*) l->lock; - lock->unlock(); -} - -void env_rwlock_write_lock(env_rwlock *l) { - auto lock = (photon::rwlock*) l->lock; - lock->lock(photon::WLOCK); -} - -void env_rwlock_write_unlock(env_rwlock *l) { - auto lock = (photon::rwlock*) l->lock; - lock->unlock(); -} - -void env_rwlock_destroy(env_rwlock *l) { - auto lock = (photon::rwlock*) l->lock; - delete lock; -} - -void env_msleep(uint64_t n) { - photon::thread_usleep(n * 1000); -} - -int env_mutex_init(env_mutex* mutex) { - mutex->mutex = new photon::mutex; - return 0; -} - -void env_mutex_lock(env_mutex* mutex) { - auto m = (photon::mutex*) mutex->mutex; - m->lock(); -} - -int env_mutex_trylock(env_mutex* mutex) { - auto m = (photon::mutex*) mutex->mutex; - return m->try_lock(); -} - -void env_mutex_unlock(env_mutex* mutex) { - auto m = (photon::mutex*) mutex->mutex; - m->unlock(); -} - -int env_mutex_destroy(env_mutex* mutex) { - auto m = (photon::mutex*) mutex->mutex; - delete m; - return 0; -} - -void env_completion_init(env_completion* completion) { - auto sem = new photon::semaphore; - completion->sem = sem; -} - -void env_completion_wait(env_completion* completion) { - auto sem = (photon::semaphore*) completion->sem; - sem->wait(1); -} - -void env_completion_complete(env_completion* completion) { - auto sem = (photon::semaphore*) completion->sem; - sem->signal(1); -} - -void env_completion_destroy(env_completion* completion) { - auto sem = (photon::semaphore*) completion->sem; - delete sem; -} - -int env_rmutex_init(env_rmutex* rmutex) { - rmutex->rmutex = new photon::recursive_mutex; - return 0; -} - -void env_rmutex_lock(env_rmutex* rmutex) { - auto m = (photon::recursive_mutex*) rmutex->rmutex; - m->lock(); -} - -void env_rmutex_unlock(env_rmutex* rmutex) { - auto m = (photon::recursive_mutex*) rmutex->rmutex; - m->unlock(); -} - -int env_rmutex_destroy(env_rmutex* rmutex) { - auto m = (photon::recursive_mutex*) rmutex->rmutex; - delete m; - return 0; -} - -int env_spinlock_init(env_spinlock* l) { - l->lock = new photon::mutex; - return 0; -} - -int env_spinlock_trylock(env_spinlock* l) { - auto lock = (photon::mutex*) l->lock; - return lock->try_lock() ? -OCF_ERR_NO_LOCK : 0; -} - -void env_spinlock_lock(env_spinlock* l) { - auto lock = (photon::mutex*) l->lock; - lock->lock(); -} - -void env_spinlock_unlock(env_spinlock* l) { - auto lock = (photon::mutex*) l->lock; - lock->unlock(); -} - -void env_spinlock_destroy(env_spinlock* l) { - auto m = (photon::mutex*) l->lock; - delete m; -} - -int env_rwsem_init(env_rwsem* s) { - s->lock = new photon::mutex; - return 0; -} - -void env_rwsem_up_read(env_rwsem* s) { - auto lock = (photon::mutex*) s->lock; - lock->unlock(); -} - -void env_rwsem_down_read(env_rwsem* s) { - auto lock = (photon::mutex*) s->lock; - lock->lock(); -} - -int env_rwsem_down_read_trylock(env_rwsem* s) { - auto lock = (photon::mutex*) s->lock; - return lock->try_lock() ? -OCF_ERR_NO_LOCK : 0; - // return 0; -} - -void env_rwsem_up_write(env_rwsem* s) { - auto lock = (photon::mutex*) s->lock; - lock->unlock(); -} - -void env_rwsem_down_write(env_rwsem* s) { - auto lock = (photon::mutex*) s->lock; - lock->lock(); -} - -int env_rwsem_down_write_trylock(env_rwsem* s) { - auto lock = (photon::mutex*) s->lock; - return lock->try_lock() ? -OCF_ERR_NO_LOCK : 0; -} - -int env_rwsem_destroy(env_rwsem* s) { - auto m = (photon::mutex*) s->lock; - delete m; - return 0; -} \ No newline at end of file diff --git a/src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env.h b/src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env.h deleted file mode 100644 index c31da559..00000000 --- a/src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env.h +++ /dev/null @@ -1,504 +0,0 @@ -#ifndef __OCF_ENV_H__ -#define __OCF_ENV_H__ - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#ifndef __USE_GNU -#define __USE_GNU -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ocf_env_list.h" -#include "ocf_env_headers.h" -#include "ocf/ocf_err.h" -#include "utils_mpool.h" - -/* linux sector 512-bytes */ -#define ENV_SECTOR_SHIFT 9 - -#define OCF_ALLOCATOR_NAME_MAX 128 - -#define PAGE_SIZE 4096 - -#define min(a,b) MIN(a,b) - -#define ENV_PRIu64 "lu" - -typedef uint8_t u8; -typedef uint16_t u16; -typedef uint32_t u32; -typedef uint64_t u64; - -typedef uint64_t sector_t; - -#define __packed __attribute__((packed)) - -#define likely(cond) __builtin_expect(!!(cond), 1) -#define unlikely(cond) __builtin_expect(!!(cond), 0) - -/* MEMORY MANAGEMENT */ -#define ENV_MEM_NORMAL 0 -#define ENV_MEM_NOIO 0 -#define ENV_MEM_ATOMIC 0 - -/* DEBUGING */ -#define ENV_WARN(cond, fmt...) printf(fmt) -#define ENV_WARN_ON(cond) ; -#define ENV_WARN_ONCE(cond, fmt...) ENV_WARN(cond, fmt) - -#define ENV_BUG() assert(0) -#define ENV_BUG_ON(cond) do { if (cond) ENV_BUG(); } while (0) -#define ENV_BUILD_BUG_ON(cond) _Static_assert(!(cond), "static "\ - "assertion failure") - -/* MISC UTILITIES */ -#ifdef __cplusplus -#define container_of(ptr, type, member) ((type*)((char*)static_castmember)*>(ptr) - offsetof(type,member))) -#else -#define container_of(ptr, type, member) ({ \ - const typeof(((type *)0)->member)*__mptr = (ptr); \ - (type *)((char *)__mptr - offsetof(type, member)); }) -#endif - -#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) - -/* STRING OPERATIONS */ -#define env_memcpy(dest, dmax, src, slen) ({ \ - memcpy(dest, src, MIN(dmax, slen)); \ - 0; \ - }) -#define env_memset(dest, dmax, val) ({ \ - memset(dest, val, dmax); \ - 0; \ - }) -#define env_memcmp(s1, s1max, s2, s2max, diff) ({ \ - *diff = memcmp(s1, s2, MIN(s1max, s2max)); \ - 0; \ - }) -#define env_strdup strndup -#define env_strnlen(s, smax) strnlen(s, smax) -#define env_strncmp(s1, slen1, s2, slen2) strncmp(s1, s2, MIN(slen1, slen2)) -#define env_strncpy(dest, dmax, src, slen) ({ \ - strncpy(dest, src, MIN(dmax - 1, slen)); \ - dest[dmax - 1] = '\0'; \ - 0; \ - }) - -/* MEMORY MANAGEMENT */ -static inline void *env_malloc(size_t size, int flags) -{ - return malloc(size); -} - -static inline void *env_zalloc(size_t size, int flags) -{ - void *ptr = malloc(size); - - if (ptr) - memset(ptr, 0, size); - - return ptr; -} - -static inline void env_free(const void *ptr) -{ - free((void *)ptr); -} - -static inline void *env_vmalloc_flags(size_t size, int flags) -{ - return malloc(size); -} - -static inline void *env_vzalloc_flags(size_t size, int flags) -{ - return env_zalloc(size, 0); -} - -static inline void *env_vmalloc(size_t size) -{ - return malloc(size); -} - -static inline void *env_vzalloc(size_t size) -{ - return env_zalloc(size, 0); -} - -static inline void env_vfree(const void *ptr) -{ - free((void *)ptr); -} - -/* SECURE MEMORY MANAGEMENT */ -/* - * OCF adapter can opt to take additional steps to securely allocate and free - * memory used by OCF to store cache metadata. This is to prevent other - * entities in the system from acquiring parts of OCF cache metadata via - * memory allocations. If this is not a concern in given product, secure - * alloc/free should default to vmalloc/vfree. - * - * Memory returned from secure alloc is not expected to be physically continous - * nor zeroed. - */ - -/* default to standard memory allocations for secure allocations */ -#define SECURE_MEMORY_HANDLING 0 - -static inline void *env_secure_alloc(size_t size) -{ - void *ptr = malloc(size); - -#if SECURE_MEMORY_HANDLING - if (ptr && mlock(ptr, size)) { - free(ptr); - ptr = NULL; - } -#endif - - return ptr; -} - -static inline void env_secure_free(const void *ptr, size_t size) -{ - if (ptr) { -#if SECURE_MEMORY_HANDLING - memset(ptr, size, 0); - /* TODO: flush CPU caches ? */ - ENV_BUG_ON(munlock(ptr)); -#endif - free((void*)ptr); - } -} - -static inline uint64_t env_get_free_memory(void) -{ - return (uint64_t)(-1); -} - -/* ALLOCATOR */ -typedef struct _env_allocator env_allocator; - -env_allocator *env_allocator_create(uint32_t size, const char *fmt_name, ...); - -#define env_allocator_create_extended(size, name, limit, zero) \ - env_allocator_create(size, name, zero) - -void env_allocator_destroy(env_allocator *allocator); - -void *env_allocator_new(env_allocator *allocator); - -void env_allocator_del(env_allocator *allocator, void *item); - -/* MUTEX */ -typedef struct { - void* mutex; -} env_mutex; - -#define env_cond_resched() ({}) - -int env_mutex_init(env_mutex *mutex); - -void env_mutex_lock(env_mutex *mutex); - -int env_mutex_trylock(env_mutex *mutex); - -void env_mutex_unlock(env_mutex *mutex); - -int env_mutex_destroy(env_mutex *mutex); - -/* RECURSIVE MUTEX */ -typedef struct { - void* rmutex; -} env_rmutex; - -int env_rmutex_init(env_rmutex* rmutex); - -void env_rmutex_lock(env_rmutex* rmutex); - -void env_rmutex_unlock(env_rmutex* rmutex); - -int env_rmutex_destroy(env_rmutex* rmutex); - -static inline int env_rmutex_lock_interruptible(env_rmutex* rmutex) { - env_rmutex_lock(rmutex); - return 0; -} - -/* RW LOCKS */ -typedef struct { - void* lock; -} env_rwlock; - -void env_rwlock_init(env_rwlock *l); - -void env_rwlock_read_lock(env_rwlock *l); - -void env_rwlock_read_unlock(env_rwlock *l); - -void env_rwlock_write_lock(env_rwlock *l); - -void env_rwlock_write_unlock(env_rwlock *l); - -void env_rwlock_destroy(env_rwlock *l); - -/* RW SEMAPHORE */ -typedef struct { - void* lock; -} env_rwsem; - -int env_rwsem_init(env_rwsem* s); - -void env_rwsem_up_read(env_rwsem* s); - -void env_rwsem_down_read(env_rwsem* s); - -int env_rwsem_down_read_trylock(env_rwsem* s); - -void env_rwsem_up_write(env_rwsem* s); - -void env_rwsem_down_write(env_rwsem* s); - -int env_rwsem_down_write_trylock(env_rwsem* s); - -int env_rwsem_destroy(env_rwsem* s); - -/* COMPLETION */ -typedef struct { - void* sem; -} env_completion; - -void env_completion_init(env_completion *completion); - -void env_completion_wait(env_completion *completion); - -void env_completion_complete(env_completion *completion); - -void env_completion_destroy(env_completion *completion); - -/* ATOMIC VARIABLES */ -typedef struct { - volatile int counter; -} env_atomic; - -typedef struct { - volatile long counter; -} env_atomic64; - -static inline int env_atomic_read(const env_atomic* a) { - return a->counter; /* TODO */ -} - -static inline void env_atomic_set(env_atomic* a, int i) { - a->counter = i; /* TODO */ -} - -static inline void env_atomic_add(int i, env_atomic* a) { - __sync_add_and_fetch(&a->counter, i); -} - -static inline void env_atomic_sub(int i, env_atomic* a) { - __sync_sub_and_fetch(&a->counter, i); -} - -static inline void env_atomic_inc(env_atomic* a) { - env_atomic_add(1, a); -} - -static inline void env_atomic_dec(env_atomic* a) { - env_atomic_sub(1, a); -} - -static inline bool env_atomic_dec_and_test(env_atomic* a) { - return __sync_sub_and_fetch(&a->counter, 1) == 0; -} - -static inline int env_atomic_add_return(int i, env_atomic* a) { - return __sync_add_and_fetch(&a->counter, i); -} - -static inline int env_atomic_sub_return(int i, env_atomic* a) { - return __sync_sub_and_fetch(&a->counter, i); -} - -static inline int env_atomic_inc_return(env_atomic* a) { - return env_atomic_add_return(1, a); -} - -static inline int env_atomic_dec_return(env_atomic* a) { - return env_atomic_sub_return(1, a); -} - -static inline int env_atomic_cmpxchg(env_atomic* a, int old, int new_value) { - return __sync_val_compare_and_swap(&a->counter, old, new_value); -} - -static inline int env_atomic_add_unless(env_atomic* a, int i, int u) { - int c, old; - c = env_atomic_read(a); - for (;;) { - if (unlikely(c == (u))) - break; - old = env_atomic_cmpxchg((a), c, c + (i)); - if (likely(old == c)) - break; - c = old; - } - return c != (u); -} - -static inline long env_atomic64_read(const env_atomic64* a) { - return a->counter; /* TODO */ -} - -static inline void env_atomic64_set(env_atomic64* a, long i) { - a->counter = i; /* TODO */ -} - -static inline void env_atomic64_add(long i, env_atomic64* a) { - __sync_add_and_fetch(&a->counter, i); -} - -static inline void env_atomic64_sub(long i, env_atomic64* a) { - __sync_sub_and_fetch(&a->counter, i); -} - -static inline void env_atomic64_inc(env_atomic64* a) { - env_atomic64_add(1, a); -} - -static inline void env_atomic64_dec(env_atomic64* a) { - env_atomic64_sub(1, a); -} - -static inline long env_atomic64_inc_return(env_atomic64* a) { - return __sync_add_and_fetch(&a->counter, 1); -} - -static inline long env_atomic64_cmpxchg(env_atomic64* a, long old_v, long new_v) { - return __sync_val_compare_and_swap(&a->counter, old_v, new_v); -} - -/* SPIN LOCKS */ -typedef struct { - void* lock; -} env_spinlock; - -int env_spinlock_init(env_spinlock* l); - -int env_spinlock_trylock(env_spinlock* l); - -void env_spinlock_lock(env_spinlock* l); - -void env_spinlock_unlock(env_spinlock* l); - -#define env_spinlock_lock_irqsave(l, flags) \ - (void)flags; \ - env_spinlock_lock(l) - -#define env_spinlock_unlock_irqrestore(l, flags) \ - (void)flags; \ - env_spinlock_unlock(l) - -void env_spinlock_destroy(env_spinlock* l); - -/* BIT OPERATIONS */ -static inline void env_bit_set(int nr, volatile void *addr) -{ - char *byte = (char *)addr + (nr >> 3); - char mask = 1 << (nr & 7); - - __sync_or_and_fetch(byte, mask); -} - -static inline void env_bit_clear(int nr, volatile void *addr) -{ - char *byte = (char *)addr + (nr >> 3); - char mask = 1 << (nr & 7); - - mask = ~mask; - __sync_and_and_fetch(byte, mask); -} - -static inline bool env_bit_test(int nr, const volatile unsigned long *addr) -{ - const char *byte = (char *)addr + (nr >> 3); - char mask = 1 << (nr & 7); - - return !!(*byte & mask); -} - -/* SCHEDULING */ -static inline int env_in_interrupt(void) { - return 0; -} - -static inline uint64_t env_get_tick_count(void) { - struct timeval tv; - gettimeofday(&tv, NULL); - return tv.tv_sec * 1000000 + tv.tv_usec; -} - -static inline uint64_t env_ticks_to_nsecs(uint64_t j) { - return j * 1000; -} - -static inline uint64_t env_ticks_to_msecs(uint64_t j) { - return j / 1000; -} - -static inline uint64_t env_ticks_to_secs(uint64_t j) { - return j / 1000000; -} - -static inline uint64_t env_secs_to_ticks(uint64_t j) { - return j * 1000000; -} - -/* SORTING */ -static inline void env_sort(void* base, size_t num, size_t size, - int (* cmp_fn)(const void*, const void*), - void (* swap_fn)(void*, void*, int size)) { - qsort(base, num, size, cmp_fn); -} - -/* TIME */ -void env_msleep(uint64_t n); - -struct env_timeval { - uint64_t sec, usec; -}; - -uint32_t env_crc32(uint32_t crc, uint8_t const *data, size_t len); - -/* EXECUTION CONTEXTS */ -static inline unsigned env_get_execution_context(void) { - return 0; -} - -static inline void env_put_execution_context(unsigned ctx) { -} - -static inline unsigned env_get_execution_context_count(void) { - return 1; -} - -#endif /* __OCF_ENV_H__ */ diff --git a/src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env_headers.h b/src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env_headers.h deleted file mode 100644 index 2016b057..00000000 --- a/src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env_headers.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef __OCF_ENV_HEADERS_H__ -#define __OCF_ENV_HEADERS_H__ - -#include -#include -#include - -/* TODO: Move prefix printing to context logger. */ -#define OCF_LOGO "OCF" -#define OCF_PREFIX_SHORT "[" OCF_LOGO "] " -#define OCF_PREFIX_LONG "Open CAS Framework" - -#define OCF_VERSION_MAIN 20 -#define OCF_VERSION_MAJOR 3 -#define OCF_VERSION_MINOR 0 - -#endif /* __OCF_ENV_HEADERS_H__ */ diff --git a/src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env_list.h b/src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env_list.h deleted file mode 100644 index bf1bd056..00000000 --- a/src/overlaybd/cache/ocf_cache/ease_bindings/env/ocf_env_list.h +++ /dev/null @@ -1,163 +0,0 @@ -#ifndef __OCF_ENV_LIST__ -#define __OCF_ENV_LIST__ - -#define LIST_POISON1 ((void *)0x101) -#define LIST_POISON2 ((void *)0x202) - -/** - * List entry structure mimicking linux kernel based one. - */ -struct list_head { - struct list_head *next; - struct list_head *prev; -}; - -/** - * start an empty list - */ -#define INIT_LIST_HEAD(l) { (l)->prev = l; (l)->next = l; } - -/** - * Add item to list head. - * @param it list entry to be added - * @param l1 list main node (head) - */ -static inline void list_add(struct list_head *it, struct list_head *l1) -{ - it->prev = l1; - it->next = l1->next; - - l1->next->prev = it; - l1->next = it; -} - -/** - * Add item it to tail. - * @param it list entry to be added - * @param l1 list main node (head) - */ -static inline void list_add_tail(struct list_head *it, struct list_head *l1) -{ - it->prev = l1->prev; - it->next = l1; - - l1->prev->next = it; - l1->prev = it; -} - -/** - * check if a list is empty (return true) - * @param l1 list main node (head) - */ -static inline int list_empty(struct list_head *l1) -{ - return l1->next == l1; -} - -/** - * delete an entry from a list - * @param it list entry to be deleted - */ -static inline void list_del(struct list_head *it) -{ - it->next->prev = it->prev; - it->prev->next = it->next; -} - -/** - * Move element to list head. - * @param it list entry to be moved - * @param l1 list main node (head) - */ -static inline void list_move(struct list_head *it, struct list_head *l1) -{ - list_del(it); - list_add(it, l1); -} - -/** - * Move element to list tail. - * @param it list entry to be moved - * @param l1 list main node (head) - */ -static inline void list_move_tail(struct list_head *it, struct list_head *l1) -{ - list_del(it); - list_add_tail(it, l1); -} - -/** - * Extract an entry. - * @param list_head_i list head item, from which entry is extracted - * @param item_type type (struct) of list entry - * @param field_name name of list_head field within item_type - */ -#define list_entry(list_head_i, item_type, field_name) \ - (item_type *)(((void*)(list_head_i)) - offsetof(item_type, field_name)) - -#define list_first_entry(list_head_i, item_type, field_name) \ - list_entry((list_head_i)->next, item_type, field_name) - -/** - * @param iterator uninitialized list_head pointer, to be used as iterator - * @param plist list head (main node) - */ -#define list_for_each(iterator, plist) \ - for (iterator = (plist)->next; \ - (iterator)->next != (plist)->next; \ - iterator = (iterator)->next) - -/** - * Safe version of list_for_each which works even if entries are deleted during - * loop. - * @param iterator uninitialized list_head pointer, to be used as iterator - * @param q another uninitialized list_head, used as helper - * @param plist list head (main node) - */ -/* - * Algorithm handles situation, where q is deleted. - * consider in example 3 element list with header h: - * - * h -> 1 -> 2 -> 3 -> - *1. i q - * - *2. i q - * - *3. q i - */ -#define list_for_each_safe(iterator, q, plist) \ - for (iterator = (q = (plist)->next->next)->prev; \ - (q) != (plist)->next; \ - iterator = (q = (q)->next)->prev) - -#define _list_entry_helper(item, head, field_name) \ - list_entry(head, typeof(*item), field_name) - -/** - * Iterate over list entries. - * @param list pointer to list item (iterator) - * @param plist pointer to list_head item - * @param field_name name of list_head field in list entry - */ -#define list_for_each_entry(item, plist, field_name) \ - for (item = _list_entry_helper(item, (plist)->next, field_name); \ - _list_entry_helper(item, (item)->field_name.next, field_name) !=\ - _list_entry_helper(item, (plist)->next, field_name); \ - item = _list_entry_helper(item, (item)->field_name.next, field_name)) - -/** - * Safe version of list_for_each_entry which works even if entries are deleted - * during loop. - * @param list pointer to list item (iterator) - * @param q another pointer to list item, used as helper - * @param plist pointer to list_head item - * @param field_name name of list_head field in list entry - */ -#define list_for_each_entry_safe(item, q, plist, field_name) \ - for (item = _list_entry_helper(item, (plist)->next, field_name), \ - q = _list_entry_helper(item, (item)->field_name.next, field_name); \ - _list_entry_helper(item, (item)->field_name.next, field_name) != \ - _list_entry_helper(item, (plist)->next, field_name); \ - item = q, q = _list_entry_helper(q, (q)->field_name.next, field_name)) - -#endif // __OCF_ENV_LIST__ diff --git a/src/overlaybd/cache/ocf_cache/ease_bindings/env/utils_mpool.cpp b/src/overlaybd/cache/ocf_cache/ease_bindings/env/utils_mpool.cpp deleted file mode 100644 index 9c6ff600..00000000 --- a/src/overlaybd/cache/ocf_cache/ease_bindings/env/utils_mpool.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#ifdef __cplusplus -extern "C" { -#include "ocf_env.h" -#include "utils_mpool.h" -} -#else -#include "ocf_env.h" -#include "utils_mpool.h" -#endif - -struct env_mpool { - int mpool_max; - /*!< Max mpool allocation order */ - - env_allocator *allocator[env_mpool_max]; - /*!< OS handle to memory pool */ - - uint32_t hdr_size; - /*!< Data header size (constant allocation part) */ - - uint32_t elem_size; - /*!< Per element size increment (variable allocation part) */ - - bool fallback; - /*!< Should mpool fallback to vmalloc */ - - int flags; - /*!< Allocation flags */ -}; - -struct env_mpool *env_mpool_create(uint32_t hdr_size, uint32_t elem_size, - int flags, int mpool_max, bool fallback, - const uint32_t limits[env_mpool_max], - const char *name_perfix, - bool zero) -{ - uint32_t i; - char name[MPOOL_ALLOCATOR_NAME_MAX] = { '\0' }; - int result; - struct env_mpool *mpool; - size_t size; - - mpool = (env_mpool *)env_zalloc(sizeof(*mpool), ENV_MEM_NORMAL); - if (!mpool) - return NULL; - - mpool->flags = flags; - mpool->fallback = fallback; - mpool->mpool_max = mpool_max; - mpool->hdr_size = hdr_size; - mpool->elem_size = elem_size; - - for (i = 0; i < (uint32_t) min(env_mpool_max, mpool_max + 1); i++) { - result = snprintf(name, sizeof(name), "%s_%u", name_perfix, - (1 << i)); - if (result < 0 || result >= (int) sizeof(name)) - goto err; - - size = hdr_size + (elem_size * (1 << i)); - - mpool->allocator[i] = env_allocator_create_extended( - size, name, limits ? limits[i] : -1, - zero); - - if (!mpool->allocator[i]) - goto err; - } - - return mpool; - -err: - env_mpool_destroy(mpool); - return NULL; -} - -void env_mpool_destroy(struct env_mpool *mallocator) -{ - if (mallocator) { - uint32_t i; - - for (i = 0; i < env_mpool_max; i++) - if (mallocator->allocator[i]) - env_allocator_destroy(mallocator->allocator[i]); - - env_free(mallocator); - } -} - -static env_allocator *env_mpool_get_allocator( - struct env_mpool *mallocator, uint32_t count) -{ - unsigned int idx; - - if (unlikely(count == 0)) - return nullptr; - - idx = 31 - __builtin_clz(count); - - if (__builtin_ffs(count) <= idx) - idx++; - - if (idx >= env_mpool_max || idx > (unsigned int) mallocator->mpool_max) - return NULL; - - return mallocator->allocator[idx]; -} - -void *env_mpool_new_f(struct env_mpool *mpool, uint32_t count, int flags) -{ - void *items = NULL; - env_allocator *allocator; - size_t size = mpool->hdr_size + (mpool->elem_size * count); - - allocator = env_mpool_get_allocator(mpool, count); - - if (allocator) { - items = env_allocator_new(allocator); - } else if(mpool->fallback) { - items = env_zalloc(size, 0); - } - -#ifdef ZERO_OR_NULL_PTR - if (ZERO_OR_NULL_PTR(items)) - return NULL; -#endif - - return items; -} - -void *env_mpool_new(struct env_mpool *mpool, uint32_t count) -{ - return env_mpool_new_f(mpool, count, mpool->flags); -} - -bool env_mpool_del(struct env_mpool *mpool, - void *items, uint32_t count) -{ - env_allocator *allocator; - - allocator = env_mpool_get_allocator(mpool, count); - - if (allocator) - env_allocator_del(allocator, items); - else if (mpool->fallback) - env_free(items); - else - return false; - - return true; -} diff --git a/src/overlaybd/cache/ocf_cache/ease_bindings/env/utils_mpool.h b/src/overlaybd/cache/ocf_cache/ease_bindings/env/utils_mpool.h deleted file mode 100644 index 315d8b8c..00000000 --- a/src/overlaybd/cache/ocf_cache/ease_bindings/env/utils_mpool.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef UTILS_MPOOL_H_ -#define UTILS_MPOOL_H_ - -#include -#include - -#define MPOOL_ALLOCATOR_NAME_MAX 128 - -enum { - env_mpool_1, - env_mpool_2, - env_mpool_4, - env_mpool_8, - env_mpool_16, - env_mpool_32, - env_mpool_64, - env_mpool_128, - - env_mpool_max -}; - -struct env_mpool; - -/** - * @brief Create CAS memory pool - * - * @param hdr_size size of constant allocation part - * @param elem_size size increment for each element - * @param flags Allocation flags - * @param mpool_max Maximal allocator size (power of two) - * @param fallback Should allocations fall back to vmalloc if allocator fails - * @param limits Array of rpool preallocation limits per each mpool allocation - * order or NULL if defaults are to be used. Array should have - * mpool_max elements - * @param name_prefix Format name prefix - * - * @return CAS memory pool - */ -struct env_mpool *env_mpool_create(uint32_t hdr_size, uint32_t elem_size, - int flags, int mpool_max, bool fallback, - const uint32_t limits[env_mpool_max], - const char *name_perfix, bool zero); - -/** - * @brief Destroy existing memory pool - * - * @param mpool memory pool - */ -void env_mpool_destroy(struct env_mpool *mpool); - -/** - * @brief Allocate new items of memory pool - * - * @note Allocation based on ATOMIC memory pool and this function can be called - * when IRQ disable - * - * @param mpool CAS memory pool reference - * @param count Count of elements to be allocated - * - * @return Pointer to the new items - */ -void *env_mpool_new(struct env_mpool *mpool, uint32_t count); - -/** - * @brief Allocate new items of memory pool with specified allocation flag - * - * @param mpool CAS memory pool reference - * @param count Count of elements to be allocated - * @param flags Kernel allocation falgs - * - * @return Pointer to the new items - */ -void *env_mpool_new_f(struct env_mpool *mpool, uint32_t count, int flags); - -/** - * @brief Free existing items of memory pool - * - * @param mpool CAS memory pool reference - * @param items Items to be freed - * @param count - Count of elements to be free - * - * @return Allocation was freed - */ -bool env_mpool_del(struct env_mpool *mpool, void *items, uint32_t count); - -#endif /* UTILS_MPOOL_H_ */ diff --git a/src/overlaybd/cache/ocf_cache/ease_bindings/provider.cpp b/src/overlaybd/cache/ocf_cache/ease_bindings/provider.cpp deleted file mode 100644 index 88da018b..00000000 --- a/src/overlaybd/cache/ocf_cache/ease_bindings/provider.cpp +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Note: Some OCF APIs are not being used under current circumstances, for example: - * ocf_mngt_cache_lock - * ocf_mngt_cache_unlock - * ocf_mngt_cache_flush - * ocf_mngt_core_flush - * ocf_mngt_cache_remove_core - * They might be added in the future, for advanced usages. - */ - -#include "provider.h" - -#include - - -extern IOAlloc *g_io_alloc; - -/* Callbacks - Start */ - -struct simple_context { - int error = 0; - photon::semaphore sem; -}; - -static void simple_complete(ocf_cache_t cache, void *priv, int error) { - auto context = (simple_context *)priv; - context->error = error; - context->sem.signal(1); -} - -struct add_core_context : public simple_context { - ocf_core_t core = nullptr; -}; - -static void add_core_complete(ocf_cache_t cache, ocf_core_t core, void *priv, int error) { - auto context = (add_core_context *)priv; - context->core = core; - context->error = error; - context->sem.signal(1); -} - -static void read_complete(ocf_io *io, int error) { - auto data = (ease_ocf_io_data *)ocf_io_get_data(io); - if (error != 0) { - auto error_ptr = (int *)io->priv2; - *error_ptr = error; - } - ocf_io_put(io); - data->sem.signal(1); -} - -/* Callbacks - End */ - -const size_t ease_ocf_provider::SectorSize = 512; - -int ease_ocf_provider::start(bool reload_media) { - /* Create context */ - auto ctx_cfg = get_context_config(); - int ret = ocf_ctx_create(&m_ctx, ctx_cfg); - if (ret != 0) { - return ret; - } - - /* Register volume */ - ret = volume_init(m_ctx); - if (ret != 0) { - ocf_ctx_put(m_ctx); - return ret; - } - - /* Configurations */ - ocf_mngt_cache_config_set_default(&m_cfg.cache); - ocf_mngt_cache_device_config_set_default(&m_cfg.device); - ocf_mngt_core_config_set_default(&m_cfg.core); - - m_cfg.cache.cache_mode = ocf_cache_mode_wt; - m_cfg.device.discard_on_start = false; - m_cfg.device.perform_test = false; - m_cfg.device.volume_params = m_volume_params; - m_cfg.device.cache_line_size = (ocf_cache_line_size_t)m_volume_params->blk_size; - m_cfg.cache.cache_line_size = (ocf_cache_line_size_t)m_volume_params->blk_size; - - strncpy(m_cfg.cache.name, CACHE_NAME, sizeof(m_cfg.cache.name)); - strncpy(m_cfg.core.name, CORE_NAME, sizeof(m_cfg.core.name)); - - ocf_uuid_set_str(&m_cfg.device.uuid, (char *)CACHE_UUID); - ocf_uuid_set_str(&m_cfg.core.uuid, (char *)CORE_UUID); - - m_cfg.device.volume_type = EASE_OCF_VOLUME_TYPE; - m_cfg.core.volume_type = EASE_OCF_VOLUME_TYPE; - - /* Start cache */ - ret = ocf_mngt_cache_start(m_ctx, &m_cache, &m_cfg.cache, nullptr); - if (ret != 0) { - LOG_ERROR("OCF: failed to start cache"); - return ret; - } - - /* Allocate and assign cache private data */ - m_queue = new ease_ocf_queue{}; - ocf_cache_set_priv(m_cache, m_queue); - - /* Create management queue */ - simple_context simple_ctx; - ret = ocf_queue_create(m_cache, &m_queue->mngt_queue, get_queue_ops()); - if (ret != 0) { - LOG_ERROR("OCF: failed to create management queue"); - return ret; - } - ocf_mngt_cache_set_mngt_queue(m_cache, m_queue->mngt_queue); - - /* Create IO submission queue */ - ret = ocf_queue_create(m_cache, &m_queue->io_queue, get_queue_ops()); - if (ret != 0) { - LOG_ERROR("OCF: failed to create io queue"); - return ret; - } - - init_queues(m_queue->mngt_queue, m_queue->io_queue); - - if (reload_media) { - /* Reload cache instance */ - ocf_mngt_cache_load(m_cache, &m_cfg.device, simple_complete, &simple_ctx); - } else { - /* Attach cache device to a new cache instance */ - ocf_mngt_cache_attach(m_cache, &m_cfg.device, simple_complete, &simple_ctx); - } - - simple_ctx.sem.wait(1); - if (simple_ctx.error != 0) { - LOG_ERROR("OCF: failed to reload/attach cache"); - return simple_ctx.error; - } - - if (reload_media) { - /* Core will be auto opened when reloading cache */ - ret = ocf_core_get_by_name(m_cache, CORE_NAME, strlen(CORE_NAME), &m_core); - if (ret != 0) { - LOG_ERROR_RETURN(0, ret, "OCF: get core by name failed"); - } - - } else { - /* Add new core to cache */ - add_core_context add_core_ctx; - ocf_mngt_cache_add_core(m_cache, &m_cfg.core, add_core_complete, &add_core_ctx); - - add_core_ctx.sem.wait(1); - if (add_core_ctx.error != 0) { - LOG_ERROR("OCF: failed to add core to cache"); - return add_core_ctx.error; - } else { - m_core = add_core_ctx.core; - } - } - m_volume_params->enable_logging = true; - LOG_INFO("OCF: OCF cache is ready, blk_size `, prefetch_unit `", m_volume_params->blk_size, - m_prefetch_unit); - return 0; -} - -int ease_ocf_provider::stop() { - LOG_INFO("OCF: OCF is going to stop ..."); - m_volume_params->enable_logging = false; - if (m_cache != nullptr) { - simple_context simple_ctx; - ocf_mngt_cache_stop(m_cache, simple_complete, &simple_ctx); - - simple_ctx.sem.wait(1); - if (simple_ctx.error != 0) { - LOG_ERROR_RETURN(0, -1, "OCF: failed to stop management cache, error = `", - simple_ctx.error); - } else { - LOG_DEBUG("OCF: succeeded to stop management cache"); - } - } - - if (m_queue != nullptr) { - ocf_queue_put(m_queue->mngt_queue); - LOG_DEBUG("OCF: done put management queue"); - } - - delete m_queue; - - if (m_ctx != nullptr) { - volume_cleanup(m_ctx); - ocf_ctx_put(m_ctx); - LOG_DEBUG("OCF: succeeded to destroy volume and context"); - } - LOG_INFO("OCF: OCF cache is fully stopped ..."); - return 0; -} - -void ease_ocf_provider::prepare_aligned_iov(size_t count, off_t offset, alignment &a, IOVector &iov, - const void *buf, void *padding_buf) { - // bound - a.lower_bound = ROUND_DOWN(offset, SectorSize); - a.upper_bound = ROUND_UP(offset + count, SectorSize); - - // padding - if (offset > a.lower_bound) { - a.lower_padding = offset - a.lower_bound; - } - if (offset + count < (size_t)a.upper_bound) { - a.upper_padding = a.upper_bound - offset - count; - } - - // middle - if (a.lower_padding != 0 && a.upper_padding != 0 && - a.upper_bound - a.lower_bound != (off_t)SectorSize) { - a.mid_count = ROUND_DOWN(a.upper_bound - a.lower_bound, SectorSize) - 2 * SectorSize; - } else { - a.mid_count = ROUND_DOWN(count, SectorSize); - } - - if (a.upper_bound - a.lower_bound == (off_t)SectorSize) { - // within one sector - iov.push_back(padding_buf, SectorSize); - } else { - // across multiple sectors - if (a.lower_padding != 0) { - // assign lower to iov - iov.push_back(padding_buf, SectorSize); - } - if (a.mid_count != 0) { - // assign middle sectors to iov - auto cur = (uint8_t *)buf; - if (a.lower_padding != 0) { - cur += SectorSize - a.lower_padding; - } - iov.push_back(cur, a.mid_count); - } - if (a.upper_padding != 0) { - // assign upper to iov - iov.push_back((uint8_t *)padding_buf + SectorSize, SectorSize); - } - } - LOG_DEBUG("bound `-`, padding `-`, mid `, iov cnt `, size `", a.lower_bound, a.upper_bound, - a.lower_padding, a.upper_padding, a.mid_count, iov.iovcnt(), iov.sum()); -} - -void ease_ocf_provider::copy_aligned_iov(size_t count, alignment &a, IOVector &iov, void *buf) { - if (a.upper_bound - a.lower_bound == (off_t)SectorSize) { - // within one sector - if (a.lower_padding != 0) { - iov.extract_front(a.lower_padding); - } - iov.memcpy_to(buf, count); - } else { - // across multiple sectors - auto cur = (uint8_t *)buf; - if (a.lower_padding != 0) { - // copy lower padding to buf - iov.extract_front(a.lower_padding); - iov.extract_front(SectorSize - a.lower_padding, buf); - cur += SectorSize - a.lower_padding; - } - if (a.upper_padding != 0) { - // skip middle, and copy upper padding to buf - if (a.mid_count > 0) { - cur += a.mid_count; - iov.extract_front(a.mid_count); - } - iov.extract_front(SectorSize - a.upper_padding, cur); - } - } -} - -ssize_t ease_ocf_provider::ocf_pread(void *buf, size_t count, off_t offset, size_t blk_addr, - OcfSrcFileCtx *ctx, bool prefetch) { - LOG_DEBUG("New IO: pread buf `, count `, offset `", buf, count, offset); - int error = 0; - IOVector iov; - alignment align{}; - void *padding_buf = nullptr; - - /* - * The padding_buf is used to store bilateral paddings. - * A not-null padding_buf indicates alignment is required. - */ - if (offset % SectorSize != 0 || count % SectorSize != 0) { - padding_buf = g_io_alloc->alloc(SectorSize * 2); - if (padding_buf == nullptr) { - LOG_ERRNO_RETURN(ENOMEM, -1, "OCF: failed to allocate padding buf"); - } - prepare_aligned_iov(count, offset, align, iov, buf, padding_buf); - } else { - iov.push_back(buf, count); - align.lower_bound = offset; - } - - DEFER({ - if (padding_buf != nullptr) - g_io_alloc->dealloc(padding_buf); - }); - - /* Create data */ - ease_ocf_io_data data(iov.iovec(), iov.iovcnt(), iov.sum(), blk_addr, ctx, prefetch); - - /* Create io */ - ocf_io *io = ocf_core_new_io(m_core, m_queue->io_queue, data.blk_addr + align.lower_bound, - (uint32_t)iov.sum(), OCF_READ, 0, 0); - if (io == nullptr) { - LOG_ERRNO_RETURN(ENOMEM, -1, "OCF: failed to create new IO, count `, offset `, blk_addr `", - iov.sum(), align.lower_bound, blk_addr); - } - - /* Set data by volume's interface, offset should be 0 */ - ocf_io_set_data(io, &data, 0); - /* Setup completion function */ - ocf_io_set_cmpl(io, nullptr, &error, read_complete); - /* Submit io */ - ocf_core_submit_io(io); - - /* Wait IO finished and check error */ - data.sem.wait(1); - if (error != 0) { - errno = data.err_no; - LOG_ERRNO_RETURN(0, -1, "OCF: IO error"); - } - - if (padding_buf != nullptr) { - copy_aligned_iov(count, align, iov, buf); - } - - LOG_DEBUG("Finish IO: pread buf `, count `, offset `", buf, count, offset); - return count; -} diff --git a/src/overlaybd/cache/ocf_cache/ease_bindings/provider.h b/src/overlaybd/cache/ocf_cache/ease_bindings/provider.h deleted file mode 100644 index 308e4ed0..00000000 --- a/src/overlaybd/cache/ocf_cache/ease_bindings/provider.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include -#include - -#include "ctx.h" -#include "queue.h" -#include "volume.h" - -class ease_ocf_provider { -public: - ease_ocf_provider(ease_ocf_volume_params *params, size_t prefetch_unit) - : m_volume_params(params), m_prefetch_unit(prefetch_unit) { - } - - int start(bool reload_media); - - int stop(); - - /** - * @brief Caller should guarantee that offset + count does not exceed the EOF. - * @return On success, count is returned. On error, other value is returned. - */ - ssize_t ocf_pread(void *buf, size_t count, off_t offset, size_t blk_addr, OcfSrcFileCtx *ctx, - bool prefetch = false); - - size_t prefetch_unit() const { - return m_prefetch_unit; - } - - static const size_t SectorSize; - -private: - static constexpr const char *CACHE_NAME = "Ease Cache"; - static constexpr const char *CORE_NAME = "Ease Core"; - static constexpr const char *CACHE_UUID = "cache"; - static constexpr const char *CORE_UUID = "core"; - - /* Main control context */ - ocf_ctx_t m_ctx = nullptr; - - /* Cache and Core objects */ - ocf_cache_t m_cache = nullptr; - ocf_core_t m_core = nullptr; - - /* Configurations */ - ease_ocf_config m_cfg = {}; - - /* Queue */ - ease_ocf_queue *m_queue = nullptr; - - /* Volume parameters */ - ease_ocf_volume_params *m_volume_params; // owned by external class - - size_t m_prefetch_unit; - - /* - * | | | | - * | | | | - * |< lower_padding >| |< mid_count >| |< upper_padding >| - * ----|-----------------------|-----[sectors ...]-----|-----------------------|---- - * | | | | - * lower_bound | | upper_bound - * offset offset + count - */ - struct alignment { - off_t lower_bound; - size_t lower_padding; - size_t mid_count; - size_t upper_padding; - off_t upper_bound; - }; - - static void prepare_aligned_iov(size_t count, off_t offset, alignment &a, IOVector &iov, - const void *buf, void *padding_buf); - - static void copy_aligned_iov(size_t count, alignment &a, IOVector &iov, void *buf); -}; \ No newline at end of file diff --git a/src/overlaybd/cache/ocf_cache/ease_bindings/queue.cpp b/src/overlaybd/cache/ocf_cache/ease_bindings/queue.cpp deleted file mode 100644 index 0dd49bd2..00000000 --- a/src/overlaybd/cache/ocf_cache/ease_bindings/queue.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "queue.h" - -#include -#include -#include - -static void *run(void *args) { - auto queue = (ocf_queue_t)args; - ocf_queue_run(queue); - return nullptr; -} - -/* Pooled queue kicker */ -class QueueKicker { -public: - explicit QueueKicker(ocf_queue_t queue) : m_queue(queue) { - } - QueueKicker(ocf_queue_t queue, size_t vcpu_num, int ev_engine, int io_engine, int mode) : m_queue(queue) { - work_pool = new photon::WorkPool(vcpu_num, ev_engine, io_engine, mode); - } - ~QueueKicker() { - delete work_pool; - } - - inline void kick() { - if (work_pool) { - work_pool->async_call(new auto([this](){ run(m_queue); })); - } else { - photon::thread_create(run, m_queue); - } - } - -private: - /* associated OCF queue */ - ocf_queue_t m_queue; - /* thread pool */ - photon::WorkPool* work_pool = nullptr; -}; - -int init_queues(ocf_queue_t mngt_queue, ocf_queue_t io_queue) { - auto mngt_queue_kicker = new QueueKicker(mngt_queue, 2, 0, 0, 64); - auto io_queue_kicker = new QueueKicker(io_queue, 4, photon::INIT_EVENT_EPOLL, photon::INIT_IO_LIBCURL, 64); - - ocf_queue_set_priv(mngt_queue, mngt_queue_kicker); - ocf_queue_set_priv(io_queue, io_queue_kicker); - return 0; -} - -/* Callback for OCF to kick the queue thread */ -static void queue_thread_kick(ocf_queue_t q) { - auto qk = (QueueKicker *)ocf_queue_get_priv(q); - qk->kick(); -} - -/* Callback for OCF to stop the queue thread */ -static void queue_thread_stop(ocf_queue_t q) { - auto qk = (QueueKicker *)ocf_queue_get_priv(q); - delete qk; -} - -/* Queue ops */ -static const ocf_queue_ops queue_ops = { - .kick = queue_thread_kick, - .kick_sync = nullptr, - .stop = queue_thread_stop, -}; - -const ocf_queue_ops *get_queue_ops() { - return &queue_ops; -} diff --git a/src/overlaybd/cache/ocf_cache/ease_bindings/queue.h b/src/overlaybd/cache/ocf_cache/ease_bindings/queue.h deleted file mode 100644 index 0019179d..00000000 --- a/src/overlaybd/cache/ocf_cache/ease_bindings/queue.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -extern "C" { -#include -} - -int init_queues(ocf_queue_t mngt_queue, ocf_queue_t io_queue); - -const ocf_queue_ops *get_queue_ops(); diff --git a/src/overlaybd/cache/ocf_cache/ease_bindings/volume.cpp b/src/overlaybd/cache/ocf_cache/ease_bindings/volume.cpp deleted file mode 100644 index 151e948f..00000000 --- a/src/overlaybd/cache/ocf_cache/ease_bindings/volume.cpp +++ /dev/null @@ -1,210 +0,0 @@ -#include "volume.h" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "provider.h" -#include "ctx.h" - -extern IOAlloc *g_io_alloc; - -struct ease_ocf_volume_io { - ease_ocf_io_data *data; - off_t offset; -}; - -// Cache volume and core volume share the same struct, uuid string may be cache or core. -// Cache volume has extra parameters used in `volume_open`. -struct ease_ocf_volume { - ocf_volume_uuid uuid; - ease_ocf_volume_params *cache_params; -}; - -static int prefetch_read(OcfSrcFileCtx *src_file_ctx, size_t count, off_t offset, off_t blk_addr) { - off_t new_offset = offset + count; - size_t new_count = src_file_ctx->provider->prefetch_unit(); - if (new_count == 0 || (size_t)new_offset >= src_file_ctx->ns_info.file_size) { - return 0; - } else if (new_offset + new_count > src_file_ctx->ns_info.file_size) { - new_count = - ROUND_UP(src_file_ctx->ns_info.file_size, ease_ocf_provider::SectorSize) - new_offset; - } - - LOG_DEBUG("OCF: Prefetch pread, count `, offset `", new_count, new_offset); - void *buf = g_io_alloc->alloc(new_count); - DEFER(g_io_alloc->dealloc(buf)); - auto ret = - src_file_ctx->provider->ocf_pread(buf, new_count, new_offset, blk_addr, src_file_ctx); - if (ret <= 0) { - LOG_ERRNO_RETURN(0, -1, "OCF: prefetch read failed") - } - return 0; -} - -static int volume_open(ocf_volume_t volume, void *volume_params) { - auto uuid = ocf_volume_get_uuid(volume); - auto vol = (ease_ocf_volume *)ocf_volume_get_priv(volume); - vol->uuid = *uuid; - - if (strncmp((const char *)vol->uuid.data, "core", vol->uuid.size) == 0) { - // For now the volume_open of core will always receive a nullptr volume_params, so blk_size - // can only be known from io_data. Maybe OCF will change it in the future. - assert(volume_params == nullptr); - } - if (volume_params != nullptr) { - vol->cache_params = (ease_ocf_volume_params *)volume_params; - } - return 0; -} - -// Do noting here. Let the upper caller to release resources -static void volume_close(ocf_volume_t volume) { -} - -static void volume_submit_io(struct ocf_io *io) { - auto vol = (ease_ocf_volume *)ocf_volume_get_priv(ocf_io_get_volume(io)); - auto vol_io = (ease_ocf_volume_io *)ocf_io_get_priv(io); - - IOVector local_iov(vol_io->data->iovs, vol_io->data->iovcnt); - if (vol_io->offset > 0) { - local_iov.extract_front(vol_io->offset); - } - if ((size_t)io->bytes < local_iov.sum()) { - local_iov.truncate(io->bytes); - } - - ssize_t ret; - if (strncmp((const char *)vol->uuid.data, "cache", vol->uuid.size) == 0) { - if (vol->cache_params->enable_logging) { - LOG_DEBUG("OCF cache `, buf `, count: `, offset: `", - io->dir == OCF_READ ? "read" : "write", local_iov.iovec()[0].iov_base, - local_iov.sum(), io->addr); - } - auto media_file = vol->cache_params->media_file; - if (io->dir == OCF_WRITE) { - ret = media_file->pwritev(local_iov.iovec(), local_iov.iovcnt(), io->addr); - } else { - ret = media_file->preadv(local_iov.iovec(), local_iov.iovcnt(), io->addr); - } - - } else { - auto src_file = vol_io->data->ctx; - if (vol_io->offset != 0 || src_file == nullptr) { - LOG_ERROR("OCF: core read with non-zero offset or null src_file, must be a bug"); - vol_io->data->err_no = EINVAL; - io->end(io, -1); - } - off_t offset = io->addr - vol_io->data->blk_addr; - LOG_DEBUG("OCF core `, buf `, count: `, offset: `", io->dir == OCF_READ ? "read" : "write", - local_iov.iovec()[0].iov_base, local_iov.sum(), offset); - if (io->dir == OCF_WRITE) { - ret = -1; - errno = ENOSYS; - } else { - if (vol_io->data->prefetch) { - photon::thread_create11(prefetch_read, vol_io->data->ctx, local_iov.sum(), offset, - vol_io->data->blk_addr); - } - SCOPE_AUDIT("download", AU_FILEOP(src_file->path, offset, ret)); - ret = src_file->src_file->preadv(local_iov.iovec(), local_iov.iovcnt(), offset); - } - } - - if (ret < 0) { - vol_io->data->err_no = errno; - io->end(io, -1); - } else if (ret == 0) { - LOG_ERROR("OCF: EOF should have been validated, must be a bug !"); - vol_io->data->err_no = EINVAL; - io->end(io, -1); - } else { - io->end(io, 0); - } -} - -/* - * We don't need to implement submit_flush(). Just complete io with success. - */ -static void volume_submit_flush(struct ocf_io *io) { - io->end(io, 0); -} - -/* - * We don't need to implement submit_discard(). Just complete io with success. - */ -static void volume_submit_discard(struct ocf_io *io) { - io->end(io, 0); -} - -static unsigned int volume_get_max_io_size(ocf_volume_t volume) { - return MiB; -} - -static uint64_t volume_get_length(ocf_volume_t volume) { - auto vol = (ease_ocf_volume *)ocf_volume_get_priv(volume); - auto uuid = ocf_volume_get_uuid(volume); - if (strncmp((const char *)uuid->data, "cache", uuid->size) == 0) { - return vol->cache_params->media_size; - } else if (strncmp((const char *)uuid->data, "core", uuid->size) == 0) { - return UINT64_MAX; - } - return 0; -} - -static int volume_io_set_data(ocf_io *io, ctx_data_t *data, uint32_t offset) { - auto vol_io = (ease_ocf_volume_io *)ocf_io_get_priv(io); - vol_io->data = (ease_ocf_io_data *)data; - vol_io->offset = offset; - return 0; -} - -static ctx_data_t *volume_io_get_data(ocf_io *io) { - auto vol_io = (ease_ocf_volume_io *)ocf_io_get_priv(io); - return vol_io->data; -} - -/* - * OCF uses a volume interface for accessing BOTH backend storage and cache storage - */ -const struct ocf_volume_properties volume_properties = { - .name = "OCF Volume", - .io_priv_size = sizeof(ease_ocf_volume_io), - .volume_priv_size = sizeof(struct ease_ocf_volume), - .caps = - { - .atomic_writes = 0, - }, - .io_ops = - { - .set_data = volume_io_set_data, - .get_data = volume_io_get_data, - }, - .deinit = nullptr, - .ops = - { - .submit_io = volume_submit_io, - .submit_flush = volume_submit_flush, - .submit_metadata = nullptr, - .submit_discard = volume_submit_discard, - .submit_write_zeroes = nullptr, - .open = volume_open, - .close = volume_close, - .get_length = volume_get_length, - .get_max_io_size = volume_get_max_io_size, - }, -}; - -int volume_init(ocf_ctx_t ocf_ctx) { - return ocf_ctx_register_volume_type(ocf_ctx, EASE_OCF_VOLUME_TYPE, &volume_properties); -} - -void volume_cleanup(ocf_ctx_t ocf_ctx) { - ocf_ctx_unregister_volume_type(ocf_ctx, EASE_OCF_VOLUME_TYPE); -} diff --git a/src/overlaybd/cache/ocf_cache/ease_bindings/volume.h b/src/overlaybd/cache/ocf_cache/ease_bindings/volume.h deleted file mode 100644 index d849e4e7..00000000 --- a/src/overlaybd/cache/ocf_cache/ease_bindings/volume.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -extern "C" { -#include -} - -#define EASE_OCF_VOLUME_TYPE 1 - -struct ease_ocf_volume_params { - size_t blk_size; - size_t media_size; - photon::fs::IFile *media_file; - bool enable_logging; -}; - -int volume_init(ocf_ctx_t ocf_ctx); - -void volume_cleanup(ocf_ctx_t ocf_ctx); diff --git a/src/overlaybd/cache/ocf_cache/include/ocf b/src/overlaybd/cache/ocf_cache/include/ocf deleted file mode 120000 index 6b86e502..00000000 --- a/src/overlaybd/cache/ocf_cache/include/ocf +++ /dev/null @@ -1 +0,0 @@ -../ocf/inc \ No newline at end of file diff --git a/src/overlaybd/cache/ocf_cache/ocf b/src/overlaybd/cache/ocf_cache/ocf deleted file mode 160000 index c2dd2259..00000000 --- a/src/overlaybd/cache/ocf_cache/ocf +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c2dd2259e47c2e5e72dc77f99d0150a5d05496d7 diff --git a/src/overlaybd/cache/ocf_cache/ocf_cache.cpp b/src/overlaybd/cache/ocf_cache/ocf_cache.cpp deleted file mode 100644 index f69506ef..00000000 --- a/src/overlaybd/cache/ocf_cache/ocf_cache.cpp +++ /dev/null @@ -1,295 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../cache.h" -#include "ocf_namespace.h" -#include "ease_bindings/provider.h" - - -extern IOAlloc *g_io_alloc; - -using namespace photon::fs; -namespace Cache { - -class OcfTruncateFile : public ForwardFile_Ownership { -public: - OcfTruncateFile(IFile *file, size_t file_size) - : ForwardFile_Ownership(file, true), m_file_size(file_size) { - } - - ssize_t pread(void *buf, size_t count, off_t offset) override { - if (offset > (off_t)m_file_size || count == 0) { - /* Read EOF, or read nothing */ - return 0; - } else if (offset < 0) { - errno = ENOSYS; - LOG_ERRNO_RETURN(0, -1, "Invalid parameters, offset `", offset); - } else if (count + offset > m_file_size) { - count = m_file_size - offset; - } - return m_file->pread(buf, count, offset); - } - - ssize_t preadv(const iovec *iov, int iovcnt, off_t offset) override { - IOVector iv(iov, iovcnt); - auto result = truncate_iov(iv, offset); - if (result <= 0) { - return result; - } - return m_file->preadv(iv.iovec(), iv.iovcnt(), offset); - } - -private: - int truncate_iov(IOVector &iv, off_t offset) const { - size_t count = iv.sum(); - if (offset > (off_t)m_file_size || count == 0) { - /* Read EOF, or read nothing */ - return 0; - } else if (offset < 0) { - errno = ENOSYS; - LOG_ERRNO_RETURN(0, -1, "Invalid parameters, offset `", offset); - } else if (count + offset > m_file_size) { - count = m_file_size - offset; - iv.truncate(count); - } - return 1; - } - - size_t m_file_size; -}; - -class OcfCachedFs; - -class OcfCachedFile : public VirtualFile { -public: - OcfCachedFile(OcfCachedFs *fs, OcfSrcFileCtx *ctx); - - ~OcfCachedFile(); - - ssize_t pread(void *buf, size_t count, off_t offset) override; - - int fadvise(off_t offset, off_t len, int advice) override; - - inline estring get_pathname() { - return m_path_name; - }; - - inline void set_pathname(const estring &pathname) { - m_path_name = pathname; - } - - int fstat(struct stat *buf) override { - return m_ctx->src_file->fstat(buf); - } - - int vioctl(int request, va_list args) override { - return m_ctx->src_file->vioctl(request, args); - } - - UNIMPLEMENTED_POINTER(IFileSystem *filesystem() override); - UNIMPLEMENTED(off_t lseek(off_t offset, int whence) override); - UNIMPLEMENTED(int fsync() override); - UNIMPLEMENTED(int fdatasync() override); - UNIMPLEMENTED(int fchmod(mode_t mode) override); - UNIMPLEMENTED(int fchown(uid_t owner, gid_t group) override); - UNIMPLEMENTED(int ftruncate(off_t length) override); - UNIMPLEMENTED(int close() override); - -private: - OcfCachedFs *m_fs; // owned by external class - OcfSrcFileCtx *m_ctx; // owned by external class - estring m_path_name; -}; - -class OcfCachedFs : public IFileSystem { -public: - OcfCachedFs(IFileSystem *src_fs, size_t prefetch_unit, OcfNamespace *ocf_ns, - IFile *media_file, bool reload_media, IOAlloc *io_alloc); - - ~OcfCachedFs(); - - int init(); - - ssize_t ocf_pread(void *buf, size_t count, off_t offset, OcfSrcFileCtx *ctx); - - inline void pooled_release(OcfCachedFile *file) { - m_src_file_pool.release(file->get_pathname()); - } - - inline IOAlloc *get_io_alloc() const { - return m_io_alloc; - } - - IFile *open(const char *pathname, int flags, mode_t mode) override; - - IFile *open(const char *pathname, int flags) override { - return open(pathname, flags, 0644); - } - - UNIMPLEMENTED(int stat(const char *pathname, struct stat *buf) override); - UNIMPLEMENTED(int lstat(const char *path, struct stat *buf) override); - UNIMPLEMENTED_POINTER(IFile *creat(const char *, mode_t) override); - UNIMPLEMENTED(int mkdir(const char *, mode_t) override); - UNIMPLEMENTED(int rmdir(const char *) override); - UNIMPLEMENTED(int link(const char *, const char *) override); - UNIMPLEMENTED(int symlink(const char *, const char *) override); - UNIMPLEMENTED(ssize_t readlink(const char *, char *, size_t) override); - UNIMPLEMENTED(int rename(const char *, const char *) override); - UNIMPLEMENTED(int chmod(const char *, mode_t) override); - UNIMPLEMENTED(int chown(const char *, uid_t, gid_t) override); - UNIMPLEMENTED(int statfs(const char *path, struct statfs *buf) override); - UNIMPLEMENTED(int statvfs(const char *path, struct statvfs *buf) override); - UNIMPLEMENTED(int access(const char *pathname, int mode) override); - UNIMPLEMENTED(int truncate(const char *path, off_t length) override); - UNIMPLEMENTED(int syncfs() override); - UNIMPLEMENTED(int unlink(const char *filename) override); - UNIMPLEMENTED(int lchown(const char *pathname, uid_t owner, gid_t group) override); - UNIMPLEMENTED_POINTER(DIR *opendir(const char *) override); - UNIMPLEMENTED(int utime(const char *path, const struct utimbuf *file_times) override); - UNIMPLEMENTED(int utimes(const char *path, const struct timeval times[2]) override); - UNIMPLEMENTED(int lutimes(const char *path, const struct timeval times[2]) override); - UNIMPLEMENTED(int mknod(const char *path, mode_t mode, dev_t dev) override); - -private: - IFileSystem *m_src_fs; // owned by external class - size_t m_prefetch_unit; - OcfNamespace *m_ocf_ns; // owned by self - IFile *m_media_file; // owned by external class - bool m_reload_media; - IOAlloc *m_io_alloc; // owned by external class - - ObjectCache m_src_file_pool; - - ease_ocf_volume_params *m_volume_params = nullptr; // owned by self - ease_ocf_provider *m_provider = nullptr; // owned by self -}; - -OcfCachedFile::OcfCachedFile(Cache::OcfCachedFs *fs, OcfSrcFileCtx *ctx) : m_fs(fs), m_ctx(ctx) { -} - -OcfCachedFile::~OcfCachedFile() { - m_fs->pooled_release(this); -} - -ssize_t OcfCachedFile::pread(void *buf, size_t count, off_t offset) { - auto ret = m_fs->ocf_pread(buf, count, offset, m_ctx); - if (ret != (ssize_t)count) { - LOG_ERRNO_RETURN(0, ret, "OcfCachedFile pread failed"); - } - return count; -} - -int OcfCachedFile::fadvise(off_t offset, off_t len, int advice) { - if (advice == POSIX_FADV_WILLNEED) { - void *buf = m_fs->get_io_alloc()->alloc(len); - DEFER(m_fs->get_io_alloc()->dealloc(buf)); - auto ret = pread(buf, len, offset); - if (ret < 0) { - LOG_ERROR_RETURN(0, -1, "prefetch read failed"); - } - return 0; - } - LOG_ERRNO_RETURN(ENOSYS, -1, "advice ` is not implemented", advice); -} - -OcfCachedFs::OcfCachedFs(IFileSystem *src_fs, size_t prefetch_unit, - OcfNamespace *ocf_ns, IFile *media_file, bool reload_media, - IOAlloc *io_alloc) - : m_src_fs(src_fs), m_prefetch_unit(prefetch_unit), m_ocf_ns(ocf_ns), m_media_file(media_file), - m_reload_media(reload_media), m_io_alloc(io_alloc), m_src_file_pool(1 * 1000 * 1000) { -} - -OcfCachedFs::~OcfCachedFs() { - m_provider->stop(); - delete m_provider; - delete m_volume_params; - delete m_ocf_ns; -} - -int OcfCachedFs::init() { - if (m_prefetch_unit % ease_ocf_provider::SectorSize != 0) { - LOG_ERROR_RETURN(0, -1, "OCF: invalid prefetch unit"); - } - g_io_alloc = m_io_alloc; - - struct stat buf {}; - if (m_media_file->fstat(&buf) != 0) { - LOG_ERROR_RETURN(0, -1, "OCF: failed to get media file size"); - } - size_t media_size = buf.st_size; - m_volume_params = - new ease_ocf_volume_params{m_ocf_ns->block_size(), media_size, m_media_file, false}; - m_provider = new ease_ocf_provider(m_volume_params, m_prefetch_unit); - - return m_provider->start(m_reload_media); -} - -ssize_t OcfCachedFs::ocf_pread(void *buf, size_t count, off_t offset, OcfSrcFileCtx *ctx) { - size_t blk_addr = ctx->ns_info.blk_idx * m_volume_params->blk_size; - bool prefetch = (m_prefetch_unit != 0); - return m_provider->ocf_pread(buf, count, offset, blk_addr, ctx, prefetch); -} - -IFile *OcfCachedFs::open(const char *pathname, int flags, mode_t mode) { - estring path_str(pathname); - if (path_str.ends_with("/")) { - LOG_ERROR_RETURN(0, nullptr, "OCF: cannot open a directory `", path_str.c_str()); - } - - auto ctor = [&]() -> OcfSrcFileCtx * { - // Open src file first - auto src_file = m_src_fs->open(path_str.c_str(), flags, mode); - if (src_file == nullptr) { - LOG_ERRNO_RETURN(0, nullptr, "OCF: failed to open src file of `", path_str); - } - - OcfNamespace::NsInfo info{}; - if (m_ocf_ns->locate_file(path_str, src_file, info) != 0) { - LOG_ERROR_RETURN(0, nullptr, "OCF: failed to locate src_file in namespace, path `", - path_str); - } - return new OcfSrcFileCtx(src_file, info, m_provider, path_str); - }; - - auto src_file_ctx = m_src_file_pool.acquire(path_str, ctor); - if (src_file_ctx == nullptr) { - LOG_ERROR_RETURN(0, nullptr, "OCF: failed to open ` from pool", path_str); - } - - auto cached_file = new OcfCachedFile(this, src_file_ctx); - cached_file->set_pathname(path_str); - - return new OcfTruncateFile(cached_file, src_file_ctx->ns_info.file_size); -} - -} /* namespace Cache */ - -namespace FileSystem { - -IFileSystem *new_ocf_cached_fs(IFileSystem *src_fs, IFileSystem *namespace_fs, size_t blk_size, - size_t prefetch_unit, IFile *media_file, bool reload_media, - IOAlloc *io_alloc) { - auto ocf_ns = new_ocf_namespace_on_fs(blk_size, namespace_fs); - if (ocf_ns->init() != 0) { - delete ocf_ns; - LOG_ERROR_RETURN(0, nullptr, "OCF: init namespace failed"); - } - - auto fs = - new Cache::OcfCachedFs(src_fs, prefetch_unit, ocf_ns, media_file, reload_media, io_alloc); - if (fs->init() != 0) { - delete fs; - LOG_ERROR_RETURN(0, nullptr, "OCF: init cache fs failed"); - } - return fs; -} - -} /* namespace FileSystem*/ diff --git a/src/overlaybd/cache/ocf_cache/ocf_namespace.cpp b/src/overlaybd/cache/ocf_cache/ocf_namespace.cpp deleted file mode 100644 index d927f876..00000000 --- a/src/overlaybd/cache/ocf_cache/ocf_namespace.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include "ocf_namespace.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include "../../zfile/crc32/crc32c.h" - -extern "C" { -#include "ocf/ocf.h" -} - -#define DIV_ROUND_UP(n, d) (((n) + (d)-1) / (d)) - -class OcfNamespaceOnFs : public OcfNamespace { -public: - OcfNamespaceOnFs(size_t blk_size, photon::fs::IFileSystem *fs) - : OcfNamespace(blk_size), m_fs(fs) { - } - - int init() override { - switch (m_blk_size) { - case ocf_cache_line_size_4: - case ocf_cache_line_size_8: - case ocf_cache_line_size_16: - case ocf_cache_line_size_32: - case ocf_cache_line_size_64: - break; - default: - LOG_ERROR_RETURN(0, -1, "OCF: invalid cache line size"); - } - - off_t max_blk_idx = 0; - off_t last_num_blocks = 0; - - for (auto file_path : enumerable(photon::fs::Walker(m_fs, ""))) { - NsInfo info; - if (get_ns_info(file_path, info) != 0) { - return -1; - } - - if (max_blk_idx < info.blk_idx) { - max_blk_idx = info.blk_idx; - last_num_blocks = DIV_ROUND_UP(info.file_size, m_blk_size); - } - } - - m_total_blocks = max_blk_idx + last_num_blocks; - LOG_DEBUG("OCF: set total_blocks to `", m_total_blocks); - return 0; - } - - int locate_file(const estring &file_path, photon::fs::IFile *src_file, NsInfo &info) override { - if (m_fs->access(file_path.c_str(), F_OK) == 0) { - if (get_ns_info(file_path, info) != 0) { - LOG_ERROR_RETURN(0, -1, "OCF: get ns info failed, path `", file_path); - } - } else { - struct stat st_buf {}; - if (src_file->fstat(&st_buf) != 0) { - LOG_ERROR_RETURN(0, -1, "OCF: failed to get size of `", file_path); - } - if (append_ns(file_path, st_buf.st_size, info) != 0) { - LOG_ERROR_RETURN(0, -1, "OCF: append ns failed, path `", file_path); - } - } - return 0; - } - -private: - struct NsFileFormat { - uint32_t magic; - uint32_t checksum; - NsInfo info; - }; - - int get_ns_info(estring_view file_path, NsInfo &info) { - auto file = m_fs->open(file_path.data(), O_RDONLY, 0644); - if (file == nullptr) { - LOG_ERRNO_RETURN(0, -1, "OCF: failed to open ns file"); - } - DEFER(delete file); - - NsFileFormat format; - ssize_t ret = file->read(&format, sizeof(format)); - if (ret != (ssize_t)sizeof(format)) { - LOG_ERRNO_RETURN(0, -1, "OCF: failed to read ns file"); - } - - if (format.magic != NS_FILE_MAGIC) { - LOG_ERROR_RETURN(0, -1, "OCF: ns file magic error"); - } - if (format.checksum != crc32::crc32c(&format.info, sizeof(format.info))) { - LOG_ERROR_RETURN(0, -1, "OCF: ns file checksum error"); - } - info = format.info; - LOG_DEBUG("OCF: load ns_info from `, blk_idx `, file_size `", file_path, info.blk_idx, - info.file_size); - return 0; - } - - int append_ns(estring_view file_path, size_t file_size, NsInfo &info) { - // Create dir if necessary - auto pos = file_path.find_last_of("/"); - if (0 < pos && pos < file_path.length()) { - auto base_dir = file_path.substr(0, pos + 1); - if (m_fs->access(base_dir.data(), F_OK) != 0 && - photon::fs::mkdir_recursive(base_dir, m_fs) != 0) { - LOG_ERRNO_RETURN(0, -1, "OCF: failed to mkdir for namespace at `", base_dir); - } - } - - // Lock in case of concurrent append - photon::scoped_lock lock(m_mutex); - - // Persist ns_info into ns_fs - info.blk_idx = (off_t)m_total_blocks; - info.file_size = file_size; - - if (write_ns_info(file_path, info) != 0) { - LOG_ERROR_RETURN(0, -1, "OCF: failed to write namespace file"); - } - - // Update total_blocks at last - size_t num_blocks = DIV_ROUND_UP(file_size, m_blk_size); - m_total_blocks += num_blocks; - - LOG_DEBUG("OCF: append namespace, file `, blk_idx `, size `", file_path, info.blk_idx, - info.file_size); - return 0; - } - - int write_ns_info(const estring &file_path, NsInfo &info) { - NsFileFormat format = { - .magic = NS_FILE_MAGIC, - .checksum = crc32::crc32c(&info, sizeof(info)), - .info = info, - }; - - auto tmp_file_path = estring().appends(file_path, ".tmp"); - { - auto tmp_file = m_fs->open(tmp_file_path.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0644); - if (tmp_file == nullptr) { - LOG_ERRNO_RETURN(0, -1, "OCF: failed to create tmp file `", tmp_file_path); - } - DEFER(delete tmp_file); - - // Write tmp file - ssize_t n_written = tmp_file->write(&format, sizeof(format)); - if (n_written != (ssize_t)sizeof(format)) { - delete tmp_file; - LOG_ERRNO_RETURN(0, -1, "OCF: failed to write tmp file `", tmp_file_path); - } - } - - // Atomic rename - if (m_fs->rename(tmp_file_path.c_str(), file_path.c_str()) != 0) { - ERRNO prev_eno; - m_fs->unlink(tmp_file_path.c_str()); - LOG_ERRNO_RETURN(prev_eno.no, -1, "OCF: failed to rename tmp file `", tmp_file_path); - } - return 0; - } - - const uint32_t NS_FILE_MAGIC = UINT32_MAX - 1; - size_t m_total_blocks = 0; - photon::fs::IFileSystem *m_fs; // owned by external class - photon::mutex m_mutex; -}; - -OcfNamespace *new_ocf_namespace_on_fs(size_t blk_size, photon::fs::IFileSystem *fs) { - return new OcfNamespaceOnFs(blk_size, fs); -} diff --git a/src/overlaybd/cache/ocf_cache/ocf_namespace.h b/src/overlaybd/cache/ocf_cache/ocf_namespace.h deleted file mode 100644 index e0d2d254..00000000 --- a/src/overlaybd/cache/ocf_cache/ocf_namespace.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include - -class OcfNamespace : public Object { -public: - explicit OcfNamespace(size_t blk_size) : m_blk_size(blk_size) { - } - - /** - * @brief Validate parameters, and load some metadata into memory - */ - virtual int init() = 0; - - /** NsInfo indicates a file's starting offset within its filesystem's address space, and its - * size */ - struct NsInfo { - off_t blk_idx; - size_t file_size; - }; - - /** - * @brief Locate a source file in namespace - * @param[in] file_path - * @param[in] src_file - * @param[out] info - * @retval 0 for success - */ - virtual int locate_file(const estring &file_path, photon::fs::IFile *src_file, - NsInfo &info) = 0; - - size_t block_size() const { - return m_blk_size; - } - -protected: - size_t m_blk_size; -}; - -OcfNamespace *new_ocf_namespace_on_fs(size_t blk_size, photon::fs::IFileSystem *fs); - -OcfNamespace *new_ocf_namespace_on_rocksdb(size_t blk_size); \ No newline at end of file diff --git a/src/overlaybd/cache/ocf_cache/test/CMakeLists.txt b/src/overlaybd/cache/ocf_cache/test/CMakeLists.txt deleted file mode 100644 index 9476e116..00000000 --- a/src/overlaybd/cache/ocf_cache/test/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -include_directories($ENV{GFLAGS}/include) -link_directories($ENV{GFLAGS}/lib) - -find_package(CURL REQUIRED) - -add_executable(ocf_perf_test ocf_perf_test.cpp) -target_include_directories( - ocf_perf_test PUBLIC - ${CURL_INCLUDE_DIRS} - ${PHOTON_INCLUDE_DIR} -) -target_link_libraries( - ocf_perf_test - gflags pthread ${CURL_LIBRARIES} - photon_static overlaybd_lib -) - -add_test( - NAME ocf_perf_test - COMMAND ${EXECUTABLE_OUTPUT_PATH}/ocf_perf_test --ut_pass=true -) diff --git a/src/overlaybd/cache/ocf_cache/test/flags.conf b/src/overlaybd/cache/ocf_cache/test/flags.conf deleted file mode 100644 index 29372c38..00000000 --- a/src/overlaybd/cache/ocf_cache/test/flags.conf +++ /dev/null @@ -1,16 +0,0 @@ ---multi_files_test=false ---cache_type=ocf ---page_size=8192 ---io_engine=1 ---concurrency=32 ---media_file_size_gb=2 ---media_file=/root/cache-bench/media ---ocf_prefetch_unit=0 - ---random_read=true ---src_file=/root/cache-bench/src ---dst_file= ---total_requests=1000000 - ---num_files=40 ---file_size_mb=50 \ No newline at end of file diff --git a/src/overlaybd/cache/ocf_cache/test/ocf_perf_test.cpp b/src/overlaybd/cache/ocf_cache/test/ocf_perf_test.cpp deleted file mode 100644 index 18b4a082..00000000 --- a/src/overlaybd/cache/ocf_cache/test/ocf_perf_test.cpp +++ /dev/null @@ -1,508 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../../../zfile/crc32/crc32c.h" -#include "../../cache.h" - -// Common params -DEFINE_bool(ut_pass, false, "pass unit test directly. This suite is only for manual test"); -DEFINE_bool(multi_files_test, false, "single or multiple files"); -DEFINE_string(cache_type, "ocf", "block or ocf"); -DEFINE_uint64(page_size, 4096, "page size or block size"); -DEFINE_string(media_file, "/tmp/cache-bench/media", "media file path"); -DEFINE_uint64(media_file_size_gb, 1, "media file size in gb"); -DEFINE_int64(io_engine, 0, "0: psync, 1: libaio, 3: iouring"); -DEFINE_uint64(concurrency, 16, "read concurrency"); -DEFINE_uint64(ocf_prefetch_unit, 0, "prefetch unit in bytes"); - -// Single file test params -DEFINE_bool(random_read, true, "random read or sequential read"); -DEFINE_string(src_file, "src", "src file path. Should be fully written before testing"); -DEFINE_string(dst_file, "", - "dst file path. Could be empty if writing is not needed. " - "Verify its checksum manually after test"); -DEFINE_uint64(total_requests, 0, "when to stop test"); - -// Multi files test params -DEFINE_uint64(num_files, 100, "num of files"); -DEFINE_uint64(file_size_mb, 10, "file size in mb"); - -// Global variables -int qps, last_qps = 0; -uint64_t total_req = 0; -bool stop_test = false; - -static void handle_signal(int) { - LOG_INFO("try to stop test"); - stop_test = true; -} - -static void handle_qps() { - if (qps - last_qps > 3000 || last_qps - qps > 3000) { - // avoid CPU 100% and no log - last_qps = qps; - photon::thread_yield(); - } - ++qps; - if (FLAGS_total_requests != 0 && ++total_req >= FLAGS_total_requests) { - stop_test = true; - } -} - -static void show_qps_loop() { - while (!stop_test) { - photon::thread_sleep(1); - LOG_INFO("qps: `", qps); - qps = 0; - } -} - -/* Single file test */ - -template -static int random_read(T *file, void *buf, size_t num_pages, photon::fs::IFile *dst_file) { - int ret; - IOVector iov; - iov.push_back(buf, FLAGS_page_size); - LOG_DEBUG("random_read: num_pages = `", num_pages); - while (!stop_test) { - int index = rand() % num_pages; - off_t offset = FLAGS_page_size * index; - ret = file->preadv(iov.iovec(), iov.iovcnt(), offset); - if (ret != (int)FLAGS_page_size) { - stop_test = true; - LOG_ERRNO_RETURN(0, -1, "read failed, offset `, ret `", offset, ret); - } - if (dst_file) { - ret = dst_file->pwritev(iov.iovec(), iov.iovcnt(), offset); - if (ret != (int)FLAGS_page_size) { - stop_test = true; - LOG_ERRNO_RETURN(0, -1, "write dst failed, offset `, ret `", offset, ret); - } - } - handle_qps(); - } - return 0; -} - -template -static int sequential_read(T *file, void *buf, size_t start_index, size_t num_pages, - photon::fs::IFile *dst_file) { - int ret; - size_t index = start_index; - LOG_DEBUG("sequential_read: start_index = `, num_pages = `", start_index, num_pages); - IOVector iov; - iov.push_back(buf, FLAGS_page_size); - while (!stop_test) { - off_t offset = FLAGS_page_size * index; - ret = file->preadv(iov.iovec(), iov.iovcnt(), offset); - if (ret != (int)FLAGS_page_size) { - stop_test = true; - LOG_ERRNO_RETURN(0, -1, "read failed, offset `, ret `", offset, ret); - } - if (dst_file) { - ret = dst_file->pwritev(iov.iovec(), iov.iovcnt(), offset); - if (ret != (int)FLAGS_page_size) { - stop_test = true; - LOG_ERRNO_RETURN(0, -1, "write dst failed, offset `, ret `", offset, ret); - } - } - if (++index >= num_pages) { - index = 0; - } - handle_qps(); - } - return 0; -} - -template -static int work(T *cache_file, IOAlloc *io_alloc) { - struct stat st_buf {}; - if (cache_file->fstat(&st_buf)) { - LOG_ERROR_RETURN(0, -1, "failed to get size"); - } - - photon::fs::IFile *dst_file = nullptr; - if (!FLAGS_dst_file.empty()) { - dst_file = photon::fs::open_localfile_adaptor( - FLAGS_dst_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644, FLAGS_io_engine); - if (!dst_file) { - LOG_ERROR_RETURN(0, -1, "failed to open dst file"); - } - LOG_INFO("open new dst file, truncate its size to `", st_buf.st_size); - dst_file->ftruncate(st_buf.st_size); - } - - size_t num_pages = st_buf.st_size / FLAGS_page_size; - std::vector join_hdls; - - void *buf = io_alloc->alloc(FLAGS_page_size); - DEFER(io_alloc->dealloc(buf)); - - if (FLAGS_random_read) { - for (uint64_t i = 0; i < FLAGS_concurrency; i++) { - auto th = photon::thread_create11(random_read, cache_file, buf, num_pages, dst_file); - join_hdls.push_back(photon::thread_enable_join(th)); - } - } else { - for (uint64_t i = 0; i < FLAGS_concurrency; i++) { - ssize_t start_index = num_pages / FLAGS_concurrency * i; - auto th = photon::thread_create11(sequential_read, cache_file, buf, start_index, - num_pages, dst_file); - join_hdls.push_back(photon::thread_enable_join(th)); - } - } - - for (auto join_hdl : join_hdls) { - photon::thread_join(join_hdl); - } - return 0; -} - -static int single_file_ocf_cache(IOAlloc *io_alloc, photon::fs::IFileSystem *src_fs, - const std::string &root_dir) { - LOG_INFO("Start single file ocf cache test"); - auto namespace_dir = root_dir + "/namespace/"; - if (::access(namespace_dir.c_str(), F_OK) != 0 && ::mkdir(namespace_dir.c_str(), 0755) != 0) { - LOG_ERRNO_RETURN(0, -1, "failed to create namespace_dir"); - } - auto namespace_fs = photon::fs::new_localfs_adaptor(namespace_dir.c_str()); - if (namespace_fs == nullptr) { - LOG_ERROR_RETURN(0, -1, "failed tp create namespace_fs"); - } - DEFER(delete namespace_fs); - - bool reload_media; - photon::fs::IFile *media_file; - if (::access(FLAGS_media_file.c_str(), F_OK) != 0) { - reload_media = false; - media_file = photon::fs::open_localfile_adaptor(FLAGS_media_file.c_str(), O_RDWR | O_CREAT, - 0644, FLAGS_io_engine); - media_file->fallocate(0, 0, FLAGS_media_file_size_gb * 1024 * 1024 * 1024); - } else { - reload_media = true; - media_file = photon::fs::open_localfile_adaptor(FLAGS_media_file.c_str(), O_RDWR, 0644, - FLAGS_io_engine); - } - DEFER(delete media_file); - - auto ocf_cached_fs = - FileSystem::new_ocf_cached_fs(src_fs, namespace_fs, FLAGS_page_size, - FLAGS_ocf_prefetch_unit, media_file, reload_media, io_alloc); - if (ocf_cached_fs == nullptr) { - LOG_ERROR_RETURN(0, -1, "new_ocf_cached_fs error"); - } - DEFER(delete ocf_cached_fs); - - auto file = ocf_cached_fs->open(FLAGS_src_file.c_str(), O_RDONLY, 0644); - if (file == nullptr) { - LOG_ERROR_RETURN(0, -1, "open file error"); - } - DEFER(delete file); - - work(file, io_alloc); - return 0; -} - -static int single_file_file_cache(IOAlloc *io_alloc, photon::fs::IFileSystem *src_fs, - const std::string &root_dir) { - LOG_INFO("Start single file full file cache test"); - auto media_fs = photon::fs::new_localfs_adaptor(root_dir.c_str(), FLAGS_io_engine); - if (media_fs == nullptr) { - LOG_ERROR_RETURN(0, -1, "failed to create media fs"); - } - auto cached_fs = FileSystem::new_full_file_cached_fs( - src_fs, media_fs, FLAGS_page_size, FLAGS_media_file_size_gb, 1000 * 1000, - 2UL * FLAGS_media_file_size_gb * 1024 * 1024 * 1024, io_alloc, 0); - if (cached_fs == nullptr) { - LOG_ERROR_RETURN(0, -1, "new_ocf_cached_fs error"); - } - DEFER(delete cached_fs); - - estring filename = FLAGS_src_file; - if (!filename.starts_with("/")) { - filename = estring().appends("/", filename); - } - - auto file = cached_fs->open(filename.c_str(), O_RDONLY, 0644); - if (file == nullptr) { - LOG_ERROR_RETURN(0, -1, "open file error"); - } - DEFER(delete file); - - work(file, io_alloc); - return 0; -} - -static int single_file_test(IOAlloc *io_alloc) { - if (!FLAGS_dst_file.empty() && FLAGS_concurrency != 1) { - LOG_ERROR_RETURN(0, -1, "Doesn't make sense to do concurrent writes on the same file") - } - - auto root_dir = FLAGS_media_file.substr(0, FLAGS_media_file.rfind('/') + 1); - - auto src_fs = photon::fs::new_localfs_adaptor("/", FLAGS_io_engine); - if (src_fs == nullptr) { - LOG_ERROR_RETURN(0, -1, "failed to create fs"); - } - DEFER(delete src_fs); - - auto qps_th = photon::thread_create11(show_qps_loop); - auto qps_join_hdl = photon::thread_enable_join(qps_th); - int ret = 0; - - if (FLAGS_cache_type == "ocf") { - ret = single_file_ocf_cache(io_alloc, src_fs, root_dir); - } else if (FLAGS_cache_type == "file") { - ret = single_file_file_cache(io_alloc, src_fs, root_dir); - } - if (ret != 0) { - stop_test = true; - } - photon::thread_join(qps_join_hdl); - return 0; -} - -/* Multiple files test */ - -struct fill_data_args { - int64_t &num_files; - photon::fs::IFileSystem *copied_fs; - photon::fs::IFile *urandom_file; - IOAlloc *io_alloc; -}; - -static void *fill_random_data(void *args_) { - auto args = (fill_data_args *)args_; - - void *buf = args->io_alloc->alloc(FLAGS_page_size); - DEFER(args->io_alloc->dealloc(buf)); - - while (true) { - if (args->num_files-- <= 0) { - return nullptr; - } - uint64_t file_index = args->num_files; - - LOG_INFO("generate random test file ` in copied_fs", file_index); - auto file = args->copied_fs->open(std::to_string(file_index).c_str(), - O_RDWR | O_CREAT | O_TRUNC, 0644); - if (file == nullptr) { - LOG_ERRNO_RETURN(0, nullptr, "error open file `", file_index); - } - DEFER(delete file); - - auto num_pages = FLAGS_file_size_mb * 1024 * 1024 / FLAGS_page_size; - for (size_t j = 0; j < num_pages; ++j) { - if (args->urandom_file->read(buf, FLAGS_page_size) != (ssize_t)FLAGS_page_size) { - LOG_ERRNO_RETURN(0, nullptr, "error read urandom"); - } - if (file->write(buf, FLAGS_page_size) != (ssize_t)FLAGS_page_size) { - LOG_ERRNO_RETURN(0, nullptr, "error write file"); - } - } - } - return nullptr; -} - -static photon::fs::IFileSystem *prepare_copied_fs(IOAlloc *io_alloc, const std::string &root_dir) { - // Prepare test files in copied_fs, and fill with random data - bool need_init = true; - if (access((root_dir + "/copied_fs").c_str(), F_OK) == 0) { - need_init = false; - } else { - system(("mkdir -p " + root_dir + "/copied_fs").c_str()); - } - - auto copied_fs = photon::fs::new_localfs_adaptor((root_dir + "/copied_fs").c_str(), - photon::fs::ioengine_libaio); - if (copied_fs == nullptr) { - LOG_ERROR_RETURN(0, nullptr, "error create copied_fs"); - } - - if (!need_init) { - LOG_INFO("copied fs exists"); - return copied_fs; - } - - auto urandom_file = photon::fs::open_localfile_adaptor("/dev/urandom", O_RDONLY, 0644); - DEFER(delete urandom_file); - - // Multi-thread fill random data - int64_t num_files = FLAGS_num_files; - auto args = new fill_data_args{num_files, copied_fs, urandom_file, io_alloc}; - photon::threads_create_join(32, fill_random_data, args); - delete args; - - // Copy them up to src_fs dir - LOG_INFO("copy test files ..."); - system(("cp -rf " + root_dir + "/copied_fs/* " + root_dir).c_str()); - - LOG_INFO("clean page cache ..."); - system("echo 3 > /proc/sys/vm/drop_caches"); - return copied_fs; -} - -static int crc_read(std::vector &cached_files, - std::vector &copied_files, IOAlloc *io_alloc) { - void *buf = io_alloc->alloc(FLAGS_page_size); - DEFER(io_alloc->dealloc(buf)); - auto num_pages = FLAGS_file_size_mb * 1024 * 1024 / FLAGS_page_size; - - while (!stop_test) { - auto file_index = rand() % FLAGS_num_files; - auto page_index = rand() % num_pages; - - if (cached_files[file_index]->pread(buf, FLAGS_page_size, page_index * FLAGS_page_size) < - 0) { - LOG_ERRNO_RETURN(0, -1, "error read cached_file"); - } - auto cached_crc = crc32::crc32c(buf, FLAGS_page_size); - if (copied_files[file_index]->pread(buf, FLAGS_page_size, page_index * FLAGS_page_size) < - 0) { - LOG_ERRNO_RETURN(0, -1, "error read copied_file"); - } - auto copied_crc = crc32::crc32c(buf, FLAGS_page_size); - - if (cached_crc != copied_crc) { - LOG_FATAL("FATAL: crc not equal !!! offset = `", page_index * FLAGS_page_size); - abort(); - } - qps++; - } - return 0; -} - -static int multiple_files_test(IOAlloc *io_alloc) { - LOG_INFO("Start multiple files test"); - auto root_dir = FLAGS_media_file.substr(0, FLAGS_media_file.rfind('/') + 1); - - auto src_fs = photon::fs::new_localfs_adaptor(root_dir.c_str(), photon::fs::ioengine_libaio); - if (src_fs == nullptr) { - LOG_ERROR_RETURN(0, -1, "failed to create fs"); - } - DEFER(delete src_fs); - - // The files in copied_fs are completely identical to those in src_fs - auto copied_fs = prepare_copied_fs(io_alloc, root_dir); - if (copied_fs == nullptr) { - return -1; - } - DEFER(delete copied_fs); - - // Create ocf_cached_fs at src_fs dir - auto namespace_dir = root_dir + "/namespace/"; - if (::access(namespace_dir.c_str(), F_OK) != 0 && ::mkdir(namespace_dir.c_str(), 0755) != 0) { - LOG_ERRNO_RETURN(0, -1, "failed to create namespace_dir"); - } - auto namespace_fs = photon::fs::new_localfs_adaptor(namespace_dir.c_str()); - if (namespace_fs == nullptr) { - LOG_ERROR_RETURN(0, -1, "failed tp create namespace_fs"); - } - DEFER(delete namespace_fs); - - bool reload_media; - photon::fs::IFile *media_file; - if (::access(FLAGS_media_file.c_str(), F_OK) != 0) { - reload_media = false; - media_file = photon::fs::open_localfile_adaptor(FLAGS_media_file.c_str(), O_RDWR | O_CREAT, - 0644, FLAGS_io_engine); - media_file->fallocate(0, 0, FLAGS_media_file_size_gb * 1024 * 1024 * 1024); - } else { - reload_media = true; - media_file = photon::fs::open_localfile_adaptor(FLAGS_media_file.c_str(), O_RDWR, 0644, - FLAGS_io_engine); - } - DEFER(delete media_file); - - auto ocf_fs = - FileSystem::new_ocf_cached_fs(src_fs, namespace_fs, FLAGS_page_size, - FLAGS_ocf_prefetch_unit, media_file, reload_media, io_alloc); - if (ocf_fs == nullptr) { - LOG_ERROR_RETURN(0, -1, "error create ocf_fs"); - } - DEFER(delete ocf_fs); - - std::vector cached_files; - std::vector copied_files; - - // DEFER close all files - auto release_resource = [&] { - for (auto &f : cached_files) { - delete f; - } - for (auto &f : copied_files) { - delete f; - } - }; - DEFER(release_resource()); - - // Open all files - for (size_t i = 0; i < FLAGS_num_files; ++i) { - auto cached_file = ocf_fs->open(std::to_string(i).c_str(), O_RDONLY, 0644); - if (cached_file == nullptr) { - LOG_ERRNO_RETURN(0, -1, "error open cached_file `", i); - } - cached_files.push_back(cached_file); - auto copied_file = copied_fs->open(std::to_string(i).c_str(), O_RDONLY, 0644); - if (copied_file == nullptr) { - LOG_ERRNO_RETURN(0, -1, "error open copied_file `", i); - } - copied_files.push_back(copied_file); - } - - // qps thread - auto qps_th = photon::thread_create11(show_qps_loop); - auto qps_join_hdl = photon::thread_enable_join(qps_th); - - // Concurrently run crc_read - std::vector join_hdls; - - for (uint64_t i = 0; i < FLAGS_concurrency; i++) { - auto th = photon::thread_create11(crc_read, cached_files, copied_files, io_alloc); - join_hdls.push_back(photon::thread_enable_join(th)); - } - - for (auto join_hdl : join_hdls) { - photon::thread_join(join_hdl); - } - photon::thread_join(qps_join_hdl); - return 0; -} - -int main(int argc, char *argv[]) { - gflags::ParseCommandLineFlags(&argc, &argv, true); - - log_output_level = ALOG_INFO; - srand(time(nullptr)); - if (FLAGS_ut_pass) { - LOG_INFO("pass unit test"); - return 0; - } - -photon::init(photon::INIT_EVENT_DEFAULT, photon::INIT_IO_DEFAULT); - - - auto pooled_allocator = new PooledAllocator<2 * 1024 * 1024, 1024, 4096>; - DEFER(delete pooled_allocator); - IOAlloc io_alloc = pooled_allocator->get_io_alloc(); - - if (!FLAGS_multi_files_test) { - single_file_test(&io_alloc); - } else { - multiple_files_test(&io_alloc); - } - LOG_INFO("test stopped"); - return 0; -} diff --git a/src/overlaybd/cache/policy/lru.h b/src/overlaybd/cache/policy/lru.h deleted file mode 100644 index 48af6a40..00000000 --- a/src/overlaybd/cache/policy/lru.h +++ /dev/null @@ -1,148 +0,0 @@ - -/* - Copyright The Overlaybd Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -#pragma once -#include -#include -#include -#include -#include -#include -#include - -namespace FileSystem { -// This is a generic LRU container, highly optimized for both speed and memory. -// The default KeyType = uint16_t, which limits the container to 64K entries as most. -// If more entries are needed, use KeyType = uint32_t or bigger. -template -class LRU { -public: - using value_type = ValueType; - using key_type = KeyType; - static_assert(std::is_unsigned::value, "KeyType must be unsigned integer"); - - // maximum # of entries in the LRU container - const static size_t LIMIT = std::numeric_limits::max(); - - const static uint64_t kInvalid = std::numeric_limits::max(); - - LRU() { - // This is a dummy node for denoting end of list. - m_head = do_alloc(); - PTR(m_head)->prev = m_head; - PTR(m_head)->next = m_head; - } - - // Insert a value into the LRU container, returning a key that is guaranteed - // not to change during the lifetime, and that can be used as parameter of use(). - // MUST ensure # entries < LIMIT, before pushing! - key_type push_front(value_type v) { - assert(m_size < LIMIT); - auto i = do_alloc(); - PTR(i)->val = v; - do_insert(PTR(m_head)->prev, m_head, i); - m_size++; - return m_head = i; - } - void access(key_type i) { - assert(i < m_array.size()); - if (m_size == 1 || i == m_head) - return; - do_remove(i); - do_insert(PTR(m_head)->prev, m_head, i); - m_head = i; - } - void mark_key_cleared(key_type i) // mark `i` as cleared (all space de-allocated), - { // by removing it from the ring but not inserting - assert(i < m_array.size()); // it to the free ring, so as to avoid it being - do_remove(i); // returned by back() and considered as a - PTR(i)->prev = PTR(i)->next = i; // candidate for eviction. - } // `use()` and `remove()` apply as usual - void remove(key_type i) { - assert(i < m_array.size()); - do_remove(i); - m_size--; - if (m_free == kInvalid) { - PTR(i)->prev = PTR(i)->next = m_free = i; - } else { - do_insert(m_free, PTR(m_free)->next, i); - } - } - void pop_back() { - assert(m_size > 0); - remove(PTR(PTR(m_head)->prev)->prev); - } - value_type &front() { - assert(m_size > 0); - return PTR(m_head)->val; - } - value_type &back() { - assert(m_size > 0); - return PTR(PTR(PTR(m_head)->prev)->prev)->val; - } - size_t size() { - return m_size; - } - bool empty() { - return m_head == PTR(m_head)->prev; - } - -protected: - struct Record { - key_type prev, next; - value_type val; - }; - std::vector m_array; - uint64_t m_free = kInvalid; - uint64_t m_size = 0; // # of valid records (excluding free) - key_type m_head; - - Record *PTR(key_type i) { - return &m_array[i]; - } - void do_insert(key_type prev, key_type next, key_type i) { - PTR(i)->prev = prev; - PTR(i)->next = next; - PTR(prev)->next = PTR(next)->prev = i; - } - void do_remove(key_type i) { - auto prev = PTR(i)->prev; - auto next = PTR(i)->next; - if (i == m_head) { - m_head = next; - } - PTR(prev)->next = next; - PTR(next)->prev = prev; - } - key_type do_alloc() { - if (m_free != kInvalid) { - auto r = m_free; - if (PTR(r)->next == r || PTR(r)->prev == r) { - m_free = kInvalid; - } else { - m_free = PTR(r)->next; - do_remove(r); - } - return r; - } else { - auto r = m_array.size(); - assert(r < LIMIT); - m_array.resize(r + 1); - return (key_type)r; - } - } -}; -} // namespace FileSystem \ No newline at end of file diff --git a/src/overlaybd/cache/pool_store.h b/src/overlaybd/cache/pool_store.h deleted file mode 100644 index 3aea97e8..00000000 --- a/src/overlaybd/cache/pool_store.h +++ /dev/null @@ -1,290 +0,0 @@ -/* - Copyright The Overlaybd Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -enum ListType : int { - LIST_ALL = 0, - LIST_FILES = 1, - LIST_DIRS = 2, -}; - -// reset cache flags -enum ResetType : int { - RST_ALL = 0x0, // reset all cache's data, include file meta - RST_MEMORY = 0x1, // reset memory cache's data - RST_DISK = 0x2, // reset disk cache's data - RST_UPPER = 0x10, // reset upper layer cache's data - RST_LOWER = 0x20, // reset lower layer cache's data -}; - -// resize cache flags -enum ResizeType : int { - RSZ_MEMORY = 0x1, // resize memory cache's capacity - RSZ_DISK = 0x2, // resize disk cache's capacity - RSZ_UPPER = 0x10, // resize upper layer cache's capacity - RSZ_LOWER = 0x20, // resize lower layer cache's capacity -}; - -namespace FileSystem { -// `CacheFnTransFunc` use to transform the filename in the cached store. -// `std::string_view` is the filename before transformation (as src_name). -// `char *` is the transformed filename (as store_key). -// `size_t` is the max buffer length of store_key. -// If transform occurs an error (such as result length more than buffer size) -// or there is not necessary to transform, this function returns 0, -// otherwise, it returns string length after transformation. -using CacheFnTransFunc = Delegate; -class ICacheStore; -struct CacheStat { - uint32_t struct_size = sizeof(CacheStat); - uint32_t refill_unit; // in bytes - uint32_t total_size; // in refill_unit - uint32_t used_size; // in refill_unit - uint64_t evict_other; // in bytes, initialized to -1UL means reset - uint64_t evict_global; // in bytes, initialized to -1UL means reset - uint64_t evict_user; // in bytes, initialized to -1UL means reset -}; - -class ICachePool : public Object { -public: - ICachePool(uint32_t pool_size = 128, uint32_t max_refilling = 128, - uint32_t refilling_threshold = -1U); - ~ICachePool(); - - ICacheStore *open(std::string_view filename, int flags, mode_t mode); - - // set quota to a dir or a file - virtual int set_quota(std::string_view pathname, size_t quota) = 0; - - // if pathname is {nullptr, 0} or "/", returns the overall stat - // if pathname is a dir, and it has quota set, returns its quota usage - // if pathname is a file, returns the file's stat - virtual int stat(CacheStat *stat, std::string_view pathname = std::string_view(nullptr, 0)) = 0; - - // force to evict specified files(s) - virtual int evict(std::string_view filename) = 0; - - // try to evict at least `size` bytes, and also make sure - // available space meet other requirements as well - virtual int evict(size_t size = 0) = 0; - - int store_release(ICacheStore *store); - - void stores_clear(); - - void set_trans_func(CacheFnTransFunc fn_trans_func); - - virtual ICacheStore *do_open(std::string_view filename, int flags, mode_t mode) = 0; - - virtual int rename(std::string_view oldname, std::string_view newname) = 0; - - virtual ssize_t list(const char *dirname, ListType type, const struct iovec *iov, int iovcnt, - const char *marker, uint32_t count) { - errno = ENOSYS; - return -1; - } - - UNIMPLEMENTED_POINTER(void *get_underlay_object(int i = 0)); - - // reset cache's data - virtual int reset(int flags = 0) { - errno = ENOSYS; - return -1; - } - - // resize cache's capacity - virtual int resize(size_t n, int flags = 0) { - errno = ENOSYS; - return -1; - } - -protected: - void *m_stores; - CacheFnTransFunc fn_trans_func; - void *m_thread_pool = nullptr; - void *m_vcpu = nullptr; // vcpu where m_therad_pool is created - std::atomic m_refilling{0}; - const uint32_t m_max_refilling = 128; - const uint32_t m_refilling_threshold = -1U; - friend class ICacheStore; -}; - -class ICacheStore : public Object { -public: - virtual ~ICacheStore(); - // public interface for reading cache file store, dealing with cache-miss - // and deduplication of concurrent reading of source file. - ssize_t preadv2(const struct iovec *iov, int iovcnt, off_t offset, int flags); - ssize_t pwritev2(const struct iovec *iov, int iovcnt, off_t offset, int flags); - ssize_t try_refill_range(off_t offset, size_t count); - - virtual int set_quota(size_t quota) = 0; - virtual int stat(CacheStat *stat) = 0; - virtual int evict(off_t offset, size_t count = -1) = 0; - // offset + size must <= origin file size - virtual std::pair queryRefillRange(off_t offset, size_t size) = 0; - virtual int fstat(struct stat *buf) = 0; - virtual int set_crc(uint32_t crc) { - errno = ENOSYS; - return -1; - } - virtual int get_crc(uint32_t *crc) { - errno = ENOSYS; - return -1; - } - virtual uint64_t get_handle() { - return -1UL; - } - - void release() { - auto ref = ref_.fetch_sub(1, std::memory_order_relaxed); - if (ref == 1 && pool_) { - pool_->store_release(this); - } else if (ref == 0) - delete this; // call do_open directly - } - - ssize_t pread(void *buf, size_t count, off_t offset) { - struct iovec iov { - buf, count - }; - return do_preadv2(&iov, 1, offset, 0); - } - - ssize_t pwrite(const void *buf, size_t count, off_t offset) { - struct iovec iov { - (void *)buf, count - }; - return do_pwritev2(&iov, 1, offset, 0); - } - - std::string_view get_src_name() { - return src_name_; - } - void set_src_name(std::string_view pathname) { - src_name_ = pathname.data(); - } - std::string_view get_store_key() { - return store_key_; - } - void set_store_key(std::string_view pathname) { - store_key_ = pathname; - } - void set_pool(ICachePool *pool) { - pool_ = pool; - } - void set_cached_size(off_t cached_size); - off_t get_actual_size() { - return actual_size_; - } - void set_actual_size(off_t actual_size) { - actual_size_ = actual_size; - } - void set_open_flags(int open_flags) { - open_flags_ = open_flags; - } - int open_src_file(photon::fs::IFile **src_file = nullptr); - void set_src_file(photon::fs::IFile *src_file) { - src_file_ = src_file; - } - photon::fs::IFileSystem *get_src_fs() { - return src_fs_; - } - void set_src_fs(photon::fs::IFileSystem *src_fs) { - src_fs_ = src_fs; - } - size_t get_page_size() { - return page_size_; - } - void set_page_size(size_t page_size) { - page_size_ = page_size; - } - IOAlloc *get_allocator() { - return allocator_; - } - void set_allocator(IOAlloc *allocator) { - allocator_ = allocator; - } - - struct try_preadv_result { - size_t iov_sum; // sum of the iovec[] - size_t refill_size; // size in bytes to refill, 0 means cache hit - union { - off_t refill_offset; // the offset to fill, if not hit - ssize_t size; // the return value of preadv(), if hit - }; - }; - virtual try_preadv_result try_preadv2(const struct iovec *iov, int iovcnt, off_t offset, - int flags); - virtual ssize_t do_preadv2(const struct iovec *iov, int iovcnt, off_t offset, int flags); - virtual ssize_t do_preadv2_mutable(struct iovec *iov, int iovcnt, off_t offset, int flags); - virtual ssize_t do_pwritev2(const struct iovec *iov, int iovcnt, off_t offset, int flags); - virtual ssize_t do_pwritev2_mutable(struct iovec *iov, int iovcnt, off_t offset, int flags); - -private: - ssize_t pwritev2_extend(const struct iovec *iov, int iovcnt, off_t offset, int flags); - ssize_t do_refill_range(uint64_t refill_off, uint64_t refill_size, size_t count, - IOVector *input = nullptr, off_t offset = 0, int flags = 0); - int tryget_size(); - static void *async_refill(void *args); - -protected: - std::string src_name_; - std::string_view store_key_; - ICachePool *pool_ = nullptr; - off_t cached_size_ = 0; - off_t actual_size_ = 0; - int open_flags_ = 0; - std::atomic ref_{0}; - photon::fs::IFile *src_file_ = nullptr; - photon::fs::IFileSystem *src_fs_ = nullptr; - size_t page_size_ = 4096; - IOAlloc *allocator_ = nullptr; - RangeLock range_lock_; - photon::mutex open_lock_; - friend class ICachePool; -}; - -class IMemCacheStore : public ICacheStore { -public: - virtual ssize_t pin_buffer(off_t offset, size_t count, int flags, /*OUT*/ iovector *iov, - void **pin_result) = 0; - - virtual int unpin_buffer(void *pin_result) = 0; -}; - -class IMemCachePool : public ICachePool { -public: - using ICachePool::ICachePool; - - virtual ssize_t pin_buffer(uint64_t handle, off_t offset, size_t count, int flags, - /*OUT*/ iovector *iov, void **pin_result) = 0; - - virtual int unpin_buffer(void *pin_result) = 0; -}; -} // namespace FileSystem diff --git a/src/overlaybd/cache/store.cpp b/src/overlaybd/cache/store.cpp deleted file mode 100644 index 9ae2ba28..00000000 --- a/src/overlaybd/cache/store.cpp +++ /dev/null @@ -1,427 +0,0 @@ -/* - Copyright The Overlaybd Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -#include "pool_store.h" -#include "cache.h" -#include -#include -#include -#include -#include -#include -#include - -using namespace FileSystem; -using namespace photon::fs; - -namespace FileSystem { - -static const uint32_t MAX_REFILLING = 128; - -ICacheStore::~ICacheStore() { - delete src_file_; -} - -ssize_t ICacheStore::preadv2(const struct iovec *iov, int iovcnt, off_t offset, int flags) { - if (offset < 0) - LOG_ERROR_RETURN(EINVAL, -1, "offset is invalid, offset : `", offset); - iovector_view view(const_cast(iov), iovcnt); - size_t iov_size = view.sum(); - if (0u == iov_size) - return 0; - if (offset >= actual_size_ || offset + static_cast(iov_size) > actual_size_) { - if (tryget_size() != 0) { - LOG_ERROR_RETURN(0, -1, "try get size failed, actual_size_ : `, offset : `, count : `", - actual_size_, offset, iov_size); - } - } - - if (offset >= actual_size_) - return 0; - IOVector input(iov, iovcnt); - if (offset + static_cast(iov_size) > actual_size_) { - input.extract_back(offset + static_cast(iov_size) - actual_size_); - iov_size = actual_size_ - offset; - } - - if ((flags & RW_V2_CACHE_ONLY) || (open_flags_ & O_CACHE_ONLY)) { - auto tr = try_preadv2(input.iovec(), input.iovcnt(), offset, flags); - if (tr.refill_size == 0 && tr.size >= 0) { - return tr.size; - } else { - return -1; - } - } - -again: - auto tr = try_preadv2(input.iovec(), input.iovcnt(), offset, flags); - if (tr.refill_size == 0 && tr.size >= 0) - return tr.size; - // open src file only when cache miss - if (open_src_file() != 0 || !src_file_) { - LOG_ERROR_RETURN(0, -1, "cache preadv2 failed, offset : `, count : `, flags : `", offset, - iov_size, flags); - } - - if (tr.refill_offset < 0) { - SCOPE_AUDIT("download", AU_FILEOP(get_src_name(), offset, tr.size)); - tr.size = src_file_->preadv2(input.iovec(), input.iovcnt(), offset, flags); - return tr.size; - } - - ssize_t ret = - do_refill_range(tr.refill_offset, tr.refill_size, iov_size, &input, offset, flags); - if (ret == -EAGAIN) - goto again; - return ret; -} - -ssize_t ICacheStore::pwritev2(const struct iovec *iov, int iovcnt, off_t offset, int flags) { - if (open_flags_ & (O_WRITE_THROUGH | O_CACHE_ONLY | O_WRITE_BACK)) { - return pwritev2_extend(iov, iovcnt, offset, flags); - } - - iovector_view view(const_cast(iov), iovcnt); - size_t size = view.sum(); - if (offset >= actual_size_ || offset + static_cast(size) > actual_size_) { - if (tryget_size() < 0) { - LOG_ERROR_RETURN(0, -1, "try get size failed, actual_size_ : `, offset : `, count : `", - actual_size_, offset, size); - } - } - - if (offset >= actual_size_) - return 0; - if (offset % page_size_ != 0 || - (size % page_size_ != 0 && offset + static_cast(size) < actual_size_)) { - LOG_ERROR_RETURN(EINVAL, -1, "size or offset is not aligned to `, size : `, offset : `", - page_size_, size, offset); - } - - if (offset + static_cast(size) <= actual_size_) { - return do_pwritev2(iov, iovcnt, offset, flags); - } - - IOVector io_vector(iov, iovcnt); - if (offset + static_cast(size) > actual_size_) { - auto ret = io_vector.extract_back(size - (actual_size_ - offset)); - if (ret != size - (actual_size_ - offset)) - LOG_ERRNO_RETURN(EINVAL, -1, "extract failed, extractSize : `, expected : ", ret, - size - (actual_size_ - offset)) - } - - auto write = do_pwritev2(io_vector.iovec(), io_vector.iovcnt(), offset, flags); - if (write != static_cast(io_vector.sum())) { - if (ENOSPC != errno) - LOG_ERROR( - "cache file write failed : `, error : `, actual_size_ : `, offset : `, sum : `", - write, ERRNO(errno), actual_size_, offset, io_vector.sum()); - } - - return write; -} - -ssize_t ICacheStore::try_refill_range(off_t offset, size_t count) { - if (offset >= actual_size_ || offset + static_cast(count) > actual_size_) { - if (tryget_size() != 0) { - LOG_ERROR_RETURN(0, -1, "try get size failed, actual_size_ : `, offset : `, count : `", - actual_size_, offset, count); - } - } - - if (offset >= actual_size_) - return 0; - if (offset + static_cast(count) > actual_size_) { - count = actual_size_ - offset; - } - -again: - auto qres = queryRefillRange(offset, count); - if (qres.first < 0) - return -1; - if (qres.second == 0) - return static_cast(count); - // open src file only when cache miss - if (open_src_file() != 0 || !src_file_) { - LOG_ERROR_RETURN(0, -1, - "try refill_range failed due to null src file, offset : `, count : `", - offset, count); - } - - ssize_t ret = do_refill_range(qres.first, qres.second, count); - if (ret == -EAGAIN) - goto again; - return ret; -} - -struct RefillContext { - ICacheStore *store; - IOVector buffer; - uint64_t refill_off; - uint64_t refill_size; - int flags; -}; - -void *ICacheStore::async_refill(void *args) { - auto ctx = (RefillContext *)args; - auto write = ctx->store->do_pwritev2(ctx->buffer.iovec(), ctx->buffer.iovcnt(), ctx->refill_off, - ctx->flags); - if (write != static_cast(ctx->refill_size)) { - if (ENOSPC != errno) - LOG_ERROR( - "cache file write failed : `, error : `, actual_size_ : `, offset : `, sum : `", - write, ERRNO(errno), ctx->store->actual_size_, ctx->refill_off, ctx->buffer.sum()); - } - - ctx->store->pool_->m_refilling.fetch_sub(1, std::memory_order_relaxed); - ctx->store->range_lock_.unlock(ctx->refill_off, ctx->refill_size); - ctx->store->release(); - photon::thread_migrate(photon::CURRENT, - static_cast(ctx->store->pool_->m_vcpu)); - delete ctx; - return nullptr; -} - -ssize_t ICacheStore::do_refill_range(uint64_t refill_off, uint64_t refill_size, size_t count, - IOVector *input, off_t offset, int flags) { - ssize_t ret = 0; - if (input && pool_ && - pool_->m_refilling.load(std::memory_order_relaxed) > pool_->m_refilling_threshold) { - SCOPE_AUDIT("download", AU_FILEOP(get_src_name(), offset, ret)); - ret = src_file_->preadv2(input->iovec(), input->iovcnt(), offset, flags); - return ret; - } - - if (refill_off + refill_size > static_cast(actual_size_)) { - refill_size = actual_size_ - refill_off; - } - - ret = range_lock_.try_lock_wait(refill_off, refill_size); - if (ret < 0) - return -EAGAIN; - { - static uint32_t max_refilling = pool_ ? pool_->m_max_refilling : MAX_REFILLING; - uint32_t refilling = max_refilling; - DEFER({ - if (refilling >= max_refilling) - range_lock_.unlock(refill_off, refill_size); - }); - IOVector buffer(*allocator_); - auto alloc = buffer.push_back(refill_size); - if (alloc < refill_size) { - LOG_ERROR("memory allocate failed, refill_size:`, alloc:`", refill_size, alloc); - if (input) { - SCOPE_AUDIT("download", AU_FILEOP(get_src_name(), offset, ret)); - ret = src_file_->preadv2(input->iovec(), input->iovcnt(), offset, flags); - return ret; - } else - return -1; - } - - { - SCOPE_AUDIT("download", AU_FILEOP(get_src_name(), refill_off, ret)); - ret = src_file_->preadv2(buffer.iovec(), buffer.iovcnt(), refill_off, flags); - } - - if (ret != static_cast(refill_size)) { - LOG_ERRNO_RETURN( - 0, -1, - "src file read failed, read : `, expectRead : `, actual_size_ : `, offset : `, sum : `", - ret, refill_size, actual_size_, refill_off, buffer.sum()); - } - - // buffer need async refill - IOVector refill_buf(buffer.iovec(), buffer.iovcnt()); - if (input && (off_t)refill_off <= offset) { - auto view = input->view(); - refill_buf.extract_front(offset - refill_off); - ret = refill_buf.memcpy_to(&view, count); - offset += ret; - } else if (input && refill_off + refill_size >= offset + count) { - iovector_view tail_iov; - tail_iov.iovcnt = 0; - input->slice(count - (refill_off - offset), refill_off - offset, &tail_iov); - ret = refill_buf.memcpy_to(&tail_iov); - input->extract_back(ret); - } else - ret = 0; - - if (input && pool_ && pool_->m_thread_pool && - (refilling = pool_->m_refilling.load(std::memory_order_relaxed)) < - pool_->m_max_refilling) { - pool_->m_refilling.fetch_add(1, std::memory_order_relaxed); - ref_.fetch_add(1, std::memory_order_relaxed); - auto ctx = new RefillContext{this, std::move(buffer), refill_off, refill_size, flags}; - auto th = static_cast(pool_->m_thread_pool) - ->thread_create(&async_refill, ctx); - photon::thread_migrate(th, photon::get_vcpu()); - } else { - auto write = do_pwritev2(buffer.iovec(), buffer.iovcnt(), refill_off, flags); - if (write != static_cast(refill_size)) { - if (ENOSPC != errno) - LOG_ERROR( - "cache file write failed : `, error : `, actual_size_ : `, offset : `, sum : `", - write, ERRNO(errno), actual_size_, refill_off, buffer.sum()); - if (!input) - return -1; - } - } - } - - if (input && ret != (ssize_t)count) { - auto tr = try_preadv2(input->iovec(), input->iovcnt(), offset, flags); - if (tr.refill_size != 0 || tr.size < 0) { - SCOPE_AUDIT("download", AU_FILEOP(get_src_name(), offset, tr.size)); - tr.size = src_file_->preadv2(input->iovec(), input->iovcnt(), offset, flags); - if (tr.size + ret != static_cast(count)) - LOG_ERRNO_RETURN(0, -1, "read failed, ret:`, offset:`,sum:`,actual_size_:`", - tr.size, offset, input->sum(), actual_size_); - } - } - - return count; -} - -void ICacheStore::set_cached_size(off_t cached_size) { - if (cached_size_ == 0) { - cached_size_ = cached_size; - } else if (cached_size > cached_size_) { - off_t last = cached_size_ / page_size_ * page_size_; - if (last != cached_size_) - evict(last); - cached_size_ = last; - } else if (cached_size < cached_size_) { - off_t last = cached_size / page_size_ * page_size_; - evict(last); - cached_size_ = last; - } -} - -ICacheStore::try_preadv_result ICacheStore::try_preadv2(const struct iovec *iov, int iovcnt, - off_t offset, int flags) { - try_preadv_result rst; - iovector_view view((iovec *)iov, iovcnt); - rst.iov_sum = view.sum(); - auto q = queryRefillRange(offset, rst.iov_sum); - if (q.first >= 0 && q.second == 0) { // no need to refill - rst.refill_size = 0; - rst.size = do_preadv2(iov, iovcnt, offset, flags); - if (rst.size != (ssize_t)rst.iov_sum) { - rst.refill_size = (size_t)-1; - rst.refill_offset = -1; - } - } else { - rst.refill_size = q.second; - rst.refill_offset = q.first; - } - - return rst; -} - -ssize_t ICacheStore::do_preadv2(const struct iovec *iov, int iovcnt, off_t offset, int flags) { - SmartCloneIOV<32> ciov(iov, iovcnt); - return do_preadv2_mutable(ciov.iov, iovcnt, offset, flags); -} - -ssize_t ICacheStore::do_preadv2_mutable(struct iovec *iov, int iovcnt, off_t offset, int flags) { - return do_preadv2(iov, iovcnt, offset, flags); -} - -ssize_t ICacheStore::do_pwritev2(const struct iovec *iov, int iovcnt, off_t offset, int flags) { - SmartCloneIOV<32> ciov(iov, iovcnt); - return do_pwritev2_mutable(ciov.iov, iovcnt, offset, flags); -} - -ssize_t ICacheStore::do_pwritev2_mutable(struct iovec *iov, int iovcnt, off_t offset, int flags) { - return do_pwritev2(iov, iovcnt, offset, flags); -} - -int ICacheStore::open_src_file(IFile **src_file) { - if (!src_fs_ || (open_flags_ & O_CACHE_ONLY)) { - if (src_file) - *src_file = src_file_; - return 0; - } - photon::scoped_lock l(open_lock_); - if (src_file_) { - if (src_file) - *src_file = src_file_; - return 0; - } - int flags = O_RDONLY; - if (open_flags_ & (O_WRITE_THROUGH | O_WRITE_BACK)) - flags |= O_CREAT; - src_file_ = src_fs_->open(src_name_.c_str(), flags); - if (!src_file_) - LOG_ERRNO_RETURN(0, -1, "open source ` failed", src_name_.c_str()); - if (src_file) - *src_file = src_file_; - return 0; -} - -ssize_t ICacheStore::pwritev2_extend(const struct iovec *iov, int iovcnt, off_t offset, int flags) { - iovector_view view(const_cast(iov), iovcnt); - size_t size = view.sum(); - if (offset % page_size_ != 0) { - LOG_ERROR_RETURN(EINVAL, -1, "offset is not aligned to `, size : `, offset : `", page_size_, - size, offset); - } - - // append only - if (offset + (off_t)size > cached_size_) { - off_t last = cached_size_ / page_size_ * page_size_; - if (last != cached_size_) { - evict(last); - cached_size_ = last; - actual_size_ = cached_size_; - } - } - - auto write = do_pwritev2(iov, iovcnt, offset, flags); - if (write != static_cast(size)) { - if (ENOSPC != errno) - LOG_ERROR( - "cache file write failed : `, error : `, actual_size_ : `, offset : `, sum : `", - write, ERRNO(errno), actual_size_, offset, size); - } - - // append only - if (write > 0 && offset + write > cached_size_) { - cached_size_ = offset + write; - if (actual_size_ < cached_size_) { - actual_size_ = cached_size_; - } - } - - return write; -} - -int ICacheStore::tryget_size() { - if (actual_size_ % page_size_ != 0) - return 0; - if (open_src_file() != 0) - return -1; - struct stat buf; - buf.st_size = 0; - if ((src_file_ && src_file_->fstat(&buf) != 0) || (!src_file_ && fstat(&buf) != 0)) - return -1; - if (buf.st_size != actual_size_) { - set_cached_size(buf.st_size); - actual_size_ = buf.st_size; - } - return 0; -} - -} // namespace FileSystem diff --git a/src/overlaybd/cache/test/CMakeLists.txt b/src/overlaybd/cache/test/CMakeLists.txt deleted file mode 100644 index 768642e4..00000000 --- a/src/overlaybd/cache/test/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -include_directories($ENV{GFLAGS}/include) -link_directories($ENV{GFLAGS}/lib) - -include_directories($ENV{GTEST}/googletest/include) -link_directories($ENV{GTEST}/lib) - -add_executable(cache_test cache_test.cpp) -target_include_directories(cache_test PUBLIC ${PHOTON_INCLUDE_DIR}) -target_link_libraries(cache_test gtest gtest_main gflags pthread photon_static overlaybd_lib) - -add_test( - NAME cache_test - COMMAND ${EXECUTABLE_OUTPUT_PATH}/cache_test -) \ No newline at end of file diff --git a/src/overlaybd/cache/test/cache_test.cpp b/src/overlaybd/cache/test/cache_test.cpp deleted file mode 100644 index d0c0553b..00000000 --- a/src/overlaybd/cache/test/cache_test.cpp +++ /dev/null @@ -1,558 +0,0 @@ -/* - Copyright The Overlaybd Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "photon/common/alog.h" -#include "photon/common/callback.h" -#include "photon/fs/localfs.h" -#include "photon/fs/aligned-file.h" -#include "photon/thread/thread.h" -#include "photon/io/fd-events.h" -#include "photon/io/aio-wrapper.h" -#include "photon/common/io-alloc.h" -#include "../cache.h" -#include "random_generator.h" - -namespace Cache { - -using namespace FileSystem; -using namespace photon::fs; - -// Cleanup and recreate the test dir -inline void SetupTestDir(const std::string& dir) { - std::string cmd = std::string("rm -r ") + dir; - system(cmd.c_str()); - cmd = std::string("mkdir -p ") + dir; - system(cmd.c_str()); -} - -void commonTest(bool cacheIsFull, bool enableDirControl, bool dirFull) { - std::string prefix = ""; - const size_t dirQuota = 32ul * 1024 * 1024; - const uint64_t refillSize = 1024 * 1024; - if (enableDirControl) { - prefix = "/John/bucket/"; - } - - std::string root("/tmp/ease/cache/cache_test/"); - SetupTestDir(root); - - std::string subDir = prefix + "dir/dir/"; - SetupTestDir(root + subDir); - std::system(std::string("touch " + root + subDir + "testFile").c_str()); - - struct stat st; - auto ok = ::stat(std::string(root + subDir + "testFile").c_str(), &st); - EXPECT_EQ(0, ok); - - std::string srcRoot("/tmp/ease/cache/src_test/"); - SetupTestDir(srcRoot); - auto srcFs = new_localfs_adaptor(srcRoot.c_str(), ioengine_psync); - - auto mediaFs = new_localfs_adaptor(root.c_str(), ioengine_libaio); - auto alignFs = new_aligned_fs_adaptor(mediaFs, 4 * 1024, true, true); - auto cacheAllocator = new AlignedAlloc(4 * 1024); - auto roCachedFs = new_full_file_cached_fs(srcFs, alignFs, refillSize, - cacheIsFull ? 0 : 512, 1000 * 1000 * 1, 128ul * 1024 * 1024, cacheAllocator, enableDirControl ? 2 : 0); - auto cachePool = roCachedFs->get_pool(); - - if (dirFull) { - cachePool->set_quota(prefix, dirQuota); - } - SetupTestDir(srcRoot + prefix + "testDir"); - auto srcFile = srcFs->open(std::string(prefix + "/testDir/file_1").c_str(), - O_RDWR|O_CREAT|O_TRUNC, 0644); - - UniformCharRandomGen gen(0, 255); - off_t offset = 0; - uint32_t kPageSize = 4 * 1024; - uint32_t kFileSize = kPageSize * 16384; // 64MB - uint32_t kPageCount = kFileSize / kPageSize; - for (uint32_t i = 0; i < kPageCount; ++i) { - std::vector data; - for (uint32_t j = 0; j < kPageSize; ++j) { - data.push_back(gen.next()); - } - srcFile->pwrite(data.data(), data.size(), offset); - offset += kPageSize; - } - - // write some unaligned - off_t lastOffset = offset; - off_t unAlignedLen = 750; - { - std::vector data; - for (uint32_t j = 0; j < kPageSize; ++j) { - data.push_back(gen.next()); - } - srcFile->pwrite(data.data(), unAlignedLen, offset); - } - - auto cachedFile = static_cast(roCachedFs->open( - std::string(prefix + "/testDir/file_1").c_str(), 0, 0644)); - - // test unaligned block - { - void* buf = malloc(kPageSize); - auto ret = cachedFile->pread(buf, kPageSize, lastOffset); - - std::vector src; - src.reserve(kPageSize); - auto retSrc = srcFile->pread(src.data(), kPageSize, lastOffset); - - EXPECT_EQ(0, std::memcmp(buf, src.data(), unAlignedLen)); - EXPECT_EQ(unAlignedLen, retSrc); - EXPECT_EQ(unAlignedLen, ret); - - LOG_INFO("read again"); - - // read again - ret = cachedFile->pread(buf, kPageSize, lastOffset); - EXPECT_EQ(unAlignedLen, ret); - - free(buf); - } - - // test aligned and unaligned block - { - void* buf = malloc(kPageSize * 4); - auto ret = cachedFile->pread(buf, kPageSize * 4, lastOffset - 2 * kPageSize); - - std::vector src; - src.reserve(kPageSize * 4); - auto retSrc = srcFile->pread(src.data(), kPageSize * 4, lastOffset - 2 * kPageSize); - - EXPECT_EQ(0, std::memcmp(buf, src.data(), 2 * kPageSize + unAlignedLen)); - EXPECT_EQ(2 * kPageSize + unAlignedLen, retSrc); - EXPECT_EQ(2 * kPageSize + unAlignedLen, ret); - - LOG_INFO("read again"); - - // read again - ret = cachedFile->pread(buf, kPageSize * 4, lastOffset - 2 * kPageSize); - EXPECT_EQ(2 * kPageSize + unAlignedLen, ret); - - free(buf); - } - - std::vector readBuf; - readBuf.reserve(kPageSize); - std::vector readSrcBuf; - readSrcBuf.reserve(kPageSize); - for (int i = 0; i != 5; ++i) { - EXPECT_EQ(kPageSize, cachedFile->read(readBuf.data(), kPageSize)); - srcFile->read(readSrcBuf.data(), kPageSize); - EXPECT_EQ(0, std::memcmp(readBuf.data(), readSrcBuf.data(), kPageSize)); - } - - if (enableDirControl && !cacheIsFull) { - CacheStat cstat = {}; - EXPECT_EQ(0, cachePool->stat(&cstat, std::string(prefix + "/testDir/file_1").c_str())); - EXPECT_EQ(kFileSize / refillSize, cstat.total_size); - cstat = {}; - EXPECT_EQ(0, cachedFile->get_store()->stat(&cstat)); - EXPECT_EQ(kFileSize / refillSize, cstat.total_size); - } - - // test refill(3) - if (!cacheIsFull) { - auto inSrcFile = cachedFile->get_source(); - cachedFile->set_source(nullptr); - struct stat stat; - inSrcFile->fstat(&stat); - cachedFile->ftruncate(stat.st_size); - void* buf = malloc(kPageSize * 3); - DEFER(free(buf)); - std::vector src; - src.reserve(kPageSize * 3); - EXPECT_EQ(kPageSize, srcFile->pread(src.data(), kPageSize, 0)); - memcpy(buf, src.data(), kPageSize); - - EXPECT_EQ(kPageSize, cachedFile->refill(buf, kPageSize, 0)); - - memset(buf, 0, kPageSize); - EXPECT_EQ(kPageSize, cachedFile->pread(buf, kPageSize, 0)); - EXPECT_EQ(0, memcmp(buf, src.data(), kPageSize)); - - struct stat st1; - ::stat(std::string(root + prefix + "/testDir/file_1").c_str(), &st1); - EXPECT_EQ(0, cachedFile->evict(0, kPageSize)); - struct stat st2; - ::stat(std::string(root + prefix + "/testDir/file_1").c_str(), &st2); - EXPECT_EQ(kPageSize, st1.st_blocks * 512 - st2.st_blocks * 512); - - // test refill last block - src.clear(); - EXPECT_EQ(kPageSize + unAlignedLen, srcFile->pread(src.data(), kPageSize * 3, lastOffset - kPageSize)); - memcpy(buf, src.data(), kPageSize * 3); - EXPECT_EQ(kPageSize + unAlignedLen, cachedFile->refill(buf, kPageSize * 3, lastOffset - kPageSize)); - memset(buf, 0, kPageSize * 3); - EXPECT_EQ(kPageSize + unAlignedLen, cachedFile->pread(buf, kPageSize * 3, lastOffset - kPageSize)); - EXPECT_EQ(0, memcmp(buf, src.data(), kPageSize + unAlignedLen)); - - cachedFile->set_source(inSrcFile); - } - - // test refill(2) - if (!cacheIsFull) { - auto inSrcFile = cachedFile->get_source(); - - void* buf = malloc(kPageSize * 2); - DEFER(free(buf)); - EXPECT_EQ(0, cachedFile->refill(kPageSize, 2 * kPageSize)); - - cachedFile->set_source(nullptr); - EXPECT_EQ(2 * kPageSize, cachedFile->pread(buf, 2 * kPageSize, kPageSize)); - std::vector src; - src.reserve(kPageSize * 2); - EXPECT_EQ(kPageSize * 2, srcFile->pread(src.data(), 2 * kPageSize, kPageSize)); - EXPECT_EQ(0, memcmp(buf, src.data(), 2 * kPageSize)); - cachedFile->set_source(inSrcFile); - - // prefetch more than 16MB - EXPECT_EQ(0, cachedFile->fadvise(234, 5000 * kPageSize, POSIX_FADV_WILLNEED)); - // prefetch tail - EXPECT_EQ(0, cachedFile->fadvise(lastOffset - kPageSize, 5000 * kPageSize, POSIX_FADV_WILLNEED)); - } - - if (dirFull) { - CacheStat cstat = {}; - EXPECT_EQ(0, cachePool->stat(&cstat, prefix)); - EXPECT_EQ(dirQuota / refillSize, cstat.total_size); - } - - // test aligned section - UniformInt32RandomGen genOffset(0, (kPageCount + 1) * kPageSize); - UniformInt32RandomGen genSize(0, 8 * kPageSize); - struct stat srcSt = {}; - srcFile->fstat(&srcSt); - for (int i = 0; i != 10000; ++i) { - auto tmpOffset = genOffset.next(); - auto size = genSize.next(); - - if (tmpOffset >= srcSt.st_size) { - size = 0; - } else { - size = tmpOffset + size > srcSt.st_size ? srcSt.st_size - tmpOffset : size; - } - void* buf = malloc(size); - auto ret = cachedFile->pread(buf, size, tmpOffset); - - std::vector src; - src.reserve(size); - auto retSrc = srcFile->pread(src.data(), size, tmpOffset); - - EXPECT_EQ(0, std::memcmp(buf, src.data(), size)); - EXPECT_EQ(size, retSrc); - EXPECT_EQ(size, ret); - free(buf); - - if (9900 == i && dirFull) { - cachedFile->get_store()->set_quota(0); - } - } - srcFile->close(); - - photon::thread_usleep(1000 * 1000ull); - ok = ::stat(std::string(root + subDir + "testFile").c_str(), &st); - EXPECT_EQ(cacheIsFull || dirFull ? -1 : 0, ok); - - if (enableDirControl) { - auto ret = cachePool->evict(std::string(prefix + "/testDir").c_str()); - EXPECT_EQ(0, ret); - } - - delete cachedFile; - - // test smaller file - { - auto smallFile = srcFs->open(std::string(prefix + "/testDir/small").c_str(), - O_RDWR|O_CREAT|O_TRUNC, 0644); - DEFER(delete smallFile); - int smallSize = 102; - std::vector smallData; - for (int i = 0; i != smallSize; ++i) { - smallData.push_back(gen.next()); - } - EXPECT_EQ(smallSize, smallFile->pwrite(smallData.data(), smallData.size(), 0)); - - auto smallCache = static_cast(roCachedFs->open( - std::string(prefix + "/testDir/small").c_str(), 0, 0644)); - DEFER(delete smallCache); - - void* sBuffer = malloc(kPageSize); - DEFER(free(sBuffer)); - EXPECT_EQ(smallSize, smallCache->pread(sBuffer, kPageSize, 0)); - EXPECT_EQ(0, std::memcmp(sBuffer, smallData.data(), smallSize)); - - memset(sBuffer, 0, kPageSize); - EXPECT_EQ(smallSize, smallCache->pread(sBuffer, kPageSize, 0)); - EXPECT_EQ(0, std::memcmp(sBuffer, smallData.data(), smallSize)); - - smallFile->close(); - } - - // test refill - { - auto refillFile = srcFs->open(std::string(prefix + "/testDir/refill").c_str(), - O_RDWR|O_CREAT|O_TRUNC, 0644); - DEFER(delete refillFile); - int refillSize = 4097; - std::vector refillData; - for (int i = 0; i != refillSize; ++i) { - refillData.push_back(gen.next()); - } - EXPECT_EQ(refillSize, refillFile->pwrite(refillData.data(), refillData.size(), 0)); - - auto refillCache = static_cast(roCachedFs->open( - std::string(prefix + "/testDir/refill").c_str(), 0, 0644)); - DEFER(delete refillCache); - - void* sBuffer = malloc(kPageSize * 2); - DEFER(free(sBuffer)); - memset(sBuffer, 0, kPageSize * 2); - EXPECT_EQ(kPageSize, refillCache->pread(sBuffer, kPageSize, 0)); - EXPECT_EQ(0, std::memcmp(sBuffer, refillData.data(), kPageSize)); - - memset(sBuffer, 0, kPageSize * 2); - EXPECT_EQ(refillSize, refillCache->pread(sBuffer, kPageSize * 2, 0)); - EXPECT_EQ(0, std::memcmp(sBuffer, refillData.data(), refillSize)); - - refillFile->close(); - } - - delete srcFs; - delete roCachedFs; -} - -TEST(RoCachedFs, Basic) { - commonTest(false, false, false); -} - -TEST(RoCachedFs, BasicCacheFull) { - commonTest(true, false, false); -} - -// TEST(RoCachedFs, BasicWithDirControl) { -// commonTest(false, true, false); -// } - -// TEST(RoCachedFs, BasicCacheFullWithDirControl) { -// commonTest(true, true, false); -// } - -TEST(RoCachedFs, CacheWithOutSrcFile) { - std::string root("/tmp/ease/cache/cache_test_no_src/"); - SetupTestDir(root); - - auto mediaFs = new_localfs_adaptor(root.c_str(), ioengine_libaio); - auto alignFs = new_aligned_fs_adaptor(mediaFs, 4 * 1024, true, true); - auto cacheAllocator = new AlignedAlloc(4 * 1024); - DEFER(delete cacheAllocator); - auto roCachedFs = new_full_file_cached_fs(nullptr, alignFs, 1024 * 1024, - 512, 1000 * 1000 * 1, 128ul * 1024 * 1024, cacheAllocator, 0); - DEFER(delete roCachedFs); - auto cachedFile = static_cast(roCachedFs->open( - std::string("/testDir/file_1").c_str(), 0, 0644)); - DEFER(delete cachedFile); - - cachedFile->ftruncate(1024 * 1024); - std::vector buf; - int len = 8 * 1024; - buf.reserve(len); - EXPECT_EQ(len, cachedFile->pwrite(buf.data(), len, 4 * 1024)); - EXPECT_EQ(len / 2, cachedFile->pread(buf.data(), 4 * 1024, 4 * 1024)); - EXPECT_EQ(-1, cachedFile->pread(buf.data(), len, 0)); - - auto writeFile = static_cast(roCachedFs->open( - std::string("/testDir/file_2").c_str(), 0, 0644)); - DEFER(delete writeFile); - writeFile->ftruncate(1024 * 1024); - buf.assign(len, 'a'); - EXPECT_EQ(len, writeFile->write(buf.data(), len)); - EXPECT_EQ(len, writeFile->write(buf.data(), len)); - std::vector res; - res.reserve(len); - EXPECT_EQ(len, writeFile->pread(res.data(), len, 0)); - EXPECT_EQ(0, std::memcmp(buf.data(), res.data(), len)); - res.assign(len, '0'); - EXPECT_EQ(len, writeFile->pread(res.data(), len, len)); - EXPECT_EQ(0, std::memcmp(buf.data(), res.data(), len)); - EXPECT_EQ(-1, writeFile->pread(res.data(), len, len * 2)); -} - -TEST(RoCachedFS, xattr) { - std::string root("/tmp/ease/cache/cache_xattr/"); - SetupTestDir(root); - - auto srcFs = new_localfs_adaptor(); - auto mediaFs = new_localfs_adaptor(root.c_str()); - auto roCachedFs = new_full_file_cached_fs(srcFs, mediaFs, 1024 * 1024, 512, 1000 * 1000 * 1, - 128ul * 1024 * 1024, nullptr, 0); - DEFER(delete roCachedFs); - - std::string path = "/tmp/ease/cache/cache_xattr/filexattr"; - auto xttarFile = srcFs->open(path.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0644); - DEFER(delete xttarFile); - auto xattrFs = dynamic_cast(roCachedFs); - std::string name = "user.testxattr", value = "yes"; - char key[20], val[20]; - auto ret = xattrFs->setxattr(path.c_str(), name.c_str(), value.c_str(), value.size(), 0); - EXPECT_EQ(0, ret); - ret = xattrFs->listxattr(path.c_str(), key, 20); - EXPECT_EQ(0, std::memcmp(key, name.data(), ret)); - ret = xattrFs->getxattr(path.c_str(), key, val, 20); - EXPECT_EQ(value.size(), ret); - EXPECT_EQ(0, std::memcmp(val, value.data(), ret)); - ret = xattrFs->removexattr(path.c_str(), key); - EXPECT_EQ(0, ret); - - auto cachedFile = static_cast(roCachedFs->open(path.c_str(), 0, 0644)); - DEFER(delete cachedFile); - auto xattrfile = dynamic_cast(cachedFile); - ret = xattrfile->fsetxattr(name.c_str(), value.c_str(), value.size(), 0); - EXPECT_EQ(0, ret); - ret = xattrfile->flistxattr(key, 20); - EXPECT_EQ(0, std::memcmp(key, name.data(), ret)); - ret = xattrfile->fgetxattr(key, val, 20); - EXPECT_EQ(value.size(), ret); - EXPECT_EQ(0, std::memcmp(val, value.data(), ret)); - ret = xattrfile->fremovexattr(key); - EXPECT_EQ(0, ret); -} - -void* worker(void* arg) { - auto fs = (ICachedFileSystem*)arg; - char buffer[1024*1024]; - char buffersrc[1024*1024]; - std::vector offset; - for (auto i = 0; i < 2048; i++) { - offset.push_back(i * 1024 * 1024); - } - auto fd = ::open("/tmp/ease/cache/src_test/huge", O_RDONLY); - DEFER(::close(fd)); - auto f = fs->open("/huge", O_RDONLY); - DEFER(delete f); - for (int i=0;i<4;i++) { - std::random_shuffle(offset.begin(), offset.end()); - for (const auto &x : offset) { - ::pread(fd, buffersrc, 1024*1024, x); - f->pread(buffer, 1024*1024, x); - EXPECT_EQ(0, memcmp(buffer, buffersrc, 1024*1024)); - fs->get_pool()->evict(); - photon::thread_yield(); - } - } - return nullptr; -} - -TEST(CachedFS, write_while_full) { - std::string srcRoot("/tmp/ease/cache/src_test/"); - SetupTestDir(srcRoot); - system("dd if=/dev/urandom of=/tmp/ease/cache/src_test/huge bs=1M count=2048"); - - std::string root("/tmp/ease/cache/cache_test/"); - SetupTestDir(root); - auto srcFs = new_localfs_adaptor(srcRoot.c_str()); - auto mediaFs = new_localfs_adaptor(root.c_str()); - auto roCachedFs = new_full_file_cached_fs(srcFs, mediaFs, 1024 * 1024, 1, 100 * 1000 * 1, - 128ul * 1024 * 1024, nullptr, 0); - - std::vector jhs; - - for (int i=0;i<2;i++) { - jhs.emplace_back(photon::thread_enable_join(photon::thread_create(worker, roCachedFs))); - } - for (auto &x : jhs) { - photon::thread_join(x); - } -} - -TEST(CachedFS, fn_trans_func) { - std::string srcRoot("/tmp/ease/cache/src_test/"); - SetupTestDir(srcRoot); - system("mkdir /tmp/ease/cache/src_test/path_aaa/"); - system("mkdir /tmp/ease/cache/src_test/path_bbb/"); - system("dd if=/dev/urandom of=/tmp/ease/cache/src_test/path_aaa/sha256:test bs=1K count=1"); - system("cp /tmp/ease/cache/src_test/path_aaa/sha256:test /tmp/ease/cache/src_test/path_bbb/sha256:test"); - - std::string root("/tmp/ease/cache/cache_test/"); - SetupTestDir(root); - auto srcFs = new_localfs_adaptor(srcRoot.c_str()); - auto mediaFs = new_localfs_adaptor(root.c_str()); - - struct NameTransCB { - size_t fn_trans_sha256(std::string_view src, char *dest, size_t size) { - auto p = src.find("/sha256:"); - if (p == std::string_view::npos) { - return 0; - } - size_t len = src.size() - p; - if (len > size) { - LOG_WARN("filename length ` exceed `", len, size); - return 0; - } - strcpy(dest, src.data() + p); - return len; - } - }cb; - auto cachedFs = new_full_file_cached_fs(srcFs, mediaFs, 1024 * 1024, 1, 100 * 1000 * 1, - 128ul * 1024 * 1024, nullptr, 0, {&cb, &NameTransCB::fn_trans_sha256}); - char buf1[1024], buf2[1024]; - auto cachedFile1 = static_cast(cachedFs->open("/path_aaa/sha256:test", 0, 0644)); - auto cachedFile2 = static_cast(cachedFs->open("/path_bbb/sha256:test", 0, 0644)); - cachedFile1->read(buf1, 1024); - auto cFile = mediaFs->open("/sha256:test", 0, 0644); - cFile->read(buf2, 1024); - EXPECT_EQ(0, memcmp(buf1, buf2, 1024)); - auto cs1 = cachedFile1->get_store(); - auto cs2 = cachedFile2->get_store(); - EXPECT_EQ(cs1, cs2); -} - -} // namespace Cache - -int main(int argc, char** argv) { - log_output_level = ALOG_ERROR; - photon::vcpu_init(); - ::testing::InitGoogleTest(&argc, argv); - - int ret = photon::fd_events_init(photon::INIT_EVENT_EPOLL); - if (ret < 0) { - LOG_ERROR_RETURN(0, -1, "failed to init epoll subsystem"); - } - - ret = photon::libaio_wrapper_init(); - if (ret < 0) - LOG_ERROR_RETURN(0, -1, "failed to init libaio subsystem"); - - ret = RUN_ALL_TESTS(); - - photon::libaio_wrapper_fini(); - photon::fd_events_fini(); - return ret; -} diff --git a/src/overlaybd/cache/test/random_generator.h b/src/overlaybd/cache/test/random_generator.h deleted file mode 100644 index 8662a1df..00000000 --- a/src/overlaybd/cache/test/random_generator.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright The Overlaybd Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#pragma once - -#include -#include - -namespace Cache { - -/* DataTypes for random generator */ -template -class RandomValueGen { - public: - RandomValueGen() {} - virtual ~RandomValueGen() {} - virtual T next() = 0; -}; - -// an uniform int random generator that produces inclusive-inclusive value range -class UniformInt32RandomGen: public RandomValueGen { - public: - UniformInt32RandomGen() = default; - UniformInt32RandomGen(uint32_t start, uint32_t end, int seed = 1213) - : gen_(seed), dis_(start, end) {} - uint32_t next() override { return dis_(gen_); } - void seed(std::mt19937::result_type val) { gen_.seed(val); } - - private: - std::mt19937 gen_; - std::uniform_int_distribution dis_; -}; - -class UniformCharRandomGen : public RandomValueGen { - public: - UniformCharRandomGen(uint32_t start, uint32_t end, int seed = 1213) - : gen_(seed), dis_(start, end) {} - unsigned char next() { return dis_(gen_); } - - private: - std::mt19937 gen_; - std::uniform_int_distribution dis_; -}; - -} // namespace Cache diff --git a/src/overlaybd/gzindex/test/CMakeLists.txt b/src/overlaybd/gzindex/test/CMakeLists.txt index 9decda13..4aebd196 100644 --- a/src/overlaybd/gzindex/test/CMakeLists.txt +++ b/src/overlaybd/gzindex/test/CMakeLists.txt @@ -7,7 +7,7 @@ link_directories($ENV{GTEST}/lib) add_executable(gzindex_test test.cpp) target_include_directories(gzindex_test PUBLIC ${PHOTON_INCLUDE_DIR}) target_link_libraries(gzindex_test gtest gtest_main gflags pthread photon_static - gzindex_lib gzip_lib cache_lib checksum_lib) + gzindex_lib gzip_lib gzip_cache_lib photon_cache_lib checksum_lib) add_test( NAME gzindex_test diff --git a/src/overlaybd/gzindex/test/test.cpp b/src/overlaybd/gzindex/test/test.cpp index 69c7086e..623e7d8e 100644 --- a/src/overlaybd/gzindex/test/test.cpp +++ b/src/overlaybd/gzindex/test/test.cpp @@ -16,8 +16,8 @@ #include "../gzfile.h" #include "../../gzip/gz.h" -#include "../../cache/gzip_cache/cached_fs.h" -#include "../../cache/cache.h" +#include "../../gzip_cache/cached_fs.h" +#include #include #include #include @@ -406,7 +406,7 @@ class GzCacheTest : public ::testing::Test { } auto mediafs = photon::fs::new_localfs_adaptor("/tmp/gzip_cache_compress"); - lfs = FileSystem::new_full_file_cached_fs( + lfs = photon::fs::new_full_file_cached_fs( lfs, mediafs, 1024 * 1024, 1, 10000000, (uint64_t)1048576 * 4096, nullptr, 0, nullptr); delete gzdata; diff --git a/src/overlaybd/cache/gzip_cache/CMakeLists.txt b/src/overlaybd/gzip_cache/CMakeLists.txt similarity index 67% rename from src/overlaybd/cache/gzip_cache/CMakeLists.txt rename to src/overlaybd/gzip_cache/CMakeLists.txt index 2e844bab..3a87fdde 100644 --- a/src/overlaybd/cache/gzip_cache/CMakeLists.txt +++ b/src/overlaybd/gzip_cache/CMakeLists.txt @@ -1,7 +1,10 @@ file(GLOB SRC_FRONTEND "*.cpp") add_library(gzip_cache_lib STATIC ${SRC_FRONTEND}) -target_link_libraries(gzip_cache_lib gzindex_lib) +target_link_libraries(gzip_cache_lib + gzindex_lib + photon_static +) target_include_directories(gzip_cache_lib PUBLIC ${PHOTON_INCLUDE_DIR} -) \ No newline at end of file +) diff --git a/src/overlaybd/cache/gzip_cache/cached_fs.cpp b/src/overlaybd/gzip_cache/cached_fs.cpp similarity index 75% rename from src/overlaybd/cache/gzip_cache/cached_fs.cpp rename to src/overlaybd/gzip_cache/cached_fs.cpp index d28de2ae..1135f422 100644 --- a/src/overlaybd/cache/gzip_cache/cached_fs.cpp +++ b/src/overlaybd/gzip_cache/cached_fs.cpp @@ -14,21 +14,21 @@ limitations under the License. */ #include "cached_fs.h" -#include "../full_file_cache/cache_pool.h" -#include "../cache.h" +#include #include +#include namespace Cache { class GzipCachedFsImpl : public GzipCachedFs { public: - GzipCachedFsImpl(FileSystem::ICachePool *pool, size_t page_size, + GzipCachedFsImpl(photon::fs::ICachedFileSystem *cache_fs, size_t page_size, size_t refill_unit, IOAlloc *io_alloc) - : pool_(pool), page_size_(page_size), + : cache_fs_(cache_fs), pool_(cache_fs->get_pool()), page_size_(page_size), refill_unit_(refill_unit), io_alloc_(io_alloc) { } ~GzipCachedFsImpl() { - delete pool_; + delete cache_fs_; } photon::fs::IFile *open_cached_gzip_file(photon::fs::IFile *file, const char *file_name) { @@ -47,7 +47,7 @@ class GzipCachedFsImpl : public GzipCachedFs { cache_store->set_src_file(file); cache_store->set_allocator(io_alloc_); cache_store->set_page_size(page_size_); - auto ret = FileSystem::new_cached_file(cache_store, page_size_, nullptr); + auto ret = photon::fs::new_cached_file(cache_store, page_size_, nullptr); if (ret == nullptr) { // if create file is failed // file and cache_store must be release, or will leak delete file; @@ -56,7 +56,8 @@ class GzipCachedFsImpl : public GzipCachedFs { return ret; } private: - FileSystem::ICachePool *pool_; + photon::fs::ICachedFileSystem *cache_fs_; + photon::fs::ICachePool *pool_; size_t page_size_; size_t refill_unit_; IOAlloc *io_alloc_; @@ -71,9 +72,12 @@ GzipCachedFs *new_gzip_cached_fs(photon::fs::IFileSystem *mediaFs, uint64_t refi if (!allocator) { allocator = new IOAlloc; } - FileCachePool *pool = nullptr; - pool = new FileCachePool(mediaFs, capacityInGB, periodInUs, diskAvailInBytes, refillUnit); - pool->Init(); - return new GzipCachedFsImpl(pool, 4096, refillUnit, allocator); + auto cache_fs = photon::fs::new_full_file_cached_fs( + nullptr, mediaFs, refillUnit, capacityInGB, periodInUs, diskAvailInBytes, + allocator, 0); + if (cache_fs == nullptr) { + LOG_ERRNO_RETURN(0, nullptr, "Create Photon full-file cache for gzip failed"); + } + return new GzipCachedFsImpl(cache_fs, 4096, refillUnit, allocator); } } // namespace Cache diff --git a/src/overlaybd/cache/gzip_cache/cached_fs.h b/src/overlaybd/gzip_cache/cached_fs.h similarity index 100% rename from src/overlaybd/cache/gzip_cache/cached_fs.h rename to src/overlaybd/gzip_cache/cached_fs.h diff --git a/src/overlaybd/registryfs/registryfs.cpp b/src/overlaybd/registryfs/registryfs.cpp index 27b52954..0744dbdf 100644 --- a/src/overlaybd/registryfs/registryfs.cpp +++ b/src/overlaybd/registryfs/registryfs.cpp @@ -14,7 +14,6 @@ limitations under the License. */ #include "registryfs.h" -#include "../base64.h" #include #include @@ -36,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +43,7 @@ #include using namespace photon::fs; +using photon::Timeout; static const estring kDockerRegistryAuthChallengeKeyValuePrefix = "www-authenticate"; static const estring kAuthHeaderKey = "Authorization"; @@ -345,7 +346,8 @@ class RegistryFSImpl : public RegistryFS { photon::net::StringWriter writer; if (!username.empty()) { std::string basic_auth = username + ":" + password; - std::string encoded = base64_encode((const BYTE*) basic_auth.c_str(), basic_auth.length()); + std::string encoded; + photon::net::Base64Encode(basic_auth, encoded); req->append_header(kAuthHeaderKey, kBasicAuthPrefix + encoded); } auto ret = req->GET(auth_url, &writer, tmo.timeout_us()); @@ -426,7 +428,7 @@ class RegistryFileImpl : public photon::fs::VirtualReadOnlyFile { if (code != 200 && code != 206) { ERRNO eno; - if (timeout.expire() < photon::now) { + if (timeout.expiration() < photon::now) { LOG_ERROR_RETURN(ETIMEDOUT, -1, "timed out in preadv ", VALUE(m_url), VALUE(offset)); } @@ -464,7 +466,7 @@ class RegistryFileImpl : public photon::fs::VirtualReadOnlyFile { again: auto code = m_fs->GET(m_url.c_str(), &headers, -1, -1, nullptr, tmo.timeout()); if (code != 200 && code != 206) { - if (tmo.expire() < photon::now) + if (tmo.expiration() < photon::now) LOG_ERROR_RETURN(ETIMEDOUT, -1, "Get meta timedout"); if (retry--) goto again; diff --git a/src/overlaybd/registryfs/registryfs_v2.cpp b/src/overlaybd/registryfs/registryfs_v2.cpp index 92416bc3..ca08322b 100644 --- a/src/overlaybd/registryfs/registryfs_v2.cpp +++ b/src/overlaybd/registryfs/registryfs_v2.cpp @@ -50,6 +50,7 @@ using namespace photon::fs; using namespace photon::net::http; +using photon::Timeout; static const estring kDockerRegistryAuthChallengeKeyValuePrefix = "www-authenticate"; static const estring kAuthHeaderKey = "Authorization"; @@ -468,7 +469,7 @@ class RegistryFileImpl_v2 : public photon::fs::VirtualReadOnlyFile { auto code = m_fs->get_data(m_url, offset, count, tmo.timeout(), op); if (code != 200 && code != 206) { ERRNO eno; - if (tmo.expire() < photon::now) { + if (tmo.expiration() < photon::now) { LOG_ERROR_RETURN(ETIMEDOUT, -1, "timed out in preadv ", VALUE(m_url), VALUE(offset)); } if (retry--) { @@ -491,7 +492,7 @@ class RegistryFileImpl_v2 : public photon::fs::VirtualReadOnlyFile { HTTP_OP op; auto code = m_fs->get_data(m_url, 0, 1, tmo.timeout(), op); if (code != 200 && code != 206) { - if (tmo.expire() < photon::now) + if (tmo.expiration() < photon::now) LOG_ERROR_RETURN(ETIMEDOUT, -1, "get meta timedout"); if (retry--) goto again; diff --git a/src/overlaybd/stream_convertor/stream_conv.cpp b/src/overlaybd/stream_convertor/stream_conv.cpp index 58722ee7..30817a6c 100644 --- a/src/overlaybd/stream_convertor/stream_conv.cpp +++ b/src/overlaybd/stream_convertor/stream_conv.cpp @@ -143,7 +143,7 @@ class StreamConvertor { m_tcp_serv = new_tcp_socket_server(); m_tcp_serv->timeout(1000UL*1000); if ( gconfig.globalConfig().reusePort() ){ - m_tcp_serv->setsockopt(SOL_SOCKET, SO_REUSEPORT, 1); + m_tcp_serv->setsockopt(SOL_SOCKET, SO_REUSEPORT, 1); } if (m_tcp_serv->bind(gconfig.globalConfig().httpPort(), IPAddr(httpAddr.c_str())) != 0) { LOG_ERRNO_RETURN(0, -1, "Failed to bind to port ", diff --git a/src/overlaybd/zfile/compressor.cpp b/src/overlaybd/zfile/compressor.cpp index d1dfee3e..3b83c41b 100644 --- a/src/overlaybd/zfile/compressor.cpp +++ b/src/overlaybd/zfile/compressor.cpp @@ -16,6 +16,7 @@ #include "compressor.h" #include "lz4/lz4.h" +#include #include #include #include @@ -45,13 +46,10 @@ static std::atomic g_qat_state{0}; #endif class BaseCompressor : public ICompressor { public: + enum : size_t { MAX_BATCH = 256 }; uint32_t max_dst_size = 0; uint32_t src_blk_size = 0; - // vector raw_data; - vector compressed_data; - vector uncompressed_data; - - const int DEFAULT_N_BATCH = 256; + enum { DEFAULT_N_BATCH = MAX_BATCH }; virtual int init(const CompressArgs *args) { auto opt = &args->opt; @@ -61,9 +59,6 @@ class BaseCompressor : public ICompressor { src_blk_size = opt->block_size; LOG_DEBUG("create batch buffer, size: `", nbatch()); - // raw_data.resize(nbatch()); - compressed_data.resize(nbatch()); - uncompressed_data.resize(nbatch()); return 0; } @@ -72,11 +67,15 @@ class BaseCompressor : public ICompressor { } virtual int do_compress(size_t *src_chunk_len /* uncompressed length per block */, - size_t *dst_chunk_len, size_t dst_buffer_capacity, size_t nblock) = 0; + size_t *dst_chunk_len, size_t dst_buffer_capacity, size_t nblock, + unsigned char **compressed_data, + unsigned char **uncompressed_data) = 0; virtual int do_decompress(size_t *src_chunk_len, /* compressed length per block */ size_t *dst_chunk_len, - size_t dst_buffer_capacity, size_t nblock) = 0; + size_t dst_buffer_capacity, size_t nblock, + unsigned char **compressed_data, + unsigned char **uncompressed_data) = 0; virtual int compress(const unsigned char *src, size_t src_len, unsigned char *dst, size_t dst_len) override { @@ -91,9 +90,14 @@ class BaseCompressor : public ICompressor { int compress_batch(const unsigned char *src, size_t *src_chunk_len, unsigned char *dst, size_t dst_buffer_capacity, size_t *dst_chunk_len, size_t n) override { + if (n == 0 || n > MAX_BATCH) { + LOG_ERROR_RETURN(EINVAL, -1, "invalid batch size (`), maximum is `", n, MAX_BATCH); + } if (dst_buffer_capacity / n < max_dst_size) { LOG_ERROR_RETURN(ENOBUFS, -1, "dst_len should be greater than `", max_dst_size - 1); } + std::array compressed_data; + std::array uncompressed_data; off_t src_offset = 0, dst_offset = 0; for (size_t i = 0; i < n; i++) { uncompressed_data[i] = ((unsigned char *)src + src_offset); @@ -101,7 +105,8 @@ class BaseCompressor : public ICompressor { src_offset += src_chunk_len[i]; dst_offset += dst_buffer_capacity / n; } - return do_compress(src_chunk_len, dst_chunk_len, dst_buffer_capacity, n); + return do_compress(src_chunk_len, dst_chunk_len, dst_buffer_capacity, n, + compressed_data.data(), uncompressed_data.data()); } virtual int decompress(const unsigned char *src, size_t src_len, unsigned char *dst, @@ -117,11 +122,16 @@ class BaseCompressor : public ICompressor { int decompress_batch(const unsigned char *src, size_t *src_chunk_len, unsigned char *dst, size_t dst_buffer_capacity, size_t *dst_chunk_len, size_t n) override { + if (n == 0 || n > MAX_BATCH) { + LOG_ERROR_RETURN(EINVAL, -1, "invalid batch size (`), maximum is `", n, MAX_BATCH); + } if (dst_buffer_capacity / n < src_blk_size) { LOG_ERROR_RETURN(ENOBUFS, -1, "dst_len (`) should be greater than compressed block size `", dst_buffer_capacity / n, src_blk_size); } + std::array compressed_data; + std::array uncompressed_data; off_t src_offset = 0, dst_offset = 0; for (size_t i = 0; i < n; i++) { compressed_data[i] = ((unsigned char *)src + src_offset); @@ -130,7 +140,8 @@ class BaseCompressor : public ICompressor { dst_offset += dst_buffer_capacity / n; } - return do_decompress(src_chunk_len, dst_chunk_len, dst_buffer_capacity, n); + return do_decompress(src_chunk_len, dst_chunk_len, dst_buffer_capacity, n, + compressed_data.data(), uncompressed_data.data()); } }; @@ -195,9 +206,6 @@ class LZ4Compressor : public BaseCompressor { if (qat_init(pQat) == 0) { qat_enable = true; g_qat_state.store(1, std::memory_order_release); - /* nbatch() now returns DEFAULT_N_BATCH (was 1 when BaseCompressor::init ran). */ - compressed_data.resize(DEFAULT_N_BATCH); - uncompressed_data.resize(DEFAULT_N_BATCH); } else { delete pQat; pQat = nullptr; @@ -213,7 +221,9 @@ class LZ4Compressor : public BaseCompressor { } virtual int do_compress(size_t *src_chunk_len, size_t *dst_chunk_len, - size_t dst_buffer_capacity, size_t nblock) override { + size_t dst_buffer_capacity, size_t nblock, + unsigned char **compressed_data, + unsigned char **uncompressed_data) override { int ret = 0; #ifdef ENABLE_QAT @@ -245,7 +255,8 @@ class LZ4Compressor : public BaseCompressor { } int do_decompress(size_t *src_chunk_len, size_t *dst_chunk_len, size_t dst_buffer_capacity, - size_t n) override { + size_t n, unsigned char **compressed_data, + unsigned char **uncompressed_data) override { int ret = 0; #ifdef ENABLE_QAT @@ -310,7 +321,8 @@ class Compressor_zstd : public BaseCompressor { virtual int do_compress(size_t *src_chunk_len /* uncompressed length per block */, size_t *dst_chunk_len, size_t dst_buffer_capacity, - size_t nblock) override { + size_t nblock, unsigned char **compressed_data, + unsigned char **uncompressed_data) override { int ret = 0; for (size_t i = 0; i < nblock; i++) { @@ -326,7 +338,9 @@ class Compressor_zstd : public BaseCompressor { virtual int do_decompress(size_t *src_chunk_len, /* compressed length per block */ size_t *dst_chunk_len, - size_t dst_buffer_capacity, size_t nblock) override { + size_t dst_buffer_capacity, size_t nblock, + unsigned char **compressed_data, + unsigned char **uncompressed_data) override { int ret = 0; for (size_t i = 0; i < nblock; i++) { diff --git a/src/test/image_service_test.cpp b/src/test/image_service_test.cpp index ae1a8a5f..d7f229df 100644 --- a/src/test/image_service_test.cpp +++ b/src/test/image_service_test.cpp @@ -42,7 +42,7 @@ char *test_ua = nullptr; photon::net::ISocketServer *new_server(std::string ip, uint16_t port) { auto server = photon::net::new_tcp_socket_server(); server->timeout(1000UL*1000); - server->setsockopt(SOL_SOCKET, SO_REUSEPORT, 1); + server->setsockopt(SOL_SOCKET, SO_REUSEPORT, 1); server->bind(port, photon::net::IPAddr(ip.c_str())); server->listen(); server->set_handler(nullptr); @@ -154,7 +154,7 @@ TEST(http_client, user_agent) { client->set_user_agent(test_ua); DEFER(delete client); auto op = client->new_operation(photon::net::http::Verb::GET, target_get); - DEFER(delete op); + DEFER(client->destroy_operation(op)); op->req.headers.content_length(0); client->call(op); EXPECT_EQ(op->status_code, 200); @@ -291,7 +291,7 @@ class HTTPServerTest : public DevIDRegisterTest { auto client = photon::net::http::new_http_client(); DEFER(delete client); auto op = client->new_operation(photon::net::http::Verb::GET, request_url); - DEFER(delete op); + DEFER(client->destroy_operation(op)); op->req.headers.content_length(0); // std::cout << "op->req.target(): " << op->req.target() << " op->req.query(): " << op->req.query() << std::endl; client->call(op); diff --git a/src/test/simple_credsrv_test.cpp b/src/test/simple_credsrv_test.cpp index 169c47df..eaeda14b 100644 --- a/src/test/simple_credsrv_test.cpp +++ b/src/test/simple_credsrv_test.cpp @@ -78,7 +78,7 @@ class SimpleAuthHandler : public HTTPHandler { TEST(auth, http_server) { auto tcpserver = photon::net::new_tcp_socket_server(); tcpserver->timeout(1000UL*1000); - tcpserver->setsockopt(SOL_SOCKET, SO_REUSEPORT, 1); + tcpserver->setsockopt(SOL_SOCKET, SO_REUSEPORT, 1); tcpserver->bind(19876, IPAddr("127.0.0.1")); tcpserver->listen(); DEFER(delete tcpserver); From 179b773bfe535fbd5f701300af42b7718599545b Mon Sep 17 00:00:00 2001 From: Lanzheng Liu Date: Fri, 28 Aug 2026 10:22:52 +0800 Subject: [PATCH 2/9] feat: integrate photon-libtcmu sources Signed-off-by: Lanzheng Liu --- CMake/Findnl.cmake | 18 + CMake/Findtcmu.cmake | 17 - CMakeLists.txt | 1 - README.md | 6 + THIRD_PARTY_NOTICES.md | 33 + src/CMakeLists.txt | 3 +- src/libtcmu/CMakeLists.txt | 23 + src/libtcmu/LICENSE.Apache2 | 201 +++ src/libtcmu/README.md | 20 + src/libtcmu/api.cpp | 382 +++++ src/libtcmu/be_byteshift.h | 70 + src/libtcmu/configfs.cpp | 316 ++++ .../0001-silent-ignore-unmatched-subtype.md | 36 + src/libtcmu/libtcmu.cpp | 1394 +++++++++++++++++ src/libtcmu/libtcmu.h | 125 ++ src/libtcmu/libtcmu_common.h | 189 +++ src/libtcmu/libtcmu_priv.h | 70 + src/libtcmu/scsi.cpp | 936 +++++++++++ src/libtcmu/scsi.h | 39 + src/libtcmu/scsi_defs.h | 121 ++ src/libtcmu/target_core_user_local.h | 153 ++ 21 files changed, 4134 insertions(+), 19 deletions(-) create mode 100644 CMake/Findnl.cmake delete mode 100644 CMake/Findtcmu.cmake create mode 100644 THIRD_PARTY_NOTICES.md create mode 100644 src/libtcmu/CMakeLists.txt create mode 100644 src/libtcmu/LICENSE.Apache2 create mode 100644 src/libtcmu/README.md create mode 100644 src/libtcmu/api.cpp create mode 100644 src/libtcmu/be_byteshift.h create mode 100644 src/libtcmu/configfs.cpp create mode 100644 src/libtcmu/docs/adr/0001-silent-ignore-unmatched-subtype.md create mode 100644 src/libtcmu/libtcmu.cpp create mode 100644 src/libtcmu/libtcmu.h create mode 100644 src/libtcmu/libtcmu_common.h create mode 100644 src/libtcmu/libtcmu_priv.h create mode 100644 src/libtcmu/scsi.cpp create mode 100644 src/libtcmu/scsi.h create mode 100644 src/libtcmu/scsi_defs.h create mode 100644 src/libtcmu/target_core_user_local.h diff --git a/CMake/Findnl.cmake b/CMake/Findnl.cmake new file mode 100644 index 00000000..81290ead --- /dev/null +++ b/CMake/Findnl.cmake @@ -0,0 +1,18 @@ +find_library(LIBNL_LIB nl-3) +find_library(LIBNL_GENL_LIB nl-genl-3) +set(LIBNL_LIBS + ${LIBNL_LIB} + ${LIBNL_GENL_LIB} +) + +find_path(LIBNL_INCLUDE_DIR + NAMES + netlink/netlink.h + PATH_SUFFIXES + libnl3 +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(nl DEFAULT_MSG LIBNL_LIBS LIBNL_INCLUDE_DIR) + +mark_as_advanced(LIBNL_INCLUDE_DIR LIBNL_LIBS) diff --git a/CMake/Findtcmu.cmake b/CMake/Findtcmu.cmake deleted file mode 100644 index 92d6ec65..00000000 --- a/CMake/Findtcmu.cmake +++ /dev/null @@ -1,17 +0,0 @@ -include(FetchContent) -set(FETCHCONTENT_QUIET false) - -FetchContent_Declare( - tcmu - GIT_REPOSITORY https://github.com/data-accelerator/photon-libtcmu.git - GIT_TAG 813fd65361bb2f348726b9c41478a44211847614 -) - -if(BUILD_TESTING) - set(BUILD_TESTING 0) - FetchContent_MakeAvailable(tcmu) - set(BUILD_TESTING 1) -else() - FetchContent_MakeAvailable(tcmu) -endif() -set(TCMU_INCLUDE_DIR ${tcmu_SOURCE_DIR}/) diff --git a/CMakeLists.txt b/CMakeLists.txt index 98a753e9..46d49afc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,7 +49,6 @@ option(ORIGIN_EXT2FS "Use original libext2fs" off) find_package(e2fs REQUIRED) find_package(photon REQUIRED) -find_package(tcmu REQUIRED) if(BUILD_STREAM_CONVERTOR) find_package(yaml-cpp) diff --git a/README.md b/README.md index 0bc65424..c6af63f4 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,12 @@ Overlaybd has 2 core component: This repository is an implementation of overlaybd based on [TCMU](https://www.kernel.org/doc/Documentation/target/tcmu-design.txt). +The userspace TCMU support is provided by the bundled +[photon-libtcmu](https://github.com/data-accelerator/photon-libtcmu) source in +`src/libtcmu`. It is built with the same PhotonLibOS instance as +OverlayBD. photon-libtcmu is available under LGPL-2.1 or Apache-2.0; OverlayBD +uses it under Apache-2.0. See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). + Overlaybd can be used as the storage backend of [Accelerated Container Image](https://github.com/containerd/accelerated-container-image), which is a solution of remote container image by fetching image data on-demand without downloading and unpacking the whole image before the container starts. Benefits from the universality of block-device, overlaybd is also a widely applicable image format for most runtime, including qemu/kvm and any other runtime supporting block or scsi api. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 00000000..85296642 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,33 @@ +# Third-Party Notices + +## tcmu-runner (via photon-libtcmu) + +The sources under `src/libtcmu` were imported from +[data-accelerator/photon-libtcmu](https://github.com/data-accelerator/photon-libtcmu) +at commit `cdcbc3cae674136da05f1f043525e11e79845c77`. + +photon-libtcmu is derived from +[open-iscsi/tcmu-runner](https://github.com/open-iscsi/tcmu-runner). It contains +only the libtcmu-related subset needed by consumers and does not include the +tcmu-runner daemon, handlers, or backing stores. The retained libtcmu code was +adapted to use PhotonLibOS and includes fixes that were not present in +tcmu-runner. + +photon-libtcmu was originally maintained as a separate project so that it +could track and synchronize with tcmu-runner. Because tcmu-runner is no longer +actively maintained, continued synchronization is no longer practical. +Rather than develop photon-libtcmu as an independent fork, its maintained +source has therefore been incorporated into OverlayBD. + +The principal upstream for the incorporated code is tcmu-runner. The original +copyright and license notices in the source files are retained. Those files +are offered under the recipient's choice of the GNU Lesser General Public +License, version 2.1 or any later version, or the Apache License, version 2.0. +OverlayBD elects to use and distribute this bundled copy under Apache-2.0. The +complete Apache-2.0 license text is preserved in: + +- `src/libtcmu/LICENSE.Apache2` + +Changes made for PhotonLibOS integration, additional fixes, and subsequent +OverlayBD maintenance are recorded in the corresponding project histories and +OverlayBD commits. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 44b0030c..d1574d2d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ find_package(RapidJSON REQUIRED MODULE) link_libraries(rt pthread resolv) +add_subdirectory(libtcmu) add_subdirectory(overlaybd) add_library(overlaybd_image_lib @@ -49,7 +50,7 @@ add_executable(overlaybd-tcmu main.cpp ) target_include_directories(overlaybd-tcmu PUBLIC - ${TCMU_INCLUDE_DIR} + libtcmu/ ${CURL_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${RAPIDJSON_INCLUDE_DIRS} diff --git a/src/libtcmu/CMakeLists.txt b/src/libtcmu/CMakeLists.txt new file mode 100644 index 00000000..cf4cbe74 --- /dev/null +++ b/src/libtcmu/CMakeLists.txt @@ -0,0 +1,23 @@ +# libtcmu is built as part of the OverlayBD project; photon_static and the +# CMake module path (for Findnl) are provided by the top-level CMakeLists. + +find_package(nl REQUIRED) + +add_library(tcmu_static + configfs.cpp + api.cpp + libtcmu.cpp + scsi.cpp +) +set_target_properties(tcmu_static PROPERTIES OUTPUT_NAME tcmu) +target_include_directories(tcmu_static + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} + PUBLIC ${LIBNL_INCLUDE_DIR} + PUBLIC ${PHOTON_INCLUDE_DIR} +) +target_compile_options(tcmu_static PRIVATE -fpermissive) +target_link_libraries(tcmu_static + ${LIBNL_LIB} + ${LIBNL_GENL_LIB} + photon_static +) diff --git a/src/libtcmu/LICENSE.Apache2 b/src/libtcmu/LICENSE.Apache2 new file mode 100644 index 00000000..ad410e11 --- /dev/null +++ b/src/libtcmu/LICENSE.Apache2 @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/src/libtcmu/README.md b/src/libtcmu/README.md new file mode 100644 index 00000000..bd723e13 --- /dev/null +++ b/src/libtcmu/README.md @@ -0,0 +1,20 @@ +# Photon-libtcmu + +Photon-libtcmu is a PhotonLibOS-based implementation of libtcmu. It is derived +from [tcmu-runner](https://github.com/open-iscsi/tcmu-runner) and contains only +the libtcmu-related subset, without the tcmu-runner daemon, handlers, or backing +stores. The retained code has been adapted to use +[PhotonLibOS](https://github.com/alibaba/PhotonLibOS). + +## License + +The original tcmu-runner libtcmu source files are offered under the recipient's +choice of the GNU Lesser General Public License, version 2.1 or any later +version, or the Apache License, version 2.0. The original copyright and license +notices are retained in the source files. + +OverlayBD elects to use and distribute this incorporated copy under the Apache +License, version 2.0. The complete license text is available in +[LICENSE.Apache2](LICENSE.Apache2). See the repository-level +[THIRD_PARTY_NOTICES.md](../../THIRD_PARTY_NOTICES.md) for provenance and +additional history. diff --git a/src/libtcmu/api.cpp b/src/libtcmu/api.cpp new file mode 100644 index 00000000..70624b1f --- /dev/null +++ b/src/libtcmu/api.cpp @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2014 Red Hat, Inc. + * + * This file is licensed to you under your choice of the GNU Lesser + * General Public License, version 2.1 or any later version (LGPLv2.1 or + * later), or the Apache License 2.0. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libtcmu_common.h" +#include "libtcmu_priv.h" +#include "be_byteshift.h" + +#include + +__thread int __tcmu_is_ework_thread = 0; + +int tcmu_cdb_get_length(uint8_t *cdb) +{ + uint8_t group_code = cdb[0] >> 5; + + /* See spc-4 4.2.5.1 operation code */ + switch (group_code) { + case 0: /*000b for 6 bytes commands */ + return 6; + case 1: /*001b for 10 bytes commands */ + case 2: /*010b for 10 bytes commands */ + return 10; + case 3: /*011b Reserved ? */ + if (cdb[0] == 0x7f) + return 8 + cdb[7]; + goto cdb_not_supp; + case 4: /*100b for 16 bytes commands */ + return 16; + case 5: /*101b for 12 bytes commands */ + return 12; + case 6: /*110b Vendor Specific */ + case 7: /*111b Vendor Specific */ + default: + /* TODO: */ + goto cdb_not_supp; + } + +cdb_not_supp: + LOG_ERROR("CDB `0x not supported.", cdb[0]); + return -EINVAL; +} + +uint64_t tcmu_cdb_get_lba(uint8_t *cdb) +{ + uint16_t val; + + switch (tcmu_cdb_get_length(cdb)) { + case 6: + val = get_unaligned_be16(&cdb[2]); + return ((cdb[1] & 0x1f) << 16) | val; + case 10: + return get_unaligned_be32(&cdb[2]); + case 12: + return get_unaligned_be32(&cdb[2]); + case 16: + return get_unaligned_be64(&cdb[2]); + default: + assert(0); + return 0; /* not reached */ + } +} + +uint32_t tcmu_cdb_get_xfer_length(uint8_t *cdb) +{ + switch (tcmu_cdb_get_length(cdb)) { + case 6: + return cdb[4]; + case 10: + return get_unaligned_be16(&cdb[7]); + case 12: + return get_unaligned_be32(&cdb[6]); + case 16: + return get_unaligned_be32(&cdb[10]); + default: + assert(0); + return 0; /* not reached */ + } +} + +/* + * Returns location of first mismatch between bytes in mem and the iovec. + * If they are the same, return -1. + */ +off_t tcmu_iovec_compare(void *mem, struct iovec *iovec, size_t size) +{ + off_t mem_off; + int ret; + + mem_off = 0; + while (size) { + size_t part = std::min(size, iovec->iov_len); + + ret = memcmp(mem + mem_off, iovec->iov_base, part); + if (ret) { + size_t pos; + char *spos = (char *)mem + mem_off; + char *dpos = (char *)iovec->iov_base; + + /* + * Data differed, this is assumed to be 'rare' + * so use a much more expensive byte-by-byte + * comparison to find out at which offset the + * data differs. + */ + for (pos = 0; pos < part && *spos++ == *dpos++; + pos++) + ; + + return pos + mem_off; + } + + size -= part; + mem_off += part; + iovec++; + } + + return -1; +} + +/* + * Consume an iovec. Count must not exceed the total iovec[] size. + */ +size_t tcmu_iovec_seek(struct iovec *iovec, size_t count) +{ + size_t consumed = 0; + + while (count) { + if (count >= iovec->iov_len) { + count -= iovec->iov_len; + iovec->iov_len = 0; + iovec++; + consumed++; + } else { + iovec->iov_base += count; + iovec->iov_len -= count; + count = 0; + } + } + + return consumed; +} + +/* + * Consume an iovec. Count must not exceed the total iovec[] size. + * iove count should be updated. + */ +void tcmu_cmd_seek(struct tcmulib_cmd *cmd, size_t count) +{ + cmd->iov_cnt -= tcmu_iovec_seek(cmd->iovec, count); +} + +size_t tcmu_iovec_length(struct iovec *iovec, size_t iov_cnt) +{ + size_t length = 0; + + while (iov_cnt) { + length += iovec->iov_len; + iovec++; + iov_cnt--; + } + + return length; +} + +void __tcmu_sense_set_data(uint8_t *sense_buf, uint8_t key, uint16_t asc_ascq) +{ + sense_buf[0] |= 0x70; /* fixed, current */ + sense_buf[2] = key; + sense_buf[7] = 0xa; + sense_buf[12] = (asc_ascq >> 8) & 0xff; + sense_buf[13] = asc_ascq & 0xff; +} + +int tcmu_sense_set_data(uint8_t *sense_buf, uint8_t key, uint16_t asc_ascq) +{ + memset(sense_buf, 0, SENSE_BUFFERSIZE); + __tcmu_sense_set_data(sense_buf, key, asc_ascq); + return TCMU_STS_PASSTHROUGH_ERR; +} + +void tcmu_sense_set_key_specific_info(uint8_t *sense_buf, uint16_t info) +{ + memset(sense_buf, 0, 18); + + put_unaligned_be16(info, &sense_buf[16]); + /* Set SKSV bit */ + sense_buf[15] |= 0x80; +} + +void tcmu_sense_set_info(uint8_t *sense_buf, uint32_t info) +{ + memset(sense_buf, 0, 18); + + put_unaligned_be32(info, &sense_buf[3]); + /* Set VALID bit */ + sense_buf[0] |= 0x80; +} + +/* + * Zero iovec. + */ +void tcmu_iovec_zero(struct iovec *iovec, size_t iov_cnt) +{ + while (iov_cnt) { + bzero(iovec->iov_base, iovec->iov_len); + + iovec++; + iov_cnt--; + } +} + +static inline bool tcmu_zeroed_mem(const char *buf, size_t size) +{ + int i; + + for (i = 0; i < (int)size; i++) { + if (buf[i]) + return false; + } + + return true; +} + +bool tcmu_iovec_zeroed(struct iovec *iovec, size_t iov_cnt) +{ + int i; + + for (i = 0; i < (int)iov_cnt; i++) { + if (!tcmu_zeroed_mem((char*)iovec[i].iov_base, iovec[i].iov_len)) + return false; + } + + return true; +} + +/* + * Copy data into an iovec, and consume the space in the iovec. + * + * Will truncate instead of overrunning the iovec. + */ +size_t tcmu_memcpy_into_iovec( + struct iovec *iovec, + size_t iov_cnt, + void *src, + size_t len) +{ + size_t copied = 0; + + while (len && iov_cnt) { + size_t to_copy = std::min(iovec->iov_len, len); + + if (to_copy) { + memcpy(iovec->iov_base, src + copied, to_copy); + + len -= to_copy; + copied += to_copy; + iovec->iov_base += to_copy; + iovec->iov_len -= to_copy; + } + + iovec++; + iov_cnt--; + } + + return copied; +} + +/* + * Copy data from an iovec, and consume the space in the iovec. + */ +size_t tcmu_memcpy_from_iovec( + void *dest, + size_t len, + struct iovec *iovec, + size_t iov_cnt) +{ + size_t copied = 0; + + while (len && iov_cnt) { + size_t to_copy = std::min(iovec->iov_len, len); + + if (to_copy) { + memcpy(dest + copied, iovec->iov_base, to_copy); + + len -= to_copy; + copied += to_copy; + iovec->iov_base += to_copy; + iovec->iov_len -= to_copy; + } + + iovec++; + iov_cnt--; + } + + return copied; +} + +#define CDB_TO_BUF_SIZE(bytes) ((bytes) * 3 + 2) +#define CDB_FIX_BYTES 64 /* 64 bytes for default */ +#define CDB_FIX_SIZE CDB_TO_BUF_SIZE(CDB_FIX_BYTES) +void tcmu_cdb_print_info(struct tcmu_device *dev, + const struct tcmulib_cmd *cmd, + const char *info) +{ + int i, n, bytes, info_len = 0; + char fix[CDB_FIX_SIZE], *buf; + + buf = fix; + + bytes = tcmu_cdb_get_length(cmd->cdb); + if (bytes < 0) + return; + + if (info) + info_len = strlen(info); + + if (CDB_TO_BUF_SIZE(bytes) + info_len > CDB_FIX_SIZE) { + buf = (char*)malloc(CDB_TO_BUF_SIZE(bytes) + info_len); + if (!buf) { + LOG_ERROR("[dev `] out of memory", dev->tcm_dev_name); + return; + } + } + + for (i = 0, n = 0; i < bytes; i++) { + n += sprintf(buf + n, "%x ", cmd->cdb[i]); + } + + if (info) + n += sprintf(buf + n, "%s", info); + + sprintf(buf + n, "\n"); + + + LOG_DEBUG("dev: `, `", dev->tcm_dev_name, buf); + + if (buf != fix) + free(buf); +} + +void tcmu_thread_cancel(pthread_t thread) +{ + void *join_retval; + int ret; + + ret = pthread_cancel(thread); + if (ret) { + LOG_ERROR("pthread_cancel failed with value `", ret); + return; + } + + ret = pthread_join(thread, &join_retval); + if (ret) { + LOG_ERROR("pthread_join failed with value `", ret); + return; + } + + if (join_retval != PTHREAD_CANCELED) + LOG_ERROR("unexpected join retval: `", join_retval); +} diff --git a/src/libtcmu/be_byteshift.h b/src/libtcmu/be_byteshift.h new file mode 100644 index 00000000..616a95bb --- /dev/null +++ b/src/libtcmu/be_byteshift.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2017 Red Hat, Inc. + * + * This file is licensed to you under your choice of the GNU Lesser + * General Public License, version 2.1 or any later version (LGPLv2.1 or + * later), or the Apache License 2.0. + */ + +#pragma once + +#include +#include +#include + +static inline void __put_unaligned_be32(uint32_t val, uint8_t *p) +{ + *p++ = val >> 24; + *p++ = val >> 16; + *p++ = val >> 8; + *p++ = val; +} + +static inline void put_unaligned_be32(uint32_t val, void *p) +{ + __put_unaligned_be32(val, p); +} + +static inline void __put_unaligned_be16(uint16_t val, uint8_t *p) +{ + *p++ = val >> 8; + *p++ = val; +} + +static inline void put_unaligned_be16(uint16_t val, void *p) +{ + __put_unaligned_be16(val, (uint8_t*)p); +} + +static inline uint16_t __get_unaligned_be16(const uint8_t *p) +{ + return p[0] << 8 | p[1]; +} + +static inline uint16_t get_unaligned_be16(const void *p) +{ + return __get_unaligned_be16((uint8_t*)p); +} + +static inline uint32_t __get_unaligned_be32(const uint8_t *p) +{ + return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; +} + +static inline uint32_t get_unaligned_be32(const void *p) +{ + return __get_unaligned_be32(p); +} + +static inline uint64_t get_unaligned_be64(const void *p) +{ + uint64_t val; + memcpy(&val, p, sizeof(val)); + return be64toh(val); +} + +static inline void put_unaligned_be64(uint64_t val, void *p) +{ + val = htobe64(val); + memcpy(p, &val, sizeof(val)); +} diff --git a/src/libtcmu/configfs.cpp b/src/libtcmu/configfs.cpp new file mode 100644 index 00000000..bd9d27f9 --- /dev/null +++ b/src/libtcmu/configfs.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2017 Red Hat, Inc. + * + * This file is licensed to you under your choice of the GNU Lesser + * General Public License, version 2.1 or any later version (LGPLv2.1 or + * later), or the Apache License 2.0. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libtcmu_common.h" +#include "libtcmu_priv.h" +#include + +#define CFGFS_BUF_SIZE 4096 + +int tcmu_cfgfs_get_int(const char *path) +{ + int fd; + char buf[16]; + ssize_t ret; + unsigned long val; + + fd = open(path, O_RDONLY); + if (fd == -1) { + if (errno == ENOENT) { + LOG_ERROR("Kernel does not support configfs file `", path); + } else { + LOG_ERROR("Could not open configfs file `: `", path, strerror(errno)); + } + return -errno; + } + + ret = read(fd, buf, sizeof(buf)); + close(fd); + if (ret == -1) { + LOG_ERROR("Could not read configfs to read attribute `: `", path, strerror(errno)); + return -errno; + } + + val = strtoul(buf, NULL, 0); + if (val > INT_MAX ) { + LOG_ERROR("could not convert string ` to value", buf); + return -EINVAL; + } + + return val; +} + +int tcmu_cfgfs_dev_get_attr_int(struct tcmu_device *dev, const char *name) +{ + char path[PATH_MAX]; + + snprintf(path, sizeof(path), CFGFS_CORE"/%s/%s/attrib/%s", + dev->tcm_hba_name, dev->tcm_dev_name, name); + return tcmu_cfgfs_get_int(path); +} + +uint64_t tcmu_cfgfs_dev_get_info_u64(struct tcmu_device *dev, const char *name, + int *fn_ret) +{ + int fd; + char path[PATH_MAX]; + char buf[CFGFS_BUF_SIZE]; + ssize_t ret; + char *rover; + char *search_pattern; + uint64_t val; + + *fn_ret = 0; + snprintf(path, sizeof(path), CFGFS_CORE"/%s/%s/info", + dev->tcm_hba_name, dev->tcm_dev_name); + + fd = open(path, O_RDONLY); + if (fd == -1) { + if (errno == ENOENT) { + LOG_ERROR("Kernel does not support device info file `.", path); + } else { + LOG_ERROR("Could not open device info file `: `", path, strerror(errno)); + } + *fn_ret = -errno; + return 0; + } + + ret = read(fd, buf, sizeof(buf)); + close(fd); + if (ret == -1) { + LOG_ERROR("Could not read configfs to read dev info: `", strerror(errno)); + *fn_ret = -EINVAL; + return 0; + } else if (ret == 0) { + LOG_ERROR("Invalid device info."); + *fn_ret = -EINVAL; + return 0; + } + buf[ret-1] = '\0'; /* paranoid? Ensure null terminated */ + + if (asprintf(&search_pattern, " %s: ", name) < 0) { + LOG_ERROR("Could not create search string."); + *fn_ret = -ENOMEM; + return 0; + } + + rover = strstr(buf, search_pattern); + free(search_pattern); + if (!rover) { + LOG_ERROR("Could not find \" `: \" in `: `", name, path, strerror(errno)); + *fn_ret = -EINVAL; + return 0; + } + rover += strlen(name) + 3; /* name plus ':' and spaces before/after */ + + val = strtoull(rover, NULL, 0); + if (val == ULLONG_MAX) { + LOG_ERROR("Could not get `: `", name, strerror(errno)); + *fn_ret = -EINVAL; + return 0; + } + + return val; +} + +int tcmu_cfgfs_dev_set_ctrl_u64(struct tcmu_device *dev, const char *key, + uint64_t val) +{ + char path[PATH_MAX]; + char buf[CFGFS_BUF_SIZE]; + + snprintf(path, sizeof(path), CFGFS_CORE"/%s/%s/control", + dev->tcm_hba_name, dev->tcm_dev_name); + snprintf(buf, sizeof(buf), "%s=%" PRIu64"", key, val); + + return tcmu_cfgfs_set_str(path, buf, strlen(buf) + 1); +} + +int tcmu_cfgfs_mod_param_set_u32(const char *name, uint32_t val) +{ + char path[PATH_MAX]; + + snprintf(path, sizeof(path), CFGFS_MOD_PARAM"/%s", name); + return tcmu_cfgfs_set_u32(path, val); +} + +/* + * Return a string that contains the device's WWN, or NULL. + * + * Callers must free the result with free(). + */ +char *tcmu_cfgfs_dev_get_wwn(struct tcmu_device *dev) +{ + int fd; + char path[PATH_MAX]; + char buf[CFGFS_BUF_SIZE]; + char *ret_buf; + int ret; + + snprintf(path, sizeof(path), + CFGFS_CORE"/%s/%s/wwn/vpd_unit_serial", + dev->tcm_hba_name, dev->tcm_dev_name); + + fd = open(path, O_RDONLY); + if (fd == -1) { + if (errno == ENOENT) { + LOG_ERROR("Kernel does not support unit serial file `", path); + } else { + LOG_ERROR("Could not open unit serial file `: `", path, strerror(errno)); + } + return NULL; + } + + ret = read(fd, buf, sizeof(buf)); + close(fd); + if (ret == -1) { + LOG_ERROR("Could not read configfs to read unit serial: `", strerror(errno)); + return NULL; + } else if (ret == 0) { + LOG_ERROR("Invalid VPD serial number."); + return NULL; + } + + /* Kill the trailing '\n' */ + buf[ret-1] = '\0'; + + /* Skip to the good stuff */ + ret = asprintf(&ret_buf, "%s", &buf[28]); + if (ret == -1) { + LOG_ERROR("could not convert string to value: `", strerror(errno)); + return NULL; + } + + return ret_buf; +} + +char *tcmu_cfgfs_get_str(const char *path) +{ + int fd, n; + char buf[CFGFS_BUF_SIZE]; + ssize_t ret; + char *val; + + memset(buf, 0, sizeof(buf)); + fd = open(path, O_RDONLY); + if (fd == -1) { + if (errno == ENOENT) { + LOG_ERROR("Kernel does not support configfs file `", path); + } else { + LOG_ERROR("Could not open configfs file `: `", path, strerror(errno)); + } + return NULL; + } + + ret = read(fd, buf, sizeof(buf)); + close(fd); + if (ret == -1) { + LOG_ERROR("Could not read configfs to read attribute `: `", path, strerror(errno)); + return NULL; + } + + if (ret == 0) + return NULL; + + /* + * Some files like members will terminate each member/line with a null + * char. Except for the last one, replace it with '\n' so parsers will + * just see an empty member. + */ + if (ret != (ssize_t)strlen(buf)) { + do { + n = strlen(buf); + buf[n] = '\n'; + } while (n < ret); + } + + /* + * Some files like members ends with a null char, but other files like + * the alua ones end with a newline. + */ + if (buf[ret - 1] == '\n') + buf[ret - 1] = '\0'; + + if (buf[ret - 1] != '\0') { + if (ret >= CFGFS_BUF_SIZE) { + LOG_ERROR("Invalid cfgfs file `: not enough space for ending null char.", + path); + return NULL; + } + /* + * In case the file does "return sprintf()" with no ending + * newline add the ending null so we will not crash below. + */ + buf[ret] = '\0'; + } + + val = strdup(buf); + if (!val) { + LOG_ERROR("could not copy buffer ` : `", buf, strerror(errno)); + return NULL; + } + + return val; +} + +int tcmu_cfgfs_set_str(const char *path, const char *val, int val_len) +{ + int fd; + ssize_t ret; + + fd = open(path, O_WRONLY); + if (fd == -1) { + if (errno == ENOENT) { + LOG_ERROR("Kernel does not support configfs file `", path); + } else { + LOG_ERROR("Could not open configfs file `: `", path, strerror(errno)); + } + return -errno; + } + + ret = write(fd, val, val_len); + close(fd); + if (ret == -1) { + LOG_ERROR("Could not write configfs to write attribute `: `", + path, strerror(errno)); + return -errno; + } + + return 0; +} + +int tcmu_cfgfs_set_u32(const char *path, uint32_t val) +{ + char buf[20]; + + sprintf(buf, "%" PRIu32"", val); + return tcmu_cfgfs_set_str(path, buf, strlen(buf) + 1); +} + +int tcmu_cfgfs_dev_exec_action(struct tcmu_device *dev, const char *name, + uint32_t val) +{ + char path[PATH_MAX]; + int ret; + + snprintf(path, sizeof(path), CFGFS_CORE"/%s/%s/action/%s", + dev->tcm_hba_name, dev->tcm_dev_name, name); + LOG_DEBUG("dev: `, executing action `", dev->tcm_dev_name, name); + ret = tcmu_cfgfs_set_u32(path, val); + LOG_DEBUG("dev: `, action ` done", dev->tcm_dev_name, name); + return ret; +} diff --git a/src/libtcmu/docs/adr/0001-silent-ignore-unmatched-subtype.md b/src/libtcmu/docs/adr/0001-silent-ignore-unmatched-subtype.md new file mode 100644 index 00000000..9b1d913c --- /dev/null +++ b/src/libtcmu/docs/adr/0001-silent-ignore-unmatched-subtype.md @@ -0,0 +1,36 @@ +# ADR 0001: Silent Ignore for Unmatched Subtype TCMU Device Requests + +## Status + +Accepted + +## Context + +In a multi-instance TCMU deployment, multiple user-space processes each register handlers for different subtypes. The kernel broadcasts `TCMU_CMD_ADDED_DEVICE`, `TCMU_CMD_REMOVED_DEVICE`, and `TCMU_CMD_RECONFIG_DEVICE` via Netlink multicast to all listeners in the "config" group. + +Previously, when a process received a device event for a subtype it did not handle, it would: + +1. Attempt `device_add()`, fail at `find_handler()`, and return `-ENOENT` +2. Send a Netlink reply (`TCMU_CMD_ADDED_DEVICE_DONE`) with the error code back to the kernel +3. Log an `ERROR`-level message + +This caused the kernel to interpret the response as a device creation failure, potentially preventing the correct handler process from claiming the device. + +## Decision + +When receiving a TCMU device event for a subtype not registered by this process, **silently ignore the message without sending a Netlink reply**. The specific rules are: + +1. **ADDED_DEVICE**: `device_add()` returns `-ENODATA` (not `-ENOENT`) when `find_handler()` yields no match. `handle_netlink()` skips `send_netlink_reply()` for this return value. +2. **REMOVED_DEVICE / RECONFIG_DEVICE**: If the device is not in `ctx->devices` (i.e., never claimed by this process), skip the Netlink reply. +3. **Logging**: Subtype mismatch is logged at `DEBUG` level, not `ERROR`. This is expected behavior in multi-instance deployments, not a failure condition. + +The return value convention: +- `-ENODATA`: "No handler registered for this subtype" — caller must not reply +- `-ENOENT` / other negatives: "Handler was found but processing failed" — caller should reply with the error + +## Consequences + +- **Enables multi-instance deployment**: Each process only responds to its own subtypes; the kernel receives exactly one reply from the correct handler. +- **Kernel timeout risk**: If no process handles a given subtype, the kernel will receive no reply and may timeout. This is acceptable — it correctly reflects that no user-space handler is available. +- **Reduced log noise**: Production logs no longer contain spurious ERROR entries for normal cross-subtype traffic. +- **Backward compatible**: Netlink v1 never sent replies anyway; this change only affects v2+ reply behavior for unmatched subtypes. diff --git a/src/libtcmu/libtcmu.cpp b/src/libtcmu/libtcmu.cpp new file mode 100644 index 00000000..8b7982a3 --- /dev/null +++ b/src/libtcmu/libtcmu.cpp @@ -0,0 +1,1394 @@ +/* + * This file is licensed to you under your choice of the GNU Lesser + * General Public License, version 2.1 or any later version (LGPLv2.1 or + * later), or the Apache License 2.0. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "target_core_user_local.h" +#include "libtcmu.h" +#include "libtcmu_priv.h" +#include "scsi_defs.h" + +#include +#include + +#define TCMU_NL_VERSION 2 + +static struct nla_policy tcmu_attr_policy[TCMU_ATTR_MAX+1] = { + {}, + { .type = NLA_STRING }, + { .type = NLA_U32 }, + {}, + { .type = NLA_STRING }, + { .type = NLA_U64 }, + { .type = NLA_U8 }, + { .type = NLA_S32 }, + { .type = NLA_U32 }, + { .type = NLA_U8 } +}; + +static int device_add(struct tcmulib_context *ctx, char *dev_name, + char *cfgstring, bool reopen); +static int device_remove(struct tcmulib_context *ctx, char *dev_name, + bool should_block); +static int handle_netlink(struct nl_cache_ops *unused, struct genl_cmd *cmd, + struct genl_info *info, void *arg); + +static struct genl_cmd tcmu_cmds[] = { + { + .c_id = TCMU_CMD_ADDED_DEVICE, + .c_name = "ADDED DEVICE", + .c_maxattr = TCMU_ATTR_MAX, + .c_msg_parser = handle_netlink, + .c_attr_policy = tcmu_attr_policy, + }, + { + .c_id = TCMU_CMD_REMOVED_DEVICE, + .c_name = "REMOVED DEVICE", + .c_maxattr = TCMU_ATTR_MAX, + .c_msg_parser = handle_netlink, + .c_attr_policy = tcmu_attr_policy, + }, + { + .c_id = TCMU_CMD_RECONFIG_DEVICE, + .c_name = "RECONFIG DEVICE", + .c_maxattr = TCMU_ATTR_MAX, + .c_msg_parser = handle_netlink, + .c_attr_policy = tcmu_attr_policy, + }, +}; + +static struct genl_ops tcmu_ops = { + .o_hdrsize = 0, + .o_id = 0, + .o_name = "TCM-USER", + .o_cache_ops = nullptr, + .o_cmds = tcmu_cmds, + .o_ncmds = ARRAY_SIZE(tcmu_cmds), +}; + +static int send_netlink_reply(struct tcmulib_context *ctx, int reply_cmd, + uint32_t dev_id, int status) +{ + struct nl_sock *sock = ctx->nl_sock; + struct nl_msg *msg; + void *hdr; + int ret = -ENOMEM; + + msg = nlmsg_alloc(); + if (!msg) + return ret; + + hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, tcmu_ops.o_id, + 0, 0, reply_cmd, TCMU_NL_VERSION); + if (!hdr) + goto free_msg; + + ret = nla_put_s32(msg, TCMU_ATTR_CMD_STATUS, status); + if (ret < 0) + goto free_msg; + + ret = nla_put_u32(msg, TCMU_ATTR_DEVICE_ID, dev_id); + if (ret < 0) + goto free_msg; + + /* Ignore ack. There is nothing we can do. */ + ret = nl_send_auto(sock, msg); +free_msg: + nlmsg_free(msg); + + if (ret < 0) + LOG_ERROR("Could not send netlink cmd `", reply_cmd); + return ret; +} + +static struct tcmu_device * +lookup_dev_by_name(struct tcmulib_context *ctx, char *dev_name) +{ + auto it = ctx->devices.find(dev_name); + if (it == ctx->devices.end()) { + return NULL; + } + return it->second; +} + +static const char *const tcmulib_cfg_type_lookup[] = { + [TCMULIB_CFG_DEV_CFGSTR] = "TCMULIB_CFG_DEV_CFGSTR", + [TCMULIB_CFG_DEV_SIZE] = "TCMULIB_CFG_DEV_SIZE", + [TCMULIB_CFG_WRITE_CACHE] = "TCMULIB_CFG_WRITE_CACHE", +}; + +static int reconfig_device(struct tcmulib_context *ctx, char *dev_name, + struct genl_info *info) +{ + struct tcmu_device *dev; + struct tcmulib_cfg_info cfg; + int ret; + + memset(&cfg, 0, sizeof(cfg)); + + dev = lookup_dev_by_name(ctx, dev_name); + if (!dev) { + LOG_DEBUG("skipping reconfig for device `: not managed by us", + dev_name); + return -ENODATA; + } + + if (info->attrs[TCMU_ATTR_DEV_CFG]) { + cfg.type = TCMULIB_CFG_DEV_CFGSTR; + cfg.data.dev_cfgstring = + nla_get_string(info->attrs[TCMU_ATTR_DEV_CFG]); + } else if (info->attrs[TCMU_ATTR_DEV_SIZE]) { + cfg.type = TCMULIB_CFG_DEV_SIZE; + cfg.data.dev_size = nla_get_u64(info->attrs[TCMU_ATTR_DEV_SIZE]); + } else if (info->attrs[TCMU_ATTR_WRITECACHE]) { + cfg.type = TCMULIB_CFG_WRITE_CACHE; + cfg.data.write_cache = + nla_get_u8(info->attrs[TCMU_ATTR_WRITECACHE]); + } else { + LOG_ERROR("[dev `] Unknown reconfig attr. Try updating libtcmu.", dev->tcm_dev_name); + return -EOPNOTSUPP; + } + + if (!dev->handler->reconfig) { + LOG_DEBUG("[dev `] Reconfiguration is not supported with this device. Request for `", + dev->tcm_dev_name, tcmulib_cfg_type_lookup[cfg.type]); + return -EOPNOTSUPP; + } + + ret = dev->handler->reconfig(dev, &cfg); + if (ret < 0) { + LOG_DEBUG("[dev `] Handler reconfig for ` failed with error `", + dev->tcm_dev_name, tcmulib_cfg_type_lookup[cfg.type], strerror(-ret)); + return ret; + } + + return 0; +} + +static int handle_netlink(struct nl_cache_ops *unused, struct genl_cmd *cmd, + struct genl_info *info, void *arg) +{ + struct tcmulib_context *ctx = arg; + int ret, reply_cmd, version = info->genlhdr->version; + char buf[32]; + + + LOG_DEBUG("cmd `. Got header version `. Supported `", + cmd->c_id, info->genlhdr->version, TCMU_NL_VERSION); + + if (!info->attrs[TCMU_ATTR_MINOR] || !info->attrs[TCMU_ATTR_DEVICE]) { + LOG_ERROR("TCMU_ATTR_MINOR or TCMU_ATTR_DEVICE not set, dropping netlink command."); + return 0; + } + + if (version > 1 && !info->attrs[TCMU_ATTR_DEVICE_ID]) { + LOG_ERROR("TCMU_ATTR_DEVICE_ID not set in v` cmd `, dropping netink command.", version, cmd->c_id); + return 0; + } + + snprintf(buf, sizeof(buf), "uio%d", nla_get_u32(info->attrs[TCMU_ATTR_MINOR])); + + switch (cmd->c_id) { + case TCMU_CMD_ADDED_DEVICE: + reply_cmd = TCMU_CMD_ADDED_DEVICE_DONE; + ret = device_add(ctx, buf, + nla_get_string(info->attrs[TCMU_ATTR_DEVICE]), + false); + break; + case TCMU_CMD_REMOVED_DEVICE: + reply_cmd = TCMU_CMD_REMOVED_DEVICE_DONE; + ret = device_remove(ctx, buf, false); + break; + case TCMU_CMD_RECONFIG_DEVICE: + reply_cmd = TCMU_CMD_RECONFIG_DEVICE_DONE; + ret = reconfig_device(ctx, buf, info); + break; + default: + LOG_ERROR("Unknown netlink command `. Netlink header received version `. libtcmu supports `", + cmd->c_id, version, TCMU_NL_VERSION); + return -EOPNOTSUPP; + } + + if (version > 1 && ret != -ENODATA) + ret = send_netlink_reply(ctx, reply_cmd, + nla_get_u32(info->attrs[TCMU_ATTR_DEVICE_ID]), + ret); + + return ret; +} + +static int set_genl_features(struct nl_sock *sock) +{ + struct nl_msg *msg; + void *hdr; + int ret = -NLE_NOMEM; + + msg = nlmsg_alloc(); + if (!msg) { + LOG_ERROR("Could not allocate a new message."); + return ret; + } + + hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, tcmu_ops.o_id, + 0, NLM_F_ACK, TCMU_CMD_SET_FEATURES, TCMU_NL_VERSION); + if (!hdr) + goto free_msg; + + ret = nla_put_u8(msg, TCMU_ATTR_SUPP_KERN_CMD_REPLY, 1); + if (ret < 0) + goto free_msg; + + /* + * Could be a older kernel. Ignore failure and just work in degraded + * mode. + */ + ret = nl_send_sync(sock, msg); + if (ret == -NLE_OPNOTSUPP) { + LOG_WARN("Kernel does not support the operation."); + ret = 0; + } + goto done; + +free_msg: + nlmsg_free(msg); + +done: + if (ret < 0) + LOG_ERROR("Could not set features. Error `", ret); + + return ret; +} + +static struct nl_sock *setup_netlink(struct tcmulib_context *ctx) +{ + struct nl_sock *sock; + int ret; + + sock = nl_socket_alloc(); + if (!sock) { + LOG_ERROR("couldn't alloc socket"); + return NULL; + } + + nl_socket_disable_seq_check(sock); + + nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, genl_handle_msg, ctx); + + ret = genl_connect(sock); + if (ret < 0) { + LOG_ERROR("couldn't connect"); + goto err_free; + } + + ret = genl_register_family(&tcmu_ops); + if (ret < 0) { + LOG_ERROR("couldn't register family"); + goto err_close; + } + + ret = genl_ops_resolve(sock, &tcmu_ops); + if (ret < 0) { + LOG_ERROR("couldn't resolve ops, is target_core_user.ko loaded?"); + goto err_unregister; + } + + ret = genl_ctrl_resolve_grp(sock, "TCM-USER", "config"); + if (ret < 0) { + LOG_ERROR("couldn't resolve netlink family group, is target_core_user.ko loaded?"); + goto err_unregister; + } + + ret = nl_socket_add_membership(sock, ret); + if (ret < 0) { + LOG_ERROR("couldn't add membership"); + goto err_unregister; + } + + ret = set_genl_features(sock); + if (ret < 0) + goto err_unregister; + + return sock; + +err_unregister: + genl_unregister_family(&tcmu_ops); +err_close: + nl_close(sock); +err_free: + nl_socket_free(sock); + + return NULL; +} + +static void teardown_netlink(struct nl_sock *sock) +{ + int ret; + + ret = genl_unregister_family(&tcmu_ops); + if (ret != 0) { + LOG_ERROR("genl_unregister_family failed, `", ret); + } + + nl_close(sock); + nl_socket_free(sock); +} + +static struct tcmulib_handler *find_handler(struct tcmulib_context *ctx, + char *cfgstring) +{ + size_t len; + char *found_at; + + found_at = strchrnul(cfgstring, '/'); + len = found_at - cfgstring; + + for (size_t i = 0; i < ctx->handlers.size(); i++) { + if (strlen(ctx->handlers[i].subtype) == len && + !strncmp(cfgstring, ctx->handlers[i].subtype, len)) { + LOG_INFO("find handler for subtype: `", cfgstring); + return &ctx->handlers[i]; + } + } + return NULL; +} + +void tcmu_dev_flush_ring(struct tcmu_device *dev) +{ + struct tcmu_mailbox *mb = dev->map; + + LOG_DEBUG("[dev `] waiting for ring to clear", dev->tcm_dev_name); + while (mb->cmd_head != mb->cmd_tail) + photon::thread_usleep(50000); + LOG_DEBUG("[dev `] ring clear", dev->tcm_dev_name); +} + +bool tcmu_dev_oooc_supported(struct tcmu_device* dev) +{ + return dev->map->flags & TCMU_MAILBOX_FLAG_CAP_OOOC; +} + +/* Read a size_t from a file. Returns -1 on error. */ +static ssize_t read_size(const char *filename) +{ + int fd; + int len, rc; + char buf[256], *endbuf; + ssize_t ret; + + fd = open(filename, O_RDONLY); + if (fd == -1) + goto err; + + len = read(fd, buf, sizeof(buf)-1); + rc = close(fd); + if (len <= 0 || rc == -1) + goto err; + + buf[len] = '\0'; /* null-terminate */ + ret = strtoull(buf, &endbuf, 0); + if (buf == endbuf || (size_t)ret == ULLONG_MAX) + goto err; + + return ret; + +err: + LOG_WARN("cannot read size from `", filename); + return -1; +} + +/* Extract configuration parameters into dev. */ +static bool device_parse_cfg(struct tcmu_device *dev, + const char *dev_name, const char *cfgstring) +{ + int len; + const char *ptr, *oldptr; + + len = snprintf(dev->dev_name, sizeof(dev->dev_name), "%s", dev_name); + if (len >= (int)sizeof(dev->dev_name)) { + LOG_ERROR("device name too long for tcmu_device"); + goto err_recompile; + } + + /* Check valid cfgstring */ + oldptr = cfgstring; + ptr = strchr(oldptr, '/'); + if (!ptr) + goto err_badcfg; + if (strncmp(cfgstring, "tcm-user", ptr-oldptr)) + goto err_badcfg; + + /* Get HBA name */ + oldptr = ptr+1; + ptr = strchr(oldptr, '/'); + if (!ptr) + goto err_badcfg; + len = ptr-oldptr; + len = snprintf(dev->tcm_hba_name, sizeof(dev->tcm_hba_name), "user_%.*s", len, oldptr); + if (len >= (int)sizeof(dev->tcm_hba_name)) { + LOG_ERROR("hba name too long for tcmu_device"); + goto err_recompile; + } + + /* Get device name */ + oldptr = ptr+1; + ptr = strchr(oldptr, '/'); + if (!ptr) + goto err_badcfg; + len = ptr-oldptr; + len = snprintf(dev->tcm_dev_name, sizeof(dev->tcm_dev_name), "%.*s", len, oldptr); + if (len >= (int)sizeof(dev->tcm_dev_name)) { + LOG_ERROR("tcm device name too long for tcmu_device"); + goto err_recompile; + } + + /* The rest is the handler-specific cfgstring */ + oldptr = ptr+1; + len = snprintf(dev->cfgstring, sizeof(dev->cfgstring), "%s", oldptr); + if (len >= (int)sizeof(dev->cfgstring)) { + LOG_WARN("additional handler cfgstring was truncated"); + /* not a terminal error. snprintf() will null-terminate */ + } + return true; + +err_badcfg: + LOG_ERROR("invalid cfgstring: expecting \"tcm-user///\""); +err_recompile: /* consider expanding string lengths in dev */ + return false; +} + +static void device_close_shm(struct tcmu_device *dev) +{ + int ret; + + ret = close(dev->fd); + if (ret != 0) { + LOG_ERROR("could not close device fd for `: `", dev->dev_name, errno); + } + ret = munmap(dev->map, dev->map_len); + if (ret != 0) { + LOG_ERROR("could not unmap device `: `", dev->dev_name, errno); + } +} + +static bool device_open_shm(struct tcmu_device *dev) +{ + size_t mmap_size; + char *mmap_name; + off_t mmap_offset; + + /* get filename, size and offset */ + mmap_name = tcmu_dev_get_memory_info(dev, NULL, &mmap_size, &mmap_offset); + if (!mmap_name) + goto err_fail; + + /* cache the map size */ + dev->map_len = mmap_size; + + /* open the map */ + dev->fd = open(mmap_name, O_RDWR | O_NONBLOCK | O_CLOEXEC); + if (dev->fd == -1) { + LOG_ERROR("could not open `, errno: `", mmap_name, errno); + goto err_mmap_name; + } + + /* bring the map into memory */ + dev->map = mmap(NULL, dev->map_len, PROT_READ|PROT_WRITE, MAP_SHARED, dev->fd, mmap_offset); + if (dev->map == MAP_FAILED) { + LOG_ERROR("could not mmap `, mmap len: `, mmap off: `, fd: `, errno: `", + mmap_name, dev->map_len, mmap_offset, dev->fd, errno); + goto err_fd_close; + } + + if (dev->map->version != KERN_IFACE_VER) { + LOG_ERROR("Kernel interface version mismatch: wanted ` got `", + KERN_IFACE_VER, (int)dev->map->version); + goto err_munmap; + } + + free(mmap_name); + return true; + +err_munmap: + munmap(dev->map, dev->map_len); +err_fd_close: + close(dev->fd); +err_mmap_name: + free(mmap_name); +err_fail: + return false; +} + +static void device_free(struct tcmu_device *dev) +{ + delete dev; +} + +static int device_add(struct tcmulib_context *ctx, char *dev_name, + char *cfgstring, bool reopen) +{ + struct tcmu_device *dev; + char *reason = NULL; + int rc; + bool reset_supp = true; + + dev = new tcmu_device(); + if (!dev) { + LOG_ERROR("calloc failed for device_add()"); + return -ENOMEM; + } + if (!device_parse_cfg(dev, dev_name, cfgstring)) + goto err_free; + + dev->handler = find_handler(ctx, dev->cfgstring); + if (!dev->handler) { + LOG_WARN("skipping device `: no matching handler for subtype", + dev->dev_name); + device_free(dev); + return -ENODATA; + } + + if (dev->handler->check_config && + !dev->handler->check_config(dev->cfgstring, &reason)) { + /* It may be handled by other handlers */ + LOG_ERROR("check_config failed for ` because of `", dev->dev_name, reason); + free(reason); + goto err_free; + } + + if (reopen) { + /* + * We might not have cleanly shutdown and IO might be + * running in the kernel or have timed out. Block the device + * so new IO is stopped, and reset the ring so we can start + * from a fresh slate. We will unblock below when we are + * completely setup. + */ + rc = tcmu_cfgfs_dev_exec_action(dev, "block_dev", 1); + /* + * As long as the block_dev file existed, try to reset + * just in case the kernel was in a invald state. + */ + if (rc == -ENOENT) { + reset_supp = false; + } else { + /* + * Force a retry of the outstanding commands. + */ + rc = tcmu_cfgfs_dev_exec_action(dev, "reset_ring", 1); + if (rc) + LOG_ERROR("[dev `] Could not reset ring `", dev->tcm_dev_name, rc); + } + } + + if (!device_open_shm(dev)) + goto err_unblock; + + dev->cmd_tail = dev->map->cmd_tail; + dev->ctx = ctx; + + rc = dev->handler->added(dev); + if (rc != 0) { + LOG_ERROR("handler open failed for `", dev->dev_name); + goto err_closeshm; + } + ctx->devices[dev->dev_name] = dev; + + if (reopen && reset_supp) + tcmu_cfgfs_dev_exec_action(dev, "block_dev", 0); + + return 0; + +err_closeshm: + device_close_shm(dev); +err_unblock: + if (reopen && reset_supp) + tcmu_cfgfs_dev_exec_action(dev, "block_dev", 0); +err_free: + device_free(dev); + return -ENOENT; +} + +static void close_devices(struct tcmulib_context *ctx) +{ + auto devices = ctx->devices; + for (auto dev : devices) { + device_remove(ctx, dev.second->dev_name, true); + } +} + +static int device_remove(struct tcmulib_context *ctx, char *dev_name, + bool should_block) +{ + struct tcmu_device *dev; + + dev = lookup_dev_by_name(ctx, dev_name); + if (!dev) { + LOG_WARN("skipping remove for device `: not managed by us", + dev_name); + return -ENODATA; + } + + /* + * If called through nl, IO will be stopped. If called by a + * app/daemon, IO might be runnning. Try to do a ordered + * shutdown and allow IO to complete normally. + */ + if (should_block) { + tcmu_cfgfs_dev_exec_action(dev, "block_dev", 1); + tcmu_dev_flush_ring(dev); + } + + ctx->devices.erase(dev_name); + + dev->handler->removed(dev); + + device_close_shm(dev); + + if (should_block) + tcmu_cfgfs_dev_exec_action(dev, "block_dev", 0); + + LOG_DEBUG("[dev `] removed from tcmulib", dev->tcm_dev_name); + device_free(dev); + return 0; +} + +static int read_uio_name(const char *uio_dev, char **dev_name) +{ + int fd; + char *tmp_path; + int ret = -1; + char buf[PATH_MAX] = {'\0'}; + + if (asprintf(&tmp_path, "/sys/class/uio/%s/name", uio_dev) == -1) + return -1; + + fd = open(tmp_path, O_RDONLY); + if (fd == -1) { + LOG_ERROR("could not open `", tmp_path); + goto free_path; + } + + ret = read(fd, buf, sizeof(buf)); + if (ret <= 0 || ret >= (int)sizeof(buf)) { + LOG_ERROR("read of ` had issues", tmp_path); + goto close; + } + + buf[ret-1] = '\0'; /* null-terminate and chop off the \n */ + + *dev_name = strdup(buf); + + ret = 0; + +close: + close(fd); +free_path: + free(tmp_path); + return ret; +} + +static int is_uio(const struct dirent *dirent) +{ + char *dev_name = NULL; + ssize_t ret = 0; + + if (strncmp(dirent->d_name, "uio", 3)) + return 0; + + if (read_uio_name(dirent->d_name, &dev_name)) + goto out; + + /* we only want uio devices whose name is a format we expect */ + if (strncmp(dev_name, "tcm-user", 8)) + goto out; + + ret = 1; + +out: + if (dev_name) + free(dev_name); + return ret; +} + +static int open_devices(struct tcmulib_context *ctx) +{ + struct dirent **dirent_list; + int num_devs; + int num_good_devs = 0; + int i; + + num_devs = scandir("/dev", &dirent_list, is_uio, alphasort); + if (num_devs == -1) + return -1; + + for (i = 0; i < num_devs; i++) { + char *dev_name = NULL; + + if (read_uio_name(dirent_list[i]->d_name, &dev_name)) + continue; + + if (device_add(ctx, dirent_list[i]->d_name, dev_name, true) < 0) { + free(dev_name); + continue; + } + free(dev_name); + + num_good_devs++; + } + + for (i = 0; i < num_devs; i++) + free(dirent_list[i]); + free(dirent_list); + + return num_good_devs; +} + +static void release_resources(struct tcmulib_context *ctx) +{ + teardown_netlink(ctx->nl_sock); + delete ctx; +} + +struct tcmulib_context *tcmulib_initialize(std::vector &handlers) +{ + struct tcmulib_context *ctx = new(struct tcmulib_context); + if (!ctx) + return NULL; + + ctx->nl_sock = setup_netlink(ctx); + if (!ctx->nl_sock) { + delete ctx; + return NULL; + } + ctx->handlers = std::move(handlers); + + int ret = open_devices(ctx); + if (ret < 0) { + release_resources(ctx); + return NULL; + } + + return ctx; +} + +void tcmulib_close(struct tcmulib_context *ctx) +{ + close_devices(ctx); + release_resources(ctx); +} + +int tcmulib_get_master_fd(struct tcmulib_context *ctx) +{ + return nl_socket_get_fd(ctx->nl_sock); +} + +int tcmulib_master_fd_ready(struct tcmulib_context *ctx) +{ + return nl_recvmsgs_default(ctx->nl_sock); +} + +void *tcmu_dev_get_private(struct tcmu_device *dev) +{ + return dev->hm_private; +} + +void tcmu_dev_set_private(struct tcmu_device *dev, void *pri) +{ + dev->hm_private = pri; +} + +const char *tcmu_dev_get_uio_name(struct tcmu_device *dev) +{ + return dev->dev_name; +} + +void tcmu_set_thread_name(const char *prefix, struct tcmu_device *dev) +{ + const char *uio = dev ? tcmu_dev_get_uio_name(dev) : NULL; + char *pname; + + /* + * If we are trying to set the pthread name in the + * event work thread, we must ignore it. + */ + if (__tcmu_is_ework_thread) { + LOG_WARN("[dev `] Do not set name for event work thread in the callback fn", dev->tcm_dev_name); + return; + } + + if (!prefix) { + LOG_ERROR("[dev `] Failed to set name for thread `", dev->tcm_dev_name, + (long unsigned int)pthread_self()); + return; + } + + if (asprintf(&pname, "%s%s%s", prefix, uio ? "-" : "", uio ? uio : "") == -1) { + LOG_ERROR("[dev `] Could not allocate thread name", dev->tcm_dev_name); + return; + } + + if (strlen(pname) >= TCMU_THREAD_NAME_LEN) { + LOG_WARN("[dev `] Cannot set thread name to `. Name must be less than ` chars.", + dev->tcm_dev_name, pname, TCMU_THREAD_NAME_LEN); + pname[TCMU_THREAD_NAME_LEN - 1] = '\0'; + LOG_WARN("[dev `] Truncating to `", dev->tcm_dev_name, pname); + } + + if (pthread_setname_np(pthread_self(), pname)) + LOG_ERROR("[dev `] Could not set thread name to `", dev->tcm_dev_name, pname); + free(pname); +} + +void tcmu_dev_set_num_lbas(struct tcmu_device *dev, uint64_t num_lbas) +{ + dev->num_lbas = num_lbas; +} + +uint64_t tcmu_dev_get_num_lbas(struct tcmu_device *dev) +{ + return dev->num_lbas; +} + +uint64_t tcmu_lba_to_byte(struct tcmu_device *dev, uint64_t lba) +{ + return lba << dev->block_size_shift; +} + +uint64_t tcmu_byte_to_lba(struct tcmu_device *dev, uint64_t byte) +{ + return byte >> dev->block_size_shift; +} + +uint64_t tcmu_cdb_to_byte(struct tcmu_device *dev, uint8_t *cdb) +{ + return tcmu_lba_to_byte(dev, tcmu_cdb_get_lba(cdb)); +} + +void tcmu_dev_set_block_size(struct tcmu_device *dev, uint32_t block_size) +{ + dev->block_size = block_size; + dev->block_size_shift = ffs(block_size) - 1; +} + +uint32_t tcmu_dev_get_block_size(struct tcmu_device *dev) +{ + return dev->block_size; +} + +/** + * tcmu_dev_set_max_xfer_len - set device's max command size + * @dev: tcmu device + * @len: max transfer length in block_size sectors + */ +void tcmu_dev_set_max_xfer_len(struct tcmu_device *dev, uint32_t len) +{ + dev->max_xfer_len = len; +} + +uint32_t tcmu_dev_get_max_xfer_len(struct tcmu_device *dev) +{ + return dev->max_xfer_len; +} + +/** + * tcmu_dev_set_opt_xcopy_rw_len - set device's emulated xcopy chunk len + * @dev: tcmu device + * @len: optimal RW len, in block_size sectors, for emulate xcopy operations + */ +void tcmu_dev_set_opt_xcopy_rw_len(struct tcmu_device *dev, uint32_t len) +{ + dev->opt_xcopy_rw_len = len; +} + +uint32_t tcmu_dev_get_opt_xcopy_rw_len(struct tcmu_device *dev) +{ + return dev->opt_xcopy_rw_len; +} + +/** + * tcmu_dev_set/get_opt_unmap_gran - set/get device's optimal unmap granularity + * @dev: tcmu device + * @len: optimal unmap granularity length in block_size sectors + * @split: true if handler needs unmaps larger then len to be split for it. + */ +void tcmu_dev_set_opt_unmap_gran(struct tcmu_device *dev, uint32_t len, + bool split) +{ + dev->split_unmaps = split; + dev->opt_unmap_gran = len; +} + +uint32_t tcmu_dev_get_opt_unmap_gran(struct tcmu_device *dev) +{ + return dev->opt_unmap_gran; +} + +/** + * tcmu_dev_set/get_max_unmap_len - set/get device's man unmap len + * @dev: tcmu device + * @len: max unmap len in block_size sectors + */ +void tcmu_dev_set_max_unmap_len(struct tcmu_device *dev, uint32_t len) +{ + dev->max_unmap_len = len; +} + +uint32_t tcmu_dev_get_max_unmap_len(struct tcmu_device *dev) +{ + return dev->max_unmap_len; +} + +/** + * tcmu_dev_set/get_unmap_gran_align - set/get device's unmap granularity alignment + * @dev: tcmu device + * @len: unmap granularity alignment length in block_size sectors + */ +void tcmu_dev_set_unmap_gran_align(struct tcmu_device *dev, uint32_t len) +{ + dev->unmap_gran_align = len; +} + +uint32_t tcmu_dev_get_unmap_gran_align(struct tcmu_device *dev) +{ + return dev->unmap_gran_align; +} + +void tcmu_dev_set_write_cache_enabled(struct tcmu_device *dev, bool enabled) +{ + dev->write_cache_enabled = enabled; +} + +bool tcmu_dev_get_write_cache_enabled(struct tcmu_device *dev) +{ + return dev->write_cache_enabled; +} + +void tcmu_dev_set_solid_state_media(struct tcmu_device *dev, bool solid_state) +{ + dev->solid_state_media = solid_state; +} + +bool tcmu_dev_get_solid_state_media(struct tcmu_device *dev) +{ + return dev->solid_state_media; +} + +void tcmu_dev_set_unmap_enabled(struct tcmu_device *dev, bool enabled) +{ + dev->unmap_enabled = enabled; +} + +bool tcmu_dev_get_unmap_enabled(struct tcmu_device *dev) +{ + return dev->unmap_enabled; +} + +void tcmu_dev_set_write_protect_enabled(struct tcmu_device *dev, bool enabled) +{ + dev->write_protect_enabled = enabled; +} + +bool tcmu_dev_get_write_protect_enabled(struct tcmu_device *dev) +{ + return dev->write_protect_enabled; +} + +int tcmu_dev_get_fd(struct tcmu_device *dev) +{ + return dev->fd; +} + +/** + * tcmu_dev_get_memory_info - retrieve information about tcmu's shared + * memory block. If the memory is mapped to another address (e.g., in + * another process) then the information in struct iovec can be converted + * using these values. Returns the filename of the mmap() that fd uses, or + * NULL if information is not available. The caller must free() the + * returned filename. + * @dev: tcmu device + * @base: receives the base address of the shared memory region + * @len: receives the size of the shared memory region + * @offset: receives the offset within fd for mmap() (conventionally 0). + */ +char * +tcmu_dev_get_memory_info(struct tcmu_device *dev, void **base, + size_t *len, off_t *offset) +{ + char *mmap_name; + const char *namefmt = "/dev/%s"; + const char *sizefmt = "/sys/class/uio/%s/maps/map0/size"; + + if (asprintf(&mmap_name, namefmt, dev->dev_name) == -1) { + LOG_ERROR("cannot construct device map filename"); + goto err_fail; + } + if (base) + *base = dev->map; + if (len) { + if (dev->map_len != 0) { + /* cached */ + *len = dev->map_len; + } else { + /* get length of map from file */ + ssize_t size; + char *size_name; + + if (asprintf(&size_name, sizefmt, dev->dev_name) == -1) { + LOG_ERROR("cannot construct device map size filename"); + goto err_free; + } + size = read_size(size_name); + free(size_name); + if (size == -1) { + LOG_ERROR("unable to read device map0 size"); + goto err_free; + } + *len = size; + } + } + if (offset) + *offset = 0; + return mmap_name; + +err_free: + free(mmap_name); +err_fail: + return NULL; +} + +char *tcmu_dev_get_cfgstring(struct tcmu_device *dev) +{ + return dev->cfgstring; +} + +struct tcmulib_handler *tcmu_dev_get_handler(struct tcmu_device *dev) +{ + return dev->handler; +} + +static inline struct tcmu_cmd_entry * +device_cmd_head(struct tcmu_device *dev) +{ + struct tcmu_mailbox *mb = dev->map; + + /* + * We must load the mb_head index using an atomic load or we'll crash + * on aarch64. See https://github.com/open-iscsi/tcmu-runner/issues/688 + */ + uint32_t mb_head = __atomic_load_n(&mb->cmd_head, __ATOMIC_ACQUIRE); + struct tcmu_cmd_entry* e = (struct tcmu_cmd_entry *) ((char *) mb + mb->cmdr_off + mb_head); + return e; +} + +static inline struct tcmu_cmd_entry * +device_cmd_tail(struct tcmu_device *dev) +{ + struct tcmu_mailbox *mb = dev->map; + + return (struct tcmu_cmd_entry *) ((char *) mb + mb->cmdr_off + dev->cmd_tail); +} + +/* update the tcmu_device's tail */ +#define TCMU_UPDATE_DEV_TAIL(dev, mb, ent) \ +do { \ + dev->cmd_tail = (dev->cmd_tail + tcmu_hdr_get_len((ent)->hdr.len_op)) % mb->cmdr_size; \ +} while (0) + +struct tcmulib_cmd *tcmulib_get_next_command(struct tcmu_device *dev, + int hm_cmd_size) +{ + struct tcmu_mailbox *mb = dev->map; + struct tcmu_cmd_entry *ent; + + while ((ent = device_cmd_tail(dev)) != device_cmd_head(dev)) { + + switch (tcmu_hdr_get_op(ent->hdr.len_op)) { + case TCMU_OP_PAD: + /* do nothing */ + break; + case TCMU_OP_CMD: { + int i; + struct tcmulib_cmd *cmd; + uint8_t *cdb = (uint8_t *) mb + ent->req.cdb_off; + int cdb_len = tcmu_cdb_get_length(cdb); + + if (cdb_len < 0) { + /* + * This should never happen so just drop cmd + * for now instead of adding a lock in the + * main IO path. + */ + break; + } + + /* Alloc memory for cmd itself, iovec and cdb */ + cmd = malloc(sizeof(*cmd) + hm_cmd_size + cdb_len + + sizeof(*cmd->iovec) * ent->req.iov_cnt); + if (!cmd) + return NULL; + cmd->cmd_id = ent->hdr.cmd_id; + cmd->completion_next = nullptr; + + /* Convert iovec addrs in-place to not be offsets */ + cmd->iov_cnt = ent->req.iov_cnt; + cmd->iovec = (struct iovec *) (cmd + 1); + for (i = 0; i < (int)ent->req.iov_cnt; i++) { + cmd->iovec[i].iov_base = (void *) mb + + (size_t) ent->req.iov[i].iov_base; + cmd->iovec[i].iov_len = ent->req.iov[i].iov_len; + } + + /* Copy cdb that currently points to the command ring */ + cmd->cdb = (uint8_t *) (cmd->iovec + cmd->iov_cnt); + memcpy(cmd->cdb, (void *) mb + ent->req.cdb_off, cdb_len); + + /* Setup handler memory area after iovecs and cdb */ + if (hm_cmd_size) + cmd->hm_private = cmd->cdb + cdb_len; + + TCMU_UPDATE_DEV_TAIL(dev, mb, ent); + return cmd; + } + default: + /* We don't even know how to handle this TCMU opcode. */ + ent->hdr.uflags |= TCMU_UFLAG_UNKNOWN_OP; + } + + TCMU_UPDATE_DEV_TAIL(dev, mb, ent); + } + + return NULL; +} + +static int tcmu_sts_to_scsi(int tcmu_sts, uint8_t *sense) +{ + switch (tcmu_sts) { + case TCMU_STS_OK: + return SAM_STAT_GOOD; + case TCMU_STS_NO_RESOURCE: + return SAM_STAT_TASK_SET_FULL; + /* + * We drop the session during timeout handling so force + * a retry to have it handled during session level recovery. + */ + case TCMU_STS_TIMEOUT: + case TCMU_STS_BUSY: + return SAM_STAT_BUSY; + case TCMU_STS_PASSTHROUGH_ERR: + break; + /* Check Conditions below */ + case TCMU_STS_RANGE: + /* LBA out of range */ + tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2100); + break; + case TCMU_STS_HW_ERR: + /* Internal target failure */ + tcmu_sense_set_data(sense, HARDWARE_ERROR, 0x4400); + break; + case TCMU_STS_MISCOMPARE: + /* Miscompare during verify operation */ + __tcmu_sense_set_data(sense, MISCOMPARE, 0x1d00); + break; + case TCMU_STS_RD_ERR: + /* Read medium error */ + tcmu_sense_set_data(sense, MEDIUM_ERROR, 0x1100); + break; + case TCMU_STS_WR_ERR: + /* Write medium error */ + tcmu_sense_set_data(sense, MEDIUM_ERROR, 0x0C00); + break; + case TCMU_STS_INVALID_CDB: + /* Invalid field in CDB */ + tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2400); + break; + case TCMU_STS_INVALID_PARAM_LIST: + /* Invalid field in parameter list */ + tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2600); + break; + case TCMU_STS_INVALID_PARAM_LIST_LEN: + /* Invalid list parameter list length */ + tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x1a00); + break; + case TCMU_STS_NOTSUPP_SEG_DESC_TYPE: + /* Unsupported segment descriptor type code */ + tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2609); + break; + case TCMU_STS_NOTSUPP_TGT_DESC_TYPE: + /* Unsupported target descriptor type code */ + tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2607); + break; + case TCMU_STS_TOO_MANY_SEG_DESC: + /* The number of segment descriptors exceeds the allowed number */ + tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2608); + break; + case TCMU_STS_TOO_MANY_TGT_DESC: + /* The number of CSCD descriptors exceeds the allowed number */ + tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2606); + break; + case TCMU_STS_CP_TGT_DEV_NOTCONN: + /* Copy target device not reachable */ + tcmu_sense_set_data(sense, COPY_ABORTED, 0x0D02); + break; + case TCMU_STS_INVALID_CP_TGT_DEV_TYPE: + /* Invalid copy target device type */ + tcmu_sense_set_data(sense, COPY_ABORTED, 0x0D03); + break; + case TCMU_STS_CAPACITY_CHANGED: + /* Device capacity has changed */ + tcmu_sense_set_data(sense, UNIT_ATTENTION, 0x2A09); + break; + case TCMU_STS_TRANSITION: + /* ALUA state transition */ + tcmu_sense_set_data(sense, NOT_READY, 0x040A); + break; + case TCMU_STS_IMPL_TRANSITION_ERR: + /* Implicit ALUA state transition failed */ + tcmu_sense_set_data(sense, UNIT_ATTENTION, 0x2A07); + break; + case TCMU_STS_EXPL_TRANSITION_ERR: + /* STPG failed */ + tcmu_sense_set_data(sense, HARDWARE_ERROR, 0x670A); + break; + case TCMU_STS_FENCED: + /* ALUA state in standby */ + tcmu_sense_set_data(sense, NOT_READY, 0x040B); + break; + case TCMU_STS_WR_ERR_INCOMPAT_FRMT: + /* Can't write - incompatible format */ + tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x3005); + break; + case TCMU_STS_NOTSUPP_SAVE_PARAMS: + /* Saving params not supported */ + tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x3900); + break; + case TCMU_STS_FRMT_IN_PROGRESS: + /* Format in progress */ + __tcmu_sense_set_data(sense, NOT_READY, 0x0404); + break; + case TCMU_STS_NOT_HANDLED: + case TCMU_STS_INVALID_CMD: + /* Invalid op code */ + tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2000); + break; + default: + LOG_ERROR("Invalid tcmu status code `", tcmu_sts); + /* Fall through. Kernel will translate to LUN comm failure */ + } + + return SAM_STAT_CHECK_CONDITION; +} + +/* update the ring buffer's tail */ +#define TCMU_UPDATE_RB_TAIL(mb, ent) \ +do { \ + mb->cmd_tail = (mb->cmd_tail + tcmu_hdr_get_len((ent)->hdr.len_op)) % mb->cmdr_size; \ +} while (0) + +static void command_complete_locked(struct tcmu_device *dev, + struct tcmulib_cmd *cmd, + int result) +{ + struct tcmu_mailbox *mb = dev->map; + struct tcmu_cmd_entry *ent = (void *) mb + mb->cmdr_off + mb->cmd_tail; + + /* current command could be PAD in async case */ + while (ent != (void *) mb + mb->cmdr_off + mb->cmd_head) { + if (tcmu_hdr_get_op(ent->hdr.len_op) == TCMU_OP_CMD) + break; + TCMU_UPDATE_RB_TAIL(mb, ent); + ent = (void *) mb + mb->cmdr_off + mb->cmd_tail; + } + + /* cmd_id could be different in async case */ + if (cmd->cmd_id != ent->hdr.cmd_id) { + ent->hdr.cmd_id = cmd->cmd_id; + } + + ent->rsp.scsi_status = tcmu_sts_to_scsi(result, cmd->sense_buf); + if (ent->rsp.scsi_status == SAM_STAT_CHECK_CONDITION) { + memcpy(ent->rsp.sense_buffer, cmd->sense_buf, + TCMU_SENSE_BUFFERSIZE); + } + + TCMU_UPDATE_RB_TAIL(mb, ent); + free(cmd); +} + +void tcmulib_command_complete( + struct tcmu_device *dev, + struct tcmulib_cmd *cmd, + int result) +{ + photon::scoped_lock lock(dev->completion_lock); + command_complete_locked(dev, cmd, result); +} + +void tcmulib_processing_start(struct tcmu_device *dev) +{ + int r; + uint32_t buf; + + /* Clear the event on the fd */ + do { + r = read(dev->fd, &buf, 4); + } while (r == -1 && errno == EINTR); + if (r == -1 && errno != EAGAIN) + LOG_ERROR("failed to read device /dev/`, `", + dev->dev_name, errno); +} + +void tcmulib_processing_complete(struct tcmu_device *dev) +{ + int r; + uint32_t buf = 0; + + /* + * Only the caller changing the counter from zero to one becomes the + * notifier. Other concurrent completions are merged into its next write. + * The original algorithm relied on cooperative threads on one VCPU; atomic + * operations preserve the same no-lost-wakeup property across WorkPool + * VCPUs without serializing the notification syscall on completion_lock. + */ + if (dev->aio_pending_wakeups.fetch_add( + 1, std::memory_order_acq_rel) != 0) + return; + + for (;;) { + /* Tell the kernel there are completed commands */ + do { + r = write(dev->fd, &buf, 4); + } while (r == -1 && errno == EINTR); + if (r == -1 && errno != EAGAIN) + LOG_ERROR("failed to write device /dev/`, `", + dev->dev_name, errno); + + uint32_t expected = 1; + if (dev->aio_pending_wakeups.compare_exchange_strong( + expected, 0, std::memory_order_acq_rel, + std::memory_order_acquire)) + return; + + /* Consume all accumulated completions but retain notifier ownership. */ + dev->aio_pending_wakeups.exchange(1, std::memory_order_acq_rel); + } +} diff --git a/src/libtcmu/libtcmu.h b/src/libtcmu/libtcmu.h new file mode 100644 index 00000000..65c3810b --- /dev/null +++ b/src/libtcmu/libtcmu.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2014 Red Hat, Inc. + * + * This file is licensed to you under your choice of the GNU Lesser + * General Public License, version 2.1 or any later version (LGPLv2.1 or + * later), or the Apache License 2.0. + */ + +/* + * This header defines the libtcmu API. + */ + +#pragma once + +#include +#include +#include +#include +#include "libtcmu_common.h" + +enum tcmulib_cfg_type { + TCMULIB_CFG_DEV_CFGSTR, + TCMULIB_CFG_DEV_SIZE, + TCMULIB_CFG_WRITE_CACHE, +}; + +struct tcmulib_cfg_info { + enum tcmulib_cfg_type type; + + union { + uint64_t dev_size; + char *dev_cfgstring; + bool write_cache; + } data; +}; + +struct tcmulib_handler { + const char *name; /* Human-friendly name */ + const char *subtype; /* Name for cfgstring matching */ + const char *cfg_desc; /* Description of this backstore's config string */ + + struct tcmulib_context *ctx; /* The context this handler is added to, + used internally by libtcmu. */ + + /* + * As much as possible, check that the cfgstring will result + * in a working device when given to us as dev->cfgstring in + * the ->open() call. + * + * This function is optional but gives configuration tools a + * chance to warn users in advance if the device they're + * trying to create is invalid. + * + * Returns true if string is valid. Only if false, set *reason + * to a string that says why. The string will be free()ed. + * Suggest using asprintf(). + */ + bool (*check_config)(const char *cfgstring, char **reason); + + int (*reconfig)(struct tcmu_device *dev, struct tcmulib_cfg_info *cfg); + + bool (*update_logdir)(void); + + /* Per-device added/removed callbacks */ + int (*added)(struct tcmu_device *dev); + void (*removed)(struct tcmu_device *dev); + + void *hm_private; /* private ptr for handler module */ + void *connection; /* private, dbus connection for this subtype */ +}; + +/* + * APIs for libtcmu only + * + * Use these functions to handle TCMU devices and events within an + * existing program's event loop. + */ + +/* Opaque (private) type */ +struct tcmulib_context; + +/* Claim subtypes you wish to handle. Returns libtcmu's master fd or -error.*/ +struct tcmulib_context *tcmulib_initialize(std::vector &handlers); + +/* Register to TCMU DBus service, for the claimed subtypes to be configurable + * in targetcli. */ +void tcmulib_register(struct tcmulib_context *ctx); + +/* Gets the master file descriptor used by tcmulib. */ +int tcmulib_get_master_fd(struct tcmulib_context *ctx); + +/* + * Call this when the master fd becomes ready, from your main thread. + * Handlers' callbacks may be called before it returns. + */ +int tcmulib_master_fd_ready(struct tcmulib_context *ctx); + +/* + * When a device fd becomes ready, call this to get SCSI cmd info in + * 'cmd' struct. libtcmu will allocate hm_cmd_size bytes for each cmd + * that can be accessed via cmd->hm_private pointer. The memory at + * hm_private will be freed in tcmulib_command_complete. + * + * Repeat until it returns false. + */ +struct tcmulib_cmd *tcmulib_get_next_command(struct tcmu_device *dev, + int hm_cmd_size); + +/* + * Mark the command as complete. + * Must be called before get_next_command() is called again. + * + * result is TCMU_STS value from libtcmu_common.h. If TCMU_STS_PASSTHROUGH_ERR + * is returned then the caller must setup the tcmulib_cmd->sense_buf. + */ +void tcmulib_command_complete(struct tcmu_device *dev, struct tcmulib_cmd *cmd, int result); + +/* Call when start processing commands (before calling tcmulib_get_next_command()) */ +void tcmulib_processing_start(struct tcmu_device *dev); + +/* Call when complete processing commands (tcmulib_get_next_command() returned NULL) */ +void tcmulib_processing_complete(struct tcmu_device *dev); + +/* Clean up loose ends when exiting */ +void tcmulib_close(struct tcmulib_context *ctx); diff --git a/src/libtcmu/libtcmu_common.h b/src/libtcmu/libtcmu_common.h new file mode 100644 index 00000000..52151704 --- /dev/null +++ b/src/libtcmu/libtcmu_common.h @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2014 Red Hat, Inc. + * + * This file is licensed to you under your choice of the GNU Lesser + * General Public License, version 2.1 or any later version (LGPLv2.1 or + * later), or the Apache License 2.0. + */ + +/* + * APIs for both libtcmu users and tcmu-runner plugins to use. + */ + +#pragma once + +#include +#include + +struct tcmu_device; +struct tgt_port; +struct tcmulib_cmd; + +/* + * TCMU return status codes + */ +enum { + TCMU_STS_ASYNC_HANDLED = -2, + TCMU_STS_NOT_HANDLED = -1, + TCMU_STS_OK = 0, + TCMU_STS_NO_RESOURCE, + /* handler has setup sense. */ + TCMU_STS_PASSTHROUGH_ERR, + TCMU_STS_BUSY, + TCMU_STS_WR_ERR, + TCMU_STS_RD_ERR, + TCMU_STS_MISCOMPARE, + TCMU_STS_INVALID_CMD, + TCMU_STS_INVALID_CDB, + TCMU_STS_INVALID_PARAM_LIST, + TCMU_STS_INVALID_PARAM_LIST_LEN, + TCMU_STS_TIMEOUT, + TCMU_STS_FENCED, + TCMU_STS_HW_ERR, + TCMU_STS_RANGE, + TCMU_STS_FRMT_IN_PROGRESS, + TCMU_STS_CAPACITY_CHANGED, + TCMU_STS_NOTSUPP_SAVE_PARAMS, + TCMU_STS_WR_ERR_INCOMPAT_FRMT, + TCMU_STS_TRANSITION, + TCMU_STS_IMPL_TRANSITION_ERR, + TCMU_STS_EXPL_TRANSITION_ERR, + TCMU_STS_NO_LOCK_HOLDERS, + /* xcopy specific errors */ + TCMU_STS_NOTSUPP_SEG_DESC_TYPE, + TCMU_STS_NOTSUPP_TGT_DESC_TYPE, + TCMU_STS_CP_TGT_DEV_NOTCONN, + TCMU_STS_INVALID_CP_TGT_DEV_TYPE, + TCMU_STS_TOO_MANY_SEG_DESC, + TCMU_STS_TOO_MANY_TGT_DESC, +}; + +#define TCMU_THREAD_NAME_LEN 16 + +#define SENSE_BUFFERSIZE 96 + +#define CFGFS_ROOT "/sys/kernel/config/target" +#define CFGFS_CORE CFGFS_ROOT"/core" + +#define CFGFS_TARGET_MOD "/sys/module/target_core_user" +#define CFGFS_MOD_PARAM CFGFS_TARGET_MOD"/parameters" + + + +#define round_up(a, b) ({ \ + __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + ((_a + (_b - 1)) / _b) * _b; }) + +#define round_down(a, b) ({ \ + __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + (_a - (_a % _b)); }) + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +struct tcmulib_cmd { + uint16_t cmd_id; + uint8_t *cdb; + struct iovec *iovec; + size_t iov_cnt; + uint8_t sense_buf[SENSE_BUFFERSIZE]; + void *hm_private; + struct tcmulib_cmd *work_next; + + /* Intrusive link and result used by the multi-VCPU completion queue. */ + struct tcmulib_cmd *completion_next; + int completion_result; +}; + +/* Set/Get methods for the opaque tcmu_device */ +void *tcmu_dev_get_private(struct tcmu_device *dev); +void tcmu_dev_set_private(struct tcmu_device *dev, void *priv); +const char *tcmu_dev_get_uio_name(struct tcmu_device *dev); +void tcmu_set_thread_name(const char *prefix, struct tcmu_device *dev); +int tcmu_dev_get_fd(struct tcmu_device *dev); +char *tcmu_dev_get_memory_info(struct tcmu_device *dev, void **base, + size_t *len, off_t *offset); +char *tcmu_dev_get_cfgstring(struct tcmu_device *dev); +void tcmu_dev_set_num_lbas(struct tcmu_device *dev, uint64_t num_lbas); +uint64_t tcmu_dev_get_num_lbas(struct tcmu_device *dev); +void tcmu_dev_set_block_size(struct tcmu_device *dev, uint32_t block_size); +uint32_t tcmu_dev_get_block_size(struct tcmu_device *dev); +uint64_t tcmu_lba_to_byte(struct tcmu_device *dev, uint64_t lba); +uint64_t tcmu_byte_to_lba(struct tcmu_device *dev, uint64_t byte); +void tcmu_dev_set_max_xfer_len(struct tcmu_device *dev, uint32_t len); +uint32_t tcmu_dev_get_max_xfer_len(struct tcmu_device *dev); +void tcmu_dev_set_opt_xcopy_rw_len(struct tcmu_device *dev, uint32_t len); +uint32_t tcmu_dev_get_opt_xcopy_rw_len(struct tcmu_device *dev); +void tcmu_dev_set_max_unmap_len(struct tcmu_device *dev, uint32_t len); +uint32_t tcmu_dev_get_max_unmap_len(struct tcmu_device *dev); +void tcmu_dev_set_opt_unmap_gran(struct tcmu_device *dev, uint32_t len, + bool split); +uint32_t tcmu_dev_get_opt_unmap_gran(struct tcmu_device *dev); +void tcmu_dev_set_unmap_gran_align(struct tcmu_device *dev, uint32_t len); +uint32_t tcmu_dev_get_unmap_gran_align(struct tcmu_device *dev); +void tcmu_dev_set_write_cache_enabled(struct tcmu_device *dev, bool enabled); +bool tcmu_dev_get_write_cache_enabled(struct tcmu_device *dev); +void tcmu_dev_set_solid_state_media(struct tcmu_device *dev, bool solid_state); +bool tcmu_dev_get_solid_state_media(struct tcmu_device *dev); +void tcmu_dev_set_unmap_enabled(struct tcmu_device *dev, bool enabled); +bool tcmu_dev_get_unmap_enabled(struct tcmu_device *dev); +void tcmu_dev_set_write_protect_enabled(struct tcmu_device *dev, bool enabled); +bool tcmu_dev_get_write_protect_enabled(struct tcmu_device *dev); +struct tcmulib_handler *tcmu_dev_get_handler(struct tcmu_device *dev); +void tcmu_dev_flush_ring(struct tcmu_device *dev); +bool tcmu_dev_oooc_supported(struct tcmu_device* dev); + +/* Set/Get methods for interacting with configfs */ +char *tcmu_cfgfs_get_str(const char *path); +int tcmu_cfgfs_set_str(const char *path, const char *val, int val_len); +int tcmu_cfgfs_get_int(const char *path); +int tcmu_cfgfs_set_u32(const char *path, uint32_t val); +int tcmu_cfgfs_dev_get_attr_int(struct tcmu_device *dev, const char *name); +int tcmu_cfgfs_dev_exec_action(struct tcmu_device *dev, const char *name, + uint32_t val); +int tcmu_cfgfs_dev_set_ctrl_u64(struct tcmu_device *dev, const char *key, + uint64_t val); +uint64_t tcmu_cfgfs_dev_get_info_u64(struct tcmu_device *dev, const char *name, + int *fn_ret); +char *tcmu_cfgfs_dev_get_wwn(struct tcmu_device *dev); +int tcmu_cfgfs_mod_param_set_u32(const char *name, uint32_t val); + +/* Helper routines for processing commands */ + +/* SCSI CDB processing */ +int tcmu_cdb_get_length(uint8_t *cdb); +uint64_t tcmu_cdb_get_lba(uint8_t *cdb); +uint32_t tcmu_cdb_get_xfer_length(uint8_t *cdb); +void tcmu_cdb_print_info(struct tcmu_device *dev, const struct tcmulib_cmd *cmd, + const char *info); +uint64_t tcmu_cdb_to_byte(struct tcmu_device *dev, uint8_t *cdb); + +/* iovec processing */ +off_t tcmu_iovec_compare(void *mem, struct iovec *iovec, size_t size); +size_t tcmu_iovec_seek(struct iovec *iovec, size_t count); +void tcmu_iovec_zero(struct iovec *iovec, size_t iov_cnt); +bool tcmu_iovec_zeroed(struct iovec *iovec, size_t iov_cnt); +size_t tcmu_iovec_length(struct iovec *iovec, size_t iov_cnt); + +/* memory mangement */ +size_t tcmu_memcpy_into_iovec(struct iovec *iovec, size_t iov_cnt, void *src, + size_t len); +size_t tcmu_memcpy_from_iovec(void *dest, size_t len, struct iovec *iovec, + size_t iov_cnt); + +/* tcmulib_cmd processing */ +void tcmu_cmd_seek(struct tcmulib_cmd *cmd, size_t count); + +/* SCSI Sense */ +int tcmu_sense_set_data(uint8_t *sense_buf, uint8_t key, uint16_t asc_ascq); +void tcmu_sense_set_info(uint8_t *sense_buf, uint32_t info); +void tcmu_sense_set_key_specific_info(uint8_t *sense_buf, uint16_t info); +void __tcmu_sense_set_data(uint8_t *sense_buf, uint8_t key, uint16_t asc_ascq); + +/* + * Misc + */ +void tcmu_thread_cancel(pthread_t thread); + +extern __thread int __tcmu_is_ework_thread; diff --git a/src/libtcmu/libtcmu_priv.h b/src/libtcmu/libtcmu_priv.h new file mode 100644 index 00000000..c1a091d5 --- /dev/null +++ b/src/libtcmu/libtcmu_priv.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2014 Red Hat, Inc. + * + * This file is licensed to you under your choice of the GNU Lesser + * General Public License, version 2.1 or any later version (LGPLv2.1 or + * later), or the Apache License 2.0. + */ + +/* + * This header defines structures private to libtcmu, and should not + * be used by anyone else. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define KERN_IFACE_VER 2 + +// The full (private) declaration +struct tcmulib_context { + std::vector handlers; + std::map devices; + struct nl_sock *nl_sock = nullptr; +}; + +struct tcmu_device { + int fd; + photon::mutex completion_lock; /* protects response-ring completion */ + std::atomic aio_pending_wakeups{0}; + + struct tcmu_mailbox *map; + size_t map_len; + + uint32_t cmd_tail; + + uint64_t num_lbas; + uint32_t block_size; + uint32_t block_size_shift; + uint32_t max_xfer_len; + uint32_t opt_xcopy_rw_len; + bool split_unmaps; + uint32_t max_unmap_len; + uint32_t opt_unmap_gran; + uint32_t unmap_gran_align; + unsigned int write_cache_enabled:1; + unsigned int solid_state_media:1; + unsigned int unmap_enabled:1; + unsigned int write_protect_enabled:1; + + char dev_name[16]; /* e.g. "uio14" */ + char tcm_hba_name[16]; /* e.g. "user_8" */ + char tcm_dev_name[128]; /* e.g. "backup2" */ + char cfgstring[PATH_MAX]; + + struct tcmulib_handler *handler; + struct tcmulib_context *ctx; + + void *hm_private; /* private ptr for handler module */ +}; diff --git a/src/libtcmu/scsi.cpp b/src/libtcmu/scsi.cpp new file mode 100644 index 00000000..4c7f4806 --- /dev/null +++ b/src/libtcmu/scsi.cpp @@ -0,0 +1,936 @@ +/* + * Copyright (c) 2014 Red Hat, Inc. + * + * This file is licensed to you under your choice of the GNU Lesser + * General Public License, version 2.1 or any later version (LGPLv2.1 or + * later), or the Apache License 2.0. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libtcmu.h" +#include "libtcmu_priv.h" +#include "be_byteshift.h" +#include "scsi.h" + +#include + +struct alua_grp { + /* ALUA spec values */ + uint8_t state; + uint8_t supported_states; + uint8_t tpgs; + uint8_t status; + uint8_t implicit_trans_secs; + bool pref; + uint16_t id; + + /* LIO settings */ + char *name; + unsigned nonop_delay_msecs; + unsigned trans_delay_msecs; + + struct tcmu_device *dev; + uint8_t num_tgt_ports; + /* entry on list returned by lib */ + // struct list_node entry; + // struct list_head tgt_ports; +}; + +struct tgt_port { + uint16_t rel_port_id; + uint8_t proto_id; + char *wwn; + + /* LIO settings */ + char *fabric; + bool enabled; + /* configfs tpgt */ + uint16_t tpgt; + + struct alua_grp *grp; + // /* entry on group's tgt_ports list */ + // struct list_node entry; +}; + +int tcmu_emulate_std_inquiry( + struct tgt_port *port, + uint8_t *cdb, + struct iovec *iovec, + size_t iov_cnt) +{ + uint8_t buf[36]; + + memset(buf, 0, sizeof(buf)); + + buf[2] = 0x05; /* SPC-3 */ + buf[3] = 0x02; /* response data format */ + + /* + * A Third-Party Copy (3PC) + * + * Enable the XCOPY + */ + buf[5] = 0x08; + if (port) + buf[5] |= port->grp->tpgs; + + buf[7] = 0x02; /* CmdQue */ + + memcpy(&buf[8], "LIO-ORG ", 8); + memset(&buf[16], 0x20, 16); + memcpy(&buf[16], "TCMU device", 11); + memcpy(&buf[32], "0002", 4); + buf[4] = 31; /* Set additional length to 31 */ + + tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, sizeof(buf)); + return TCMU_STS_OK; +} + +/* This func from CCAN str/hex/hex.c. Public Domain */ +bool char_to_hex(unsigned char *val, char c) +{ + if (c >= '0' && c <= '9') { + *val = c - '0'; + return true; + } + if (c >= 'a' && c <= 'f') { + *val = c - 'a' + 10; + return true; + } + if (c >= 'A' && c <= 'F') { + *val = c - 'A' + 10; + return true; + } + return false; +} + +int tcmu_emulate_evpd_inquiry( + struct tcmu_device *dev, + struct tgt_port *port, + uint8_t *cdb, + struct iovec *iovec, + size_t iov_cnt) +{ + switch (cdb[2]) { + case 0x0: /* Supported VPD pages */ + { + char data[16]; + + memset(data, 0, sizeof(data)); + + /* data[1] (page code) already 0 */ + /* + * spc4r22 7.7.13 The supported VPD page list shall contain + * a list of all VPD page codes (see 7.7) implemented by the + * logical unit in ascending order beginning with page code 00h + */ + data[4] = 0x00; + data[5] = 0x80; + data[6] = 0x83; + data[7] = 0xb0; + data[8] = 0xb1; + data[9] = 0xb2; + + data[3] = 6; + + tcmu_memcpy_into_iovec(iovec, iov_cnt, data, sizeof(data)); + return TCMU_STS_OK; + } + break; + case 0x80: /* Unit Serial Number */ + { + char data[512]; + char *wwn; + uint32_t len; + + memset(data, 0, sizeof(data)); + + data[1] = 0x80; + + wwn = tcmu_cfgfs_dev_get_wwn(dev); + if (!wwn) + return TCMU_STS_HW_ERR; + + /* + * The maximum length of the unit_serial has limited + * to 254 Bytes in kernel, so here limit to 256 Bytes + * will be enough. + */ + len = snprintf(&data[4], 256, "%s", wwn); + data[3] = len + 1; + + tcmu_memcpy_into_iovec(iovec, iov_cnt, data, sizeof(data)); + + free(wwn); + return TCMU_STS_OK; + } + break; + case 0x83: /* Device identification */ + { + char data[512]; + char *ptr, *p, *wwn; + size_t len, used = 4; + uint16_t *tot_len = (uint16_t*) &data[2]; + uint32_t padding; + bool next; + int i; + + memset(data, 0, sizeof(data)); + + data[1] = 0x83; + + wwn = tcmu_cfgfs_dev_get_wwn(dev); + if (!wwn) + return TCMU_STS_HW_ERR; + + ptr = &data[4]; + + /* 1/5: T10 Vendor id */ + ptr[0] = 2; /* code set: ASCII */ + ptr[1] = 1; /* identifier: T10 vendor id */ + memcpy(&ptr[4], "LIO-ORG ", 8); + len = snprintf(&ptr[12], sizeof(data) - 16, "%s", wwn); + + ptr[3] = 8 + len + 1; + used += (uint8_t)ptr[3] + 4; + ptr += (uint8_t)ptr[3] + 4; + + /* 2/5: NAA binary */ + ptr[0] = 1; /* code set: binary */ + ptr[1] = 3; /* identifier: NAA */ + ptr[3] = 16; /* body length for naa registered extended format */ + + /* + * Set type 6 and use OpenFabrics IEEE Company ID: 00 14 05 + */ + ptr[4] = 0x60; + ptr[5] = 0x01; + ptr[6] = 0x40; + ptr[7] = 0x50; + + /* + * Fill in the rest with a binary representation of WWN + * + * This implementation only uses a nibble out of every byte of + * WWN, but this is what the kernel does, and it's nice for our + * values to match. + */ + next = true; + for (p = wwn, i = 7; *p && i < 20; p++) { + uint8_t val; + + if (!char_to_hex(&val, *p)) + continue; + + if (next) { + next = false; + ptr[i++] |= val; + } else { + next = true; + ptr[i] = val << 4; + } + } + + used += 20; + ptr += 20; + + /* 3/6: Vendor specific */ + ptr[0] = 2; /* code set: ASCII */ + ptr[1] = 0; /* identifier: vendor-specific */ + + len = snprintf(&ptr[4], sizeof(data) - used - 4, "%s", + tcmu_dev_get_cfgstring(dev)); + ptr[3] = len + 1; + + used += (uint8_t)ptr[3] + 4; + ptr += (uint8_t)ptr[3] + 4; + + if (!port) + goto finish_page83; + + /* 4/5: Relative target port ID */ + ptr[0] = port->proto_id << 4; /* proto id */ + ptr[0] |= 0x1; /* Code set: binary */ + ptr[1] = 0x80; /* PIV set */ + ptr[1] |= 0x10; /* Association: 1b assoc with target port */ + ptr[1] |= 0x4; /* Designator type: Relative target port ID */ + ptr[3] = 4; + /* rel tgt port ID */ + ptr[6] = (port->rel_port_id >> 8) & 0xff; + ptr[7] = port->rel_port_id & 0xff; + used += 8; + ptr += 8; + + /* 5/5: Target port group */ + ptr[0] = port->proto_id << 4; /* proto id */ + ptr[0] |= 0x1; /* Code set: binary */ + ptr[1] = 0x80; /* PIV set */ + ptr[1] |= 0x10; /* Association: 1b assoc with target port */ + ptr[1] |= 0x5; /* Designator type: target port group */ + ptr[3] = 4; + /* tpg id */ + ptr[6] = (port->grp->id >> 8) & 0xff; + ptr[7] = port->grp->id & 0xff; + used += 8; + ptr += 8; + + /* SCSI name */ + ptr[0] = port->proto_id << 4; + ptr[0] |= 0x3; /* CODE SET = UTF-8 */ + ptr[1] = 0x80; /* PIV=1 */ + ptr[1] |= 0x10; /* ASSOCIATION = target port: 01b */ + ptr[1] |= 0x8; /* DESIGNATOR TYPE = SCSI name string */ + len = snprintf(&ptr[4], sizeof(data) - used - 4, "%s,t,0x%04x", port->wwn, port->tpgt); + len += 1; /* Include NULL terminator */ + /* + * The null-terminated, null-padded (see 4.4.2) SCSI + * NAME STRING field contains a UTF-8 format string. + * The number of bytes in the SCSI NAME STRING field + * (i.e., the value in the DESIGNATOR LENGTH field) + * shall be no larger than 256 and shall be a multiple + * of four. + */ + padding = ((-len) & 3); + if (padding) + len += padding; + if (len > 256) + len=256; + ptr[3] = len; + used += len + 4; + ptr += len + 4; + + /* Target device designator */ + ptr[0] = port->proto_id << 4; + ptr[0] |= 0x3; /* CODE SET = UTF-8 */ + ptr[1] = 0x80; /* PIV=1 */ + ptr[1] |= 0x20; /* ASSOCIATION = target device: 10b */ + ptr[1] |= 0x8; /* DESIGNATOR TYPE = SCSI name string */ + len = snprintf(&ptr[4], sizeof(data) - used -4, "%s", port->wwn); + len +=1; /* Include NULL terminator */ + /* + * The null-terminated, null-padded (see 4.4.2) SCSI + * NAME STRING field contains a UTF-8 format string. + * The number of bytes in the SCSI NAME STRING field + * (i.e., the value in the DESIGNATOR LENGTH field) + * shall be no larger than 256 and shall be a multiple + * of four. + */ + padding = ((-len) & 3); + if (padding) + len += padding; + if (len >256) + len = 256; + ptr[3] = len; + used += len + 4; + +finish_page83: + /* Done with descriptor list */ + + *tot_len = htobe16(used - 4); + + tcmu_memcpy_into_iovec(iovec, iov_cnt, data, used); + + free(wwn); + wwn = NULL; + + return TCMU_STS_OK; + } + break; + case 0xb0: /* Block Limits */ + { + char data[64]; + uint32_t max_xfer_length; + uint32_t unmap_gran_align; + uint16_t val16; + uint32_t val32; + uint64_t val64; + + memset(data, 0, sizeof(data)); + + data[1] = 0xb0; + + val16 = htobe16(0x3c); + memcpy(&data[2], &val16, 2); + + /* WSNZ = 1: the device server won't support a value of zero + * in the NUMBER OF LOGICAL BLOCKS field in the WRITE SAME + * command CDBs + */ + data[4] = 0x01; + + /* + * From SCSI Commands Reference Manual, section Block Limits + * VPD page (B0h) + * + * MAXIMUM COMPARE AND WRITE LENGTH: set to a non-zero value + * indicates the maximum value that the device server accepts + * in the NUMBER OF LOGICAL BLOCKS field in the COMPARE AND + * WRITE command. + * + * It should be less than or equal to MAXIMUM TRANSFER LENGTH. + */ + data[5] = MAX_CAW_LENGTH; + + /* + * Daemons like runner may override the user requested + * value due to device specific limits. + */ + max_xfer_length = tcmu_dev_get_max_xfer_len(dev); + + val32 = htobe32(max_xfer_length); + /* Max xfer length */ + memcpy(&data[8], &val32, 4); + /* Optimal xfer length */ + memcpy(&data[12], &val32, 4); + + if (tcmu_dev_get_unmap_enabled(dev)) { + /* MAXIMUM UNMAP LBA COUNT */ + val32 = htobe32(tcmu_dev_get_max_unmap_len(dev)); + memcpy(&data[20], &val32, 4); + + /* MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT */ + val32 = htobe32(VPD_MAX_UNMAP_BLOCK_DESC_COUNT); + memcpy(&data[24], &val32, 4); + + /* OPTIMAL UNMAP GRANULARITY */ + val32 = htobe32(tcmu_dev_get_opt_unmap_gran(dev)); + memcpy(&data[28], &val32, 4); + + /* UNMAP GRANULARITY ALIGNMENT */ + unmap_gran_align = tcmu_dev_get_unmap_gran_align(dev); + val32 = htobe32(unmap_gran_align); + memcpy(&data[32], &val32, 4); + + /* UGAVALID: An unmap granularity alignment valid bit */ + data[32] |= 0x80; + } + + /* MAXIMUM WRITE SAME LENGTH */ + val64 = htobe64(VPD_MAX_WRITE_SAME_LENGTH); + memcpy(&data[36], &val64, 8); + + tcmu_memcpy_into_iovec(iovec, iov_cnt, data, sizeof(data)); + + return TCMU_STS_OK; + } + break; + case 0xb1: /* Block Device Characteristics VPD page */ + { + char data[64]; + uint16_t val16; + + memset(data, 0, sizeof(data)); + + /* + * From spc-5 Revision 14, section 6.7.2 Standard INQUIRY data + * set the devive type to Direct access block device. + */ + data[0] = 0x00; + + /* PAGE CODE (B1h) */ + data[1] = 0xb1; + + /* PAGE LENGTH (003Ch)*/ + val16 = htobe16(0x003c); + memcpy(&data[2], &val16, 2); + + if (tcmu_dev_get_solid_state_media(dev)) { + val16 = htobe16(0x0001); + memcpy(&data[4], &val16, 2); + } + + tcmu_memcpy_into_iovec(iovec, iov_cnt, data, sizeof(data)); + return TCMU_STS_OK; + } + break; + case 0xb2: /* Logical Block Provisioning VPD page */ + { + char data[64]; + uint16_t val16; + + memset(data, 0, sizeof(data)); + + /* + * From spc-5 Revision 14, section 6.7.2 Standard INQUIRY data + * set the device type to Direct access block device. + */ + data[0] = 0x00; + + /* PAGE CODE (B2h) */ + data[1] = 0xb2; + + /* + * PAGE LENGTH field: PROVISIONING GROUP DESCRIPTOR field will be + * not present. + */ + val16 = htobe16(0x0004); + memcpy(&data[2], &val16, 2); + + /* + * The logical block provisioning read zeros (LBPRZ) field. + * + * The logical block data represented by unmapped LBAs is set to zeros + */ + data[5] = 0x04; + + /* + * The logical block provisioning unmap (LBPU|LBPWS|LBPWS10) fields. + * + * This will enable the UNMAP command for the device server and write + * same(10|16) command. + */ + if (tcmu_dev_get_unmap_enabled(dev)) + data[5] |= 0xe0; + + tcmu_memcpy_into_iovec(iovec, iov_cnt, data, sizeof(data)); + return TCMU_STS_OK; + } + break; + default: + LOG_ERROR("[dev `] Vital product data page code ` not support", dev->tcm_dev_name, + cdb[2]); + return TCMU_STS_INVALID_CDB; + } +} + +/* + * Emulate INQUIRY(0x12) + */ +int tcmu_emulate_inquiry( + struct tcmu_device *dev, + struct tgt_port *port, + uint8_t *cdb, + struct iovec *iovec, + size_t iov_cnt) +{ + if (!(cdb[1] & 0x01)) { + if (cdb[2]) + return TCMU_STS_INVALID_CDB; + return tcmu_emulate_std_inquiry(port, cdb, iovec, iov_cnt); + } + return tcmu_emulate_evpd_inquiry(dev, port, cdb, iovec, iov_cnt); +} + +int tcmu_emulate_test_unit_ready( + uint8_t *cdb, + struct iovec *iovec, + size_t iov_cnt) +{ + return TCMU_STS_OK; +} + +int tcmu_emulate_read_capacity_10( + uint64_t num_lbas, + uint32_t block_size, + uint8_t *cdb, + struct iovec *iovec, + size_t iov_cnt) +{ + uint8_t buf[8]; + uint32_t val32; + + memset(buf, 0, sizeof(buf)); + + if (num_lbas < 0x100000000ULL) { + // Return the LBA of the last logical block, so subtract 1. + val32 = htobe32(num_lbas-1); + } else { + // This lets the initiator know that he needs to use + // Read Capacity(16). + val32 = 0xffffffff; + } + + memcpy(&buf[0], &val32, 4); + + val32 = htobe32(block_size); + memcpy(&buf[4], &val32, 4); + + /* all else is zero */ + + tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, sizeof(buf)); + + return TCMU_STS_OK; +} + +int tcmu_emulate_read_capacity_16( + uint64_t num_lbas, + uint32_t block_size, + uint8_t *cdb, + struct iovec *iovec, + size_t iov_cnt) +{ + uint8_t buf[32]; + uint64_t val64; + uint32_t val32; + + memset(buf, 0, sizeof(buf)); + + // Return the LBA of the last logical block, so subtract 1. + val64 = htobe64(num_lbas-1); + memcpy(&buf[0], &val64, 8); + + val32 = htobe32(block_size); + memcpy(&buf[8], &val32, 4); + + /* + * Logical Block Provisioning Management Enabled (LBPME) bit + * + * The LBPME bit sets to one and then the logical unit implements + * logical block provisioning management + */ + buf[14] = 0x80; + + /* + * The logical block provisioning read zeros (LBPRZ) bit shall be + * set to one if the LBPRZ field is set to xx1b in VPD B2. The + * LBPRZ bit shall be set to zero if the LBPRZ field is not set + * to xx1b. + */ + buf[14] |= 0x40; + + /* all else is zero */ + + tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, sizeof(buf)); + + return TCMU_STS_OK; +} + +static void copy_to_response_buf(uint8_t *to_buf, size_t to_len, + uint8_t *from_buf, size_t from_len) +{ + if (!to_buf) + return; + /* + * SPC 4r37: 4.3.5.6 Allocation length: + * + * The device server shall terminate transfers to the Data-In Buffer + * when the number of bytes or blocks specified by the ALLOCATION + * LENGTH field have been transferred or when all available data + * have been transferred, whichever is less. + */ + memcpy(to_buf, from_buf, to_len > from_len ? from_len : to_len); +} + +static int handle_rwrecovery_page(struct tcmu_device *dev, uint8_t *ret_buf, + size_t ret_buf_len) +{ + uint8_t buf[12]; + + memset(buf, 0, sizeof(buf)); + buf[0] = 0x1; + buf[1] = 0xa; + + copy_to_response_buf(ret_buf, ret_buf_len, buf, 12); + return 12; +} + +static int handle_cache_page(struct tcmu_device *dev, uint8_t *ret_buf, + size_t ret_buf_len) +{ + uint8_t buf[20]; + + memset(buf, 0, sizeof(buf)); + buf[0] = 0x8; + buf[1] = 0x12; + + /* + * If device supports a writeback cache then set writeback + * cache enable (WCE) + */ + if (tcmu_dev_get_write_cache_enabled(dev)) + buf[2] = 0x4; + + copy_to_response_buf(ret_buf, ret_buf_len, buf, 20); + return 20; +} + +static int handle_control_page(struct tcmu_device *dev, uint8_t *ret_buf, + size_t ret_buf_len) +{ + uint8_t buf[12]; + + memset(buf, 0, sizeof(buf)); + buf[0] = 0x0a; + buf[1] = 0x0a; + + /* From spc4r31, section 7.5.7 Control mode Page + * + * GLTSD = 1: because we don't implicitly save log parameters + * + * A global logging target save disable (GLTSD) bit set to + * zero specifies that the logical unit implicitly saves, at + * vendor specific intervals, each log parameter in which the + * TSD bit (see 7.3) is set to zero. A GLTSD bit set to one + * specifies that the logical unit shall not implicitly save + * any log parameters. + */ + buf[2] = 0x02; + + /* From spc4r31, section 7.5.7 Control mode Page + * + * TAS = 1: Currently not settable by tcmu. Using the LIO default + * + * A task aborted status (TAS) bit set to zero specifies that + * aborted commands shall be terminated by the device server + * without any response to the application client. A TAS bit + * set to one specifies that commands aborted by the actions + * of an I_T nexus other than the I_T nexus on which the command + * was received shall be completed with TASK ABORTED status + */ + buf[5] = 0x40; + + /* From spc4r31, section 7.5.7 Control mode Page + * + * BUSY TIMEOUT PERIOD: Currently is unlimited + * + * The BUSY TIMEOUT PERIOD field specifies the maximum time, in + * 100 milliseconds increments, that the application client allows + * for the device server to return BUSY status for unanticipated + * conditions that are not a routine part of commands from the + * application client. This value may be rounded down as defined + * in 5.4(the Parameter rounding section). + * + * A 0000h value in this field is undefined by this standard. + * An FFFFh value in this field is defined as an unlimited period. + */ + buf[8] = 0xff; + buf[9] = 0xff; + + copy_to_response_buf(ret_buf, ret_buf_len, buf, 12); + return 12; +} + + +static struct mode_sense_handler { + uint8_t page; + uint8_t subpage; + int (*get)(struct tcmu_device *dev, uint8_t *buf, size_t buf_len); +} modesense_handlers[] = { + {0x1, 0, handle_rwrecovery_page}, + {0x8, 0, handle_cache_page}, + {0xa, 0, handle_control_page}, +}; + +static ssize_t handle_mode_sense(struct tcmu_device *dev, + struct mode_sense_handler *handler, + uint8_t **buf, size_t alloc_len, + size_t *used_len, bool sense_ten) +{ + int ret; + + ret = handler->get(dev, *buf, alloc_len - *used_len); + + if (!sense_ten && (*used_len + ret >= 255)) + return -EINVAL; + + /* + * SPC 4r37: 4.3.5.6 Allocation length: + * + * If the information being transferred to the Data-In Buffer includes + * fields containing counts of the number of bytes in some or all of + * the data (e.g., the PARAMETER DATA LENGTH field, the PAGE LENGTH + * field, the DESCRIPTOR LENGTH field, the AVAILABLE DATA field), + * then the contents of these fields shall not be altered to reflect + * the truncation, if any, that results from an insufficient + * ALLOCATION LENGTH value + */ + /* + * Setup the buffer so to still loop over the handlers, but just + * increment the used_len so we can return the + * final value. + */ + if (*buf && (*used_len + ret >= alloc_len)) + *buf = NULL; + + *used_len += ret; + if (*buf) + *buf += ret; + return ret; +} + +/* + * Handle MODE_SENSE(6) and MODE_SENSE(10). + * + * For TYPE_DISK only. + */ +int tcmu_emulate_mode_sense( + struct tcmu_device *dev, + uint8_t *cdb, + struct iovec *iovec, + size_t iov_cnt) +{ + bool sense_ten = (cdb[0] == MODE_SENSE_10); + uint8_t page_code = cdb[2] & 0x3f; + uint8_t subpage_code = cdb[3]; + size_t alloc_len = tcmu_cdb_get_xfer_length(cdb); + int i; + int ret; + size_t used_len; + uint8_t *buf; + uint8_t *orig_buf = NULL; + + if (!alloc_len) + return TCMU_STS_OK; + + /* Mode parameter header. Mode data length filled in at the end. */ + used_len = sense_ten ? 8 : 4; + if (used_len > alloc_len) + goto fail; + + buf = calloc(1, alloc_len); + if (!buf) + return TCMU_STS_NO_RESOURCE; + + orig_buf = buf; + buf += used_len; + + /* Don't fill in device-specific parameter */ + /* This helper fn doesn't support sw write protect (SWP) */ + + /* Don't report block descriptors */ + + if (page_code == 0x3f) { + for (i = 0; i < (int)ARRAY_SIZE(modesense_handlers); i++) { + ret = handle_mode_sense(dev, &modesense_handlers[i], + &buf, alloc_len, &used_len, + sense_ten); + if (ret < 0) + goto free_buf; + } + } else { + ret = 0; + + for (i = 0; i < (int)ARRAY_SIZE(modesense_handlers); i++) { + if (page_code == modesense_handlers[i].page && + subpage_code == modesense_handlers[i].subpage) { + ret = handle_mode_sense(dev, + &modesense_handlers[i], + &buf, alloc_len, + &used_len, sense_ten); + break; + } + } + + if (ret <= 0) + goto free_buf; + } + + if (sense_ten) { + uint16_t *ptr = (uint16_t*) orig_buf; + *ptr = htobe16(used_len - 2); + } + else { + orig_buf[0] = used_len - 1; + } + + if (tcmu_dev_get_write_protect_enabled(dev)) { + if (sense_ten) { + orig_buf[3] |= 0x80; + } else { + orig_buf[2] |= 0x80; + } + } + + tcmu_memcpy_into_iovec(iovec, iov_cnt, orig_buf, alloc_len); + free(orig_buf); + return TCMU_STS_OK; + +free_buf: + free(orig_buf); +fail: + return TCMU_STS_INVALID_CDB; +} + +/* + * Handle MODE_SELECT(6) and MODE_SELECT(10). + * + * For TYPE_DISK only. + */ +int tcmu_emulate_mode_select( + struct tcmu_device *dev, + uint8_t *cdb, + struct iovec *iovec, + size_t iov_cnt) +{ + bool select_ten = (cdb[0] == MODE_SELECT_10); + uint8_t page_code = cdb[2] & 0x3f; + uint8_t subpage_code = cdb[3]; + size_t alloc_len = tcmu_cdb_get_xfer_length(cdb); + int i; + int ret = 0; + size_t hdr_len = select_ten ? 8 : 4; + uint8_t buf[512]; + uint8_t in_buf[512]; + bool got_sense = false; + + if (!alloc_len) + return TCMU_STS_OK; + + if (tcmu_memcpy_from_iovec(in_buf, sizeof(in_buf), iovec, iov_cnt) >= sizeof(in_buf)) + return TCMU_STS_INVALID_PARAM_LIST_LEN; + + /* Abort if !pf or sp */ + if (!(cdb[1] & 0x10) || (cdb[1] & 0x01)) + return TCMU_STS_INVALID_CDB; + + memset(buf, 0, sizeof(buf)); + for (i = 0; i < (int)ARRAY_SIZE(modesense_handlers); i++) { + if (page_code == modesense_handlers[i].page + && subpage_code == modesense_handlers[i].subpage) { + ret = modesense_handlers[i].get(dev, &buf[hdr_len], + sizeof(buf) - hdr_len); + if (ret <= 0) + return TCMU_STS_INVALID_CDB; + + if (!select_ten && (hdr_len + ret >= 255)) + return TCMU_STS_INVALID_CDB; + + got_sense = true; + break; + } + } + + if (!got_sense) + return TCMU_STS_INVALID_CDB; + + if (alloc_len < (hdr_len + ret)) + return TCMU_STS_INVALID_PARAM_LIST_LEN; + + /* Verify what was selected is identical to what sense returns, since we + don't support actually setting anything. */ + if (memcmp(&buf[hdr_len], &in_buf[hdr_len], ret)) + return TCMU_STS_INVALID_PARAM_LIST; + + return TCMU_STS_OK; +} + +int tcmu_emulate_start_stop(struct tcmu_device *dev, uint8_t *cdb) +{ + if ((cdb[4] >> 4) & 0xf) + return TCMU_STS_INVALID_CDB; + + /* Currently, we don't allow ejecting the medium, so we're + * ignoring the FBO_PREV_EJECT flag, but it may turn out that + * initiators do not handle this well, so we may have to change + * this behavior. + */ + + if (!(cdb[4] & 0x01)) + return TCMU_STS_INVALID_CDB; + + return TCMU_STS_OK; +} diff --git a/src/libtcmu/scsi.h b/src/libtcmu/scsi.h new file mode 100644 index 00000000..1dc1068a --- /dev/null +++ b/src/libtcmu/scsi.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2014 Red Hat, Inc. + * + * This file is licensed to you under your choice of the GNU Lesser + * General Public License, version 2.1 or any later version (LGPLv2.1 or + * later), or the Apache License 2.0. + */ + +/* + * API used by tcmu-runner and example daemons + */ + +#pragma once + +#include +#include + +/* Temporarily limit this to 32M */ +#define VPD_MAX_UNMAP_LBA_COUNT (32 * 1024 * 1024) +#define VPD_MAX_UNMAP_BLOCK_DESC_COUNT 0x04 +/* Temporarily limit this is 0x1 */ +#define MAX_CAW_LENGTH 0x01 + +#define VPD_MAX_WRITE_SAME_LENGTH 0xFFFFFFFF + +/* Basic implementations of mandatory SCSI commands */ +bool char_to_hex(unsigned char *val, char c); +struct tcmur_handler *tcmu_get_runner_handler(struct tcmu_device *dev); +int tcmu_emulate_inquiry(struct tcmu_device *dev, struct tgt_port *port, uint8_t *cdb, struct iovec *iovec, size_t iov_cnt); +int tcmu_emulate_start_stop(struct tcmu_device *dev, uint8_t *cdb); +int tcmu_emulate_test_unit_ready(uint8_t *cdb, struct iovec *iovec, size_t iov_cnt); +int tcmu_emulate_read_capacity_10(uint64_t num_lbas, uint32_t block_size, uint8_t *cdb, + struct iovec *iovec, size_t iov_cnt); +int tcmu_emulate_read_capacity_16(uint64_t num_lbas, uint32_t block_size, uint8_t *cdb, + struct iovec *iovec, size_t iov_cnt); +int tcmu_emulate_mode_sense(struct tcmu_device *dev, uint8_t *cdb, + struct iovec *iovec, size_t iov_cnt); +int tcmu_emulate_mode_select(struct tcmu_device *dev, uint8_t *cdb, + struct iovec *iovec, size_t iov_cnt); diff --git a/src/libtcmu/scsi_defs.h b/src/libtcmu/scsi_defs.h new file mode 100644 index 00000000..98b88294 --- /dev/null +++ b/src/libtcmu/scsi_defs.h @@ -0,0 +1,121 @@ +/* + * Additional values not defined by other headers, they + * seem a little incomplete. + * + * Find codes in the various SCSI specs. + * BTW sense codes are at www.t10.org/lists/asc-num.txt + * + */ +#pragma once + +/* + * SCSI Opcodes + */ +#define READ_FORMAT_CAPACITIES 0x23 +#define UNMAP 0x42 +#define GET_CONFIGURATION 0x46 +#define READ_DISC_INFORMATION 0x51 +#define MODE_SELECT_10 0x55 +#define MODE_SENSE_10 0x5a +#define EXTENDED_COPY 0x83 +#define RECEIVE_COPY_RESULTS 0x84 /* RECEIVE COPY STATUS */ +#define READ_16 0x88 +#define COMPARE_AND_WRITE 0x89 +#define WRITE_16 0x8a +#define WRITE_VERIFY_16 0x8e +#define SYNCHRONIZE_CACHE_16 0x91 +#define WRITE_SAME_16 0x93 +#define SERVICE_ACTION_IN_16 0x9e +#define READ_DVD_STRUCTURE 0xad +#define MECHANISM_STATUS 0xbd +#define MAINTENANCE_IN 0xa3 +#define MAINTENANCE_OUT 0xa4 +#define MI_REPORT_TARGET_PGS 0x0a +#define MO_SET_TARGET_PGS 0x0a + +/* + * Receive Copy Results Sevice Actions + */ +#define RCR_SA_COPY_STATUS 0x00 +#define RCR_SA_RECEIVE_DATA 0x01 +#define RCR_SA_OPERATING_PARAMETERS 0x03 +#define RCR_SA_FAILED_SEGMENT_DETAILS 0x04 + +/* + * Receive Copy Results Operating Parameters + */ +#define RCR_OP_MAX_TARGET_DESC_COUNT 0x02 +#define RCR_OP_MAX_SEGMENT_DESC_COUNT 0x01 +#define RCR_OP_MAX_DESC_LIST_LEN 1024 +#define RCR_OP_MAX_SEGMENT_LEN 16777216 +#define RCR_OP_TOTAL_CONCURR_COPIES 0x01 +#define RCR_OP_MAX_CONCURR_COPIES 0x01 +#define RCR_OP_DATA_SEG_GRAN_LOG2 0x09 +#define RCR_OP_INLINE_DATA_GRAN_LOG2 0x09 +#define RCR_OP_HELD_DATA_GRAN_LOG2 0x09 + +/* + * Receive Copy Results descriptor type codes supports + */ +#define RCR_OP_IMPLE_DES_LIST_LENGTH 0x02 +#define XCOPY_SEG_DESC_TYPE_CODE_B2B 0x02 /* block --> block */ +#define XCOPY_TARGET_DESC_TYPE_CODE_ID 0xe4 /* Identification descriptor */ + +/* + * Service action opcodes + */ +#define READ_CAPACITY_16 0x10 + +/* SCSI protocols; these are taken from SPC-3 section 7.5 */ +enum scsi_protocol { + SCSI_PROTOCOL_FCP = 0, /* Fibre Channel */ + SCSI_PROTOCOL_SPI = 1, /* parallel SCSI */ + SCSI_PROTOCOL_SSA = 2, /* Serial Storage Architecture - Obsolete */ + SCSI_PROTOCOL_SBP = 3, /* firewire */ + SCSI_PROTOCOL_SRP = 4, /* Infiniband RDMA */ + SCSI_PROTOCOL_ISCSI = 5, + SCSI_PROTOCOL_SAS = 6, + SCSI_PROTOCOL_ADT = 7, /* Media Changers */ + SCSI_PROTOCOL_ATA = 8, + SCSI_PROTOCOL_UNSPEC = 0xf, /* No specific protocol */ +}; + +/* + * SCSI Architecture Model (SAM) Status codes. Taken from SAM-3 draft + * T10/1561-D Revision 4 Draft dated 7th November 2002. + */ +#define SAM_STAT_GOOD 0x00 +#define SAM_STAT_CHECK_CONDITION 0x02 +#define SAM_STAT_CONDITION_MET 0x04 +#define SAM_STAT_BUSY 0x08 +#define SAM_STAT_INTERMEDIATE 0x10 +#define SAM_STAT_INTERMEDIATE_CONDITION_MET 0x14 +#define SAM_STAT_RESERVATION_CONFLICT 0x18 +#define SAM_STAT_COMMAND_TERMINATED 0x22 /* obsolete in SAM-3 */ +#define SAM_STAT_TASK_SET_FULL 0x28 +#define SAM_STAT_ACA_ACTIVE 0x30 +#define SAM_STAT_TASK_ABORTED 0x40 + +#define ALUA_ACCESS_STATE_OPTIMIZED 0x0 +#define ALUA_ACCESS_STATE_NON_OPTIMIZED 0x1 +#define ALUA_ACCESS_STATE_STANDBY 0x2 +#define ALUA_ACCESS_STATE_UNAVAILABLE 0x3 +#define ALUA_ACCESS_STATE_LBA_DEPENDENT 0x4 +#define ALUA_ACCESS_STATE_OFFLINE 0xe +#define ALUA_ACCESS_STATE_TRANSITIONING 0xf + +#define ALUA_SUP_OPTIMIZED 0x01 +#define ALUA_SUP_NON_OPTIMIZED 0x02 +#define ALUA_SUP_STANDBY 0x04 +#define ALUA_SUP_UNAVAILABLE 0x08 +#define ALUA_SUP_LBA_DEPENDENT 0x10 +#define ALUA_SUP_OFFLINE 0x40 +#define ALUA_SUP_TRANSITIONING 0x80 + +#define TPGS_ALUA_NONE 0x00 +#define TPGS_ALUA_IMPLICIT 0x10 +#define TPGS_ALUA_EXPLICIT 0x20 + +#define ALUA_STAT_NONE 0x00 +#define ALUA_STAT_ALTERED_BY_EXPLICIT_STPG 0x01 +#define ALUA_STAT_ALTERED_BY_IMPLICIT_ALUA 0x02 diff --git a/src/libtcmu/target_core_user_local.h b/src/libtcmu/target_core_user_local.h new file mode 100644 index 00000000..40e3400b --- /dev/null +++ b/src/libtcmu/target_core_user_local.h @@ -0,0 +1,153 @@ +#pragma once +/* This header will be used by application too */ + +#include +#include + +#define TCMU_VERSION "2.0" + +/* + * Ring Design + * ----------- + * + * The mmaped area is divided into three parts: + * 1) The mailbox (struct tcmu_mailbox, below) + * 2) The command ring + * 3) Everything beyond the command ring (data) + * + * The mailbox tells userspace the offset of the command ring from the + * start of the shared memory region, and how big the command ring is. + * + * The kernel passes SCSI commands to userspace by putting a struct + * tcmu_cmd_entry in the ring, updating mailbox->cmd_head, and poking + * userspace via uio's interrupt mechanism. + * + * tcmu_cmd_entry contains a header. If the header type is PAD, + * userspace should skip hdr->length bytes (mod cmdr_size) to find the + * next cmd_entry. + * + * Otherwise, the entry will contain offsets into the mmaped area that + * contain the cdb and data buffers -- the latter accessible via the + * iov array. iov addresses are also offsets into the shared area. + * + * When userspace is completed handling the command, set + * entry->rsp.scsi_status, fill in rsp.sense_buffer if appropriate, + * and also set mailbox->cmd_tail equal to the old cmd_tail plus + * hdr->length, mod cmdr_size. If cmd_tail doesn't equal cmd_head, it + * should process the next packet the same way, and so on. + */ + +#define TCMU_MAILBOX_VERSION 2 +#define ALIGN_SIZE 64 /* Should be enough for most CPUs */ +#define TCMU_MAILBOX_FLAG_CAP_OOOC (1 << 0) /* Out-of-order completions */ + +struct tcmu_mailbox { + __u16 version; + __u16 flags; + __u32 cmdr_off; + __u32 cmdr_size; + + __u32 cmd_head; + + /* Updated by user. On its own cacheline */ + __u32 cmd_tail __attribute__((__aligned__(ALIGN_SIZE))); + +} __attribute__((packed)); + +enum tcmu_opcode { + TCMU_OP_PAD = 0, + TCMU_OP_CMD, +}; + +/* + * Only a few opcodes, and length is 8-byte aligned, so use low bits for opcode. + */ +struct tcmu_cmd_entry_hdr { + __u32 len_op; + __u16 cmd_id; + __u8 kflags; +#define TCMU_UFLAG_UNKNOWN_OP 0x1 + __u8 uflags; + +} __attribute__((packed)); + +#define TCMU_OP_MASK 0x7 + +static __inline__ enum tcmu_opcode tcmu_hdr_get_op(__u32 len_op) +{ + return len_op & TCMU_OP_MASK; +} + +static __inline__ void tcmu_hdr_set_op(__u32 *len_op, enum tcmu_opcode op) +{ + *len_op &= ~TCMU_OP_MASK; + *len_op |= (op & TCMU_OP_MASK); +} + +static __inline__ __u32 tcmu_hdr_get_len(__u32 len_op) +{ + return len_op & ~TCMU_OP_MASK; +} + +static __inline__ void tcmu_hdr_set_len(__u32 *len_op, __u32 len) +{ + *len_op &= TCMU_OP_MASK; + *len_op |= len; +} + +/* Currently the same as SCSI_SENSE_BUFFERSIZE */ +#define TCMU_SENSE_BUFFERSIZE 96 + +struct tcmu_cmd_entry { + struct tcmu_cmd_entry_hdr hdr; + + union { + struct { + uint32_t iov_cnt; + uint32_t iov_bidi_cnt; + uint32_t iov_dif_cnt; + uint64_t cdb_off; + uint64_t __pad1; + uint64_t __pad2; + struct iovec iov[0]; + } req; + struct { + uint8_t scsi_status; + uint8_t __pad1; + uint16_t __pad2; + uint32_t __pad3; + char sense_buffer[TCMU_SENSE_BUFFERSIZE]; + } rsp; + }; + +} __attribute__((packed)); + +#define TCMU_OP_ALIGN_SIZE sizeof(uint64_t) + +enum tcmu_genl_cmd { + TCMU_CMD_UNSPEC, + TCMU_CMD_ADDED_DEVICE, + TCMU_CMD_REMOVED_DEVICE, + TCMU_CMD_RECONFIG_DEVICE, + TCMU_CMD_ADDED_DEVICE_DONE, + TCMU_CMD_REMOVED_DEVICE_DONE, + TCMU_CMD_RECONFIG_DEVICE_DONE, + TCMU_CMD_SET_FEATURES, + __TCMU_CMD_MAX, +}; +#define TCMU_CMD_MAX (__TCMU_CMD_MAX - 1) + +enum tcmu_genl_attr { + TCMU_ATTR_UNSPEC, + TCMU_ATTR_DEVICE, + TCMU_ATTR_MINOR, + TCMU_ATTR_PAD, + TCMU_ATTR_DEV_CFG, + TCMU_ATTR_DEV_SIZE, + TCMU_ATTR_WRITECACHE, + TCMU_ATTR_CMD_STATUS, + TCMU_ATTR_DEVICE_ID, + TCMU_ATTR_SUPP_KERN_CMD_REPLY, + __TCMU_ATTR_MAX, +}; +#define TCMU_ATTR_MAX (__TCMU_ATTR_MAX - 1) From 10e9d1e97fb7e685cfa549e9800926affe725902 Mon Sep 17 00:00:00 2001 From: Lanzheng Liu Date: Fri, 28 Aug 2026 10:23:13 +0800 Subject: [PATCH 3/9] feat: dispatch TCMU I/O across Photon WorkPool Signed-off-by: Lanzheng Liu --- README.md | 5 +- src/config.h | 2 +- src/example_config/overlaybd-registryv2.json | 1 + src/example_config/overlaybd.json | 1 + src/main.cpp | 271 +++++++++++++------ 5 files changed, 191 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index c6af63f4..a5e79ca0 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ To build overlaybd from source code, the following dependencies are required: * CMake >= 3.14 -* gcc/g++ >= 7 +* gcc/g++ >= 8 * Libaio, libcurl, libnl3, glib2 and openssl runtime and development libraries. * CentOS 7/Fedora: `sudo yum install libaio-devel libcurl-devel openssl-devel libnl3-devel libzstd-static e2fsprogs-devel` @@ -158,6 +158,7 @@ Default configure file `overlaybd.json` is installed to `/etc/overlaybd/`. "path": "/opt/overlaybd/cred.json" }, "ioEngine": 0, + "workpoolSize": 8, "download": { "enable": true, "delay": 600, @@ -216,7 +217,7 @@ Default configure file `overlaybd.json` is installed to `/etc/overlaybd/`. | exporterConfig.port | port for http server to show metrics. | | exporterConfig.updateInterval | Time interval to update metrics in microseconds. | | enableAudit | Enable audit or not. | -| enableThread | Enable overlaybd device run in seprate thread or not. Note `cacheType` should be `ocf`. `false` is default. | +| workpoolSize | Number of Photon vCPUs (OS threads) in the shared device work pool. `8` is default. | | auditPath | The path for audit file, `/var/log/overlaybd-audit.log` is the default value. | | registryFsVersion | registry client version, 'v1' libcurl based, 'v2' is photon http based. 'v2' is the default value. | | prefetchConfig.concurrency | Prefetch concurrency for reloading trace, `16` is default | diff --git a/src/config.h b/src/config.h index d89a210a..b19375ed 100644 --- a/src/config.h +++ b/src/config.h @@ -153,7 +153,7 @@ struct GlobalConfig : public ConfigUtils::Config { APPCFG_PARA(logPath, std::string, "/var/log/overlaybd.log"); APPCFG_PARA(download, DownloadConfig); APPCFG_PARA(enableAudit, bool, true); - APPCFG_PARA(enableThread, bool, false); + APPCFG_PARA(workpoolSize, uint32_t, 8); APPCFG_PARA(p2pConfig, P2PConfig); APPCFG_PARA(exporterConfig, ExporterConfig); APPCFG_PARA(auditPath, std::string, "/var/log/overlaybd-audit.log"); diff --git a/src/example_config/overlaybd-registryv2.json b/src/example_config/overlaybd-registryv2.json index 5228416a..990df9b0 100644 --- a/src/example_config/overlaybd-registryv2.json +++ b/src/example_config/overlaybd-registryv2.json @@ -13,6 +13,7 @@ "path": "/opt/overlaybd/cred.json" }, "ioEngine": 0, + "workpoolSize": 8, "download": { "enable": true, "delay": 600, diff --git a/src/example_config/overlaybd.json b/src/example_config/overlaybd.json index 2fe5cd26..cc274fe2 100644 --- a/src/example_config/overlaybd.json +++ b/src/example_config/overlaybd.json @@ -18,6 +18,7 @@ "path": "/opt/overlaybd/cred.json" }, "ioEngine": 0, + "workpoolSize": 8, "download": { "enable": true, "delay": 600, diff --git a/src/main.cpp b/src/main.cpp index e151dd38..31f65a34 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,6 +17,9 @@ #include "image_file.h" #include "image_service.h" #include "tools/comm_func.h" +#include "libtcmu/libtcmu.h" +#include "libtcmu/libtcmu_common.h" + #include #include #include @@ -25,10 +28,9 @@ #include #include #include -#include +#include +#include -#include -#include #include #include #include @@ -36,30 +38,32 @@ #include #include #include +#include +#include +#include +#include #include +#include class TCMUDevLoop; +class TCMUWorkPool; #define MAX_OPEN_FD 1048576 struct obd_dev { ImageFile *file; TCMUDevLoop *loop; - uint32_t aio_pending_wakeups; - uint32_t inflight; - std::thread *work; - photon::semaphore start, end; + std::atomic inflight{0}; + std::atomic completed{nullptr}; + photon::semaphore completion_sem{0}; + std::atomic completion_stopping{false}; std::string dev_id; }; -struct handle_args { - struct tcmu_device *dev; - struct tcmulib_cmd *cmd; -}; - class TCMULoop; TCMULoop *main_loop = nullptr; ImageService *imgservice = nullptr; +TCMUWorkPool *io_work_pool = nullptr; class TCMULoop { protected: @@ -149,6 +153,20 @@ ssize_t sure(SureIODelegate io, const struct iovec *iov, int iovcnt, off_t offse goto again; } +void complete_command(struct tcmu_device *dev, struct tcmulib_cmd *cmd, + int result) { + obd_dev *odev = (obd_dev *)tcmu_dev_get_private(dev); + cmd->completion_result = result; + + auto head = odev->completed.load(std::memory_order_relaxed); + do { + cmd->completion_next = head; + } while (!odev->completed.compare_exchange_weak( + head, cmd, std::memory_order_release, std::memory_order_relaxed)); + + odev->completion_sem.signal(1); +} + void cmd_handler(struct tcmu_device *dev, struct tcmulib_cmd *cmd) { obd_dev *odev = (obd_dev *)tcmu_dev_get_private(dev); ImageFile *file = odev->file; @@ -159,13 +177,13 @@ void cmd_handler(struct tcmu_device *dev, struct tcmulib_cmd *cmd) { case INQUIRY: photon::thread_yield(); ret = tcmu_emulate_inquiry(dev, NULL, cmd->cdb, cmd->iovec, cmd->iov_cnt); - tcmulib_command_complete(dev, cmd, ret); + complete_command(dev, cmd, ret); break; case TEST_UNIT_READY: photon::thread_yield(); ret = tcmu_emulate_test_unit_ready(cmd->cdb, cmd->iovec, cmd->iov_cnt); - tcmulib_command_complete(dev, cmd, ret); + complete_command(dev, cmd, ret); break; case SERVICE_ACTION_IN_16: @@ -175,21 +193,21 @@ void cmd_handler(struct tcmu_device *dev, struct tcmulib_cmd *cmd) { cmd->iovec, cmd->iov_cnt); else ret = TCMU_STS_NOT_HANDLED; - tcmulib_command_complete(dev, cmd, ret); + complete_command(dev, cmd, ret); break; case MODE_SENSE: case MODE_SENSE_10: photon::thread_yield(); ret = tcmu_emulate_mode_sense(dev, cmd->cdb, cmd->iovec, cmd->iov_cnt); - tcmulib_command_complete(dev, cmd, ret); + complete_command(dev, cmd, ret); break; case MODE_SELECT: case MODE_SELECT_10: photon::thread_yield(); ret = tcmu_emulate_mode_select(dev, cmd->cdb, cmd->iovec, cmd->iov_cnt); - tcmulib_command_complete(dev, cmd, ret); + complete_command(dev, cmd, ret); break; case READ_6: @@ -200,9 +218,9 @@ void cmd_handler(struct tcmu_device *dev, struct tcmulib_cmd *cmd) { ret = sure({file, &ImageFile::preadv}, cmd->iovec, cmd->iov_cnt, tcmu_cdb_to_byte(dev, cmd->cdb)); if (ret == length) { - tcmulib_command_complete(dev, cmd, TCMU_STS_OK); + complete_command(dev, cmd, TCMU_STS_OK); } else { - tcmulib_command_complete(dev, cmd, TCMU_STS_RD_ERR); + complete_command(dev, cmd, TCMU_STS_RD_ERR); } break; @@ -213,12 +231,12 @@ void cmd_handler(struct tcmu_device *dev, struct tcmulib_cmd *cmd) { length = tcmu_iovec_length(cmd->iovec, cmd->iov_cnt); ret = file->pwritev(cmd->iovec, cmd->iov_cnt, tcmu_cdb_to_byte(dev, cmd->cdb)); if (ret == length) { - tcmulib_command_complete(dev, cmd, TCMU_STS_OK); + complete_command(dev, cmd, TCMU_STS_OK); } else { if (errno == EROFS) { - tcmulib_command_complete(dev, cmd, TCMU_STS_WR_ERR_INCOMPAT_FRMT); + complete_command(dev, cmd, TCMU_STS_WR_ERR_INCOMPAT_FRMT); } else { - tcmulib_command_complete(dev, cmd, TCMU_STS_WR_ERR); + complete_command(dev, cmd, TCMU_STS_WR_ERR); } } break; @@ -227,9 +245,9 @@ void cmd_handler(struct tcmu_device *dev, struct tcmulib_cmd *cmd) { case SYNCHRONIZE_CACHE_16: ret = file->fdatasync(); if (ret == 0) { - tcmulib_command_complete(dev, cmd, TCMU_STS_OK); + complete_command(dev, cmd, TCMU_STS_OK); } else { - tcmulib_command_complete(dev, cmd, TCMU_STS_WR_ERR); + complete_command(dev, cmd, TCMU_STS_WR_ERR); } break; @@ -239,59 +257,83 @@ void cmd_handler(struct tcmu_device *dev, struct tcmulib_cmd *cmd) { length = tcmu_lba_to_byte(dev, tcmu_cdb_get_xfer_length(cmd->cdb)); ret = file->fallocate(3, tcmu_cdb_to_byte(dev, cmd->cdb), length); if (ret == 0) { - tcmulib_command_complete(dev, cmd, TCMU_STS_OK); + complete_command(dev, cmd, TCMU_STS_OK); } else { - tcmulib_command_complete(dev, cmd, TCMU_STS_WR_ERR); + complete_command(dev, cmd, TCMU_STS_WR_ERR); } } else { LOG_ERROR("unknown write_same command `", cmd->cdb[0]); - tcmulib_command_complete(dev, cmd, TCMU_STS_NOT_HANDLED); + complete_command(dev, cmd, TCMU_STS_NOT_HANDLED); } break; case MAINTENANCE_IN: case MAINTENANCE_OUT: - tcmulib_command_complete(dev, cmd, TCMU_STS_NOT_HANDLED); + complete_command(dev, cmd, TCMU_STS_NOT_HANDLED); break; default: LOG_ERROR("unknown command `", cmd->cdb[0]); - tcmulib_command_complete(dev, cmd, TCMU_STS_NOT_HANDLED); + complete_command(dev, cmd, TCMU_STS_NOT_HANDLED); break; } +} - // call tcmulib_processing_complete(dev) if needed - ++odev->aio_pending_wakeups; - int wake_up = (odev->aio_pending_wakeups == 1) ? 1 : 0; - while (wake_up) { - tcmulib_processing_complete(dev); - photon::thread_yield(); +class TCMUWorkPool : public photon::WorkPool { +public: + TCMUWorkPool(size_t vcpu_num, int event_engine, int io_engine) + : photon::WorkPool(vcpu_num, event_engine, io_engine, -1) {} + + void dispatch_commands(struct tcmu_device *dev, + const std::vector &commands, + bool device_idle) { + if (commands.empty()) + return; + + if (device_idle && commands.size() == 1) { + start_command(dev, commands.front()); + return; + } - if (odev->aio_pending_wakeups > 1) { - odev->aio_pending_wakeups = 1; - wake_up = 1; - } else { - odev->aio_pending_wakeups = 0; - wake_up = 0; + const auto worker_count = std::min( + commands.size(), static_cast(get_vcpu_num())); + for (size_t worker = 0; worker < worker_count; ++worker) { + // Let WorkPool choose the next VCPU with its global round-robin. + if (thread_migrate() != 0) + LOG_ERROR("failed to migrate TCMU dispatcher into WorkPool"); + for (size_t i = worker; i < commands.size(); i += worker_count) + start_command(dev, commands[i]); } + + if (migrate_to_home(photon::CURRENT, dev) != 0) + LOG_ERROR("failed to return TCMU dispatcher to its home VCPU"); } - odev->inflight--; -} + int migrate_to_home(photon::thread *thread, struct tcmu_device *dev) { + return thread_migrate(thread, home_worker(dev)); + } -void *handle(void *args) { - handle_args *obj = (handle_args *)args; - cmd_handler(obj->dev, obj->cmd); - delete obj; - return nullptr; -} +private: + static void start_command(struct tcmu_device *dev, + struct tcmulib_cmd *cmd) { + if (!photon::thread_create11(&cmd_handler, dev, cmd)) { + LOG_ERROR("failed to create Photon thread for TCMU command"); + cmd_handler(dev, cmd); + } + } + + size_t home_worker(struct tcmu_device *dev) { + return (reinterpret_cast(dev) >> 6) % get_vcpu_num(); + } +}; class TCMUDevLoop { protected: struct tcmu_device *dev; EventLoop *loop; + photon::join_handle *completion_thread; + std::vector pending_commands; int fd; - photon::ThreadPool<32> threadpool; int wait_for_readable(EventLoop *) { auto ret = photon::wait_for_fd_readable(fd); @@ -307,28 +349,98 @@ class TCMUDevLoop { int on_accept(EventLoop *) { struct tcmulib_cmd *cmd; obd_dev *odev = (obd_dev *)tcmu_dev_get_private(dev); + pending_commands.clear(); + bool device_idle = false; tcmulib_processing_start(dev); while ((cmd = tcmulib_get_next_command(dev, 0)) != NULL) { - odev->inflight++; - threadpool.thread_create(&handle, new handle_args{dev, cmd}); + auto prev = odev->inflight.fetch_add(1, std::memory_order_relaxed); + if (pending_commands.empty()) + device_idle = (prev == 0); + pending_commands.push_back(cmd); } + io_work_pool->dispatch_commands(dev, pending_commands, device_idle); return 0; } + void drain_completions() { + obd_dev *odev = (obd_dev *)tcmu_dev_get_private(dev); + auto commands = odev->completed.exchange(nullptr, std::memory_order_acquire); + if (!commands) + return; + + // The MPSC stack is LIFO. Reverse it to preserve the order in which + // workers published completions before updating the response ring. + struct tcmulib_cmd *ordered = nullptr; + while (commands) { + auto next = commands->completion_next; + commands->completion_next = ordered; + ordered = commands; + commands = next; + } + + uint32_t drained = 0; + while (ordered) { + auto next = ordered->completion_next; + tcmulib_command_complete(dev, ordered, ordered->completion_result); + ordered = next; + drained++; + } + tcmulib_processing_complete(dev); + odev->inflight.fetch_sub(drained, std::memory_order_release); + } + + void run_completion_loop() { + obd_dev *odev = (obd_dev *)tcmu_dev_get_private(dev); + for (;;) { + odev->completion_sem.wait(1); + + auto extra = odev->completion_sem.count(); + if (extra) + odev->completion_sem.wait(extra); + drain_completions(); + + if (odev->completion_stopping.load(std::memory_order_acquire) && + odev->inflight.load(std::memory_order_acquire) == 0) + return; + } + } + public: explicit TCMUDevLoop(struct tcmu_device *dev) : dev(dev), loop(new_event_loop({this, &TCMUDevLoop::wait_for_readable}, - {this, &TCMUDevLoop::on_accept})) { + {this, &TCMUDevLoop::on_accept})), + completion_thread(nullptr) { fd = tcmu_dev_get_fd(dev); } ~TCMUDevLoop() { loop->stop(); delete loop; + obd_dev *odev = (obd_dev *)tcmu_dev_get_private(dev); + while (odev->inflight.load(std::memory_order_acquire) != 0) { + photon::thread_usleep(1000); + } + odev->completion_stopping.store(true, std::memory_order_release); + odev->completion_sem.signal(1); + photon::thread_join(completion_thread); } void run() { + auto thread = photon::thread_create11(&TCMUDevLoop::run_completion_loop, this); + if (!thread) { + LOG_ERROR("failed to create TCMU completion thread"); + abort(); + } + completion_thread = photon::thread_enable_join(thread); + if (io_work_pool->migrate_to_home(thread, dev) != 0) { + LOG_ERROR("failed to migrate TCMU completion thread to WorkPool"); + abort(); + } loop->async_run(); + if (io_work_pool->migrate_to_home(loop->loop_thread(), dev) != 0) { + LOG_ERROR("failed to migrate TCMU accept loop to WorkPool"); + abort(); + } } }; @@ -361,8 +473,6 @@ static int dev_open(struct tcmu_device *dev) { } obd_dev *odev = new obd_dev; - odev->aio_pending_wakeups = 0; - odev->inflight = 0; odev->file = file; odev->dev_id = dev_id; @@ -373,27 +483,9 @@ static int dev_open(struct tcmu_device *dev) { tcmu_dev_set_write_cache_enabled(dev, false); tcmu_dev_set_write_protect_enabled(dev, file->read_only); - if (imgservice->global_conf.enableThread()) { - auto obd_th = [](obd_dev *odev, struct tcmu_device *dev) { - photon::init(photon::INIT_EVENT_EPOLL, photon::INIT_IO_LIBCURL); - DEFER(photon::fini()); - - odev->loop = new TCMUDevLoop(dev); - odev->loop->run(); - LOG_INFO("obd device running"); - odev->start.signal(1); - - odev->end.wait(1); - delete odev->loop; - LOG_INFO("obd device exit"); - }; - - odev->work = new std::thread(obd_th, odev, dev); - odev->start.wait(1); - } else { - odev->loop = new TCMUDevLoop(dev); - odev->loop->run(); - } + odev->loop = new TCMUDevLoop(dev); + odev->loop->run(); + LOG_INFO("obd device running"); struct timeval end; gettimeofday(&end, NULL); @@ -406,15 +498,7 @@ static int dev_open(struct tcmu_device *dev) { static int close_cnt = 0; static void dev_close(struct tcmu_device *dev) { obd_dev *odev = (obd_dev *)tcmu_dev_get_private(dev); - if (imgservice->global_conf.enableThread()) { - odev->end.signal(1); - if (odev->work->joinable()) { - odev->work->join(); - } - delete odev->work; - } else { - delete odev->loop; - } + delete odev->loop; delete odev->file; delete odev; LOG_INFO("dev closed `", tcmu_get_path(dev)); @@ -438,7 +522,9 @@ int main(int argc, char **argv) { mallopt(M_TRIM_THRESHOLD, 128 * 1024); prctl(PR_SET_THP_DISABLE, 1); - photon::init(photon::INIT_EVENT_DEFAULT, photon::INIT_IO_DEFAULT); + photon::PhotonOptions photon_options; + photon_options.use_pooled_stack_allocator = true; + photon::init(photon::INIT_EVENT_DEFAULT, photon::INIT_IO_DEFAULT, photon_options); photon::block_all_signal(); photon::sync_signal(SIGTERM, &sigint_handler); photon::sync_signal(SIGINT, &sigint_handler); @@ -451,6 +537,17 @@ int main(int argc, char **argv) { return -1; } + std::unique_ptr work_pool; + auto pool_size = imgservice->global_conf.workpoolSize(); + if (pool_size == 0) { + LOG_WARN("workpoolSize is zero, using one worker"); + pool_size = 1; + } + work_pool.reset(new TCMUWorkPool(pool_size, photon::INIT_EVENT_EPOLL, + photon::INIT_IO_LIBCURL)); + io_work_pool = work_pool.get(); + LOG_INFO("I/O work pool initialized with ` workers", pool_size); + /* * Handings for rlimit and netlink are from tcmu-runner main.c */ @@ -524,6 +621,8 @@ int main(int argc, char **argv) { tcmulib_close(tcmulib_ctx); LOG_INFO("tcmulib closed"); + io_work_pool = nullptr; + work_pool.reset(); delete imgservice; return 0; } From 4e7fd2f4f1de58026788207482767fb8cc128cc1 Mon Sep 17 00:00:00 2001 From: Lanzheng Liu Date: Fri, 28 Aug 2026 10:23:29 +0800 Subject: [PATCH 4/9] build: stabilize release builds across distributions Signed-off-by: Lanzheng Liu --- .github/workflows/release/build.sh | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release/build.sh b/.github/workflows/release/build.sh index 0e39dc25..febffd16 100644 --- a/.github/workflows/release/build.sh +++ b/.github/workflows/release/build.sh @@ -26,6 +26,15 @@ PACKAGE_RELEASE="" CMAKE="cmake" CPACK="cpack" +retry_tdnf() { + local attempt + for attempt in 1 2 3; do + tdnf "$@" && return 0 + tdnf clean all + done + return 1 +} + # Install Dependencies if [[ ${OS} =~ "ubuntu" ]]; then @@ -35,6 +44,17 @@ if [[ ${OS} =~ "ubuntu" ]]; then apt-get install -y libgflags-dev libcurl4-openssl-dev libssl-dev libaio-dev libnl-3-dev libnl-genl-3-dev rpm wget make g++ git dpkg-dev sudo pkg-config apt-get install -y uuid-dev libjson-c-dev libkmod-dev libsystemd-dev autoconf automake libtool libpci-dev nasm libzstd-dev libext2fs-dev zlib1g-dev + if [[ ${OS} == "ubuntu:18.04" ]]; then + apt-get install -y software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test + apt-get update -y + apt-get install -y gcc-9 g++-9 + COMPILER="-DCMAKE_C_COMPILER=/usr/bin/gcc-9 -DCMAKE_CXX_COMPILER=/usr/bin/g++-9" + elif [[ ${OS} == "ubuntu:20.04" ]]; then + apt-get install -y gcc-9 g++-9 + COMPILER="-DCMAKE_C_COMPILER=/usr/bin/gcc-9 -DCMAKE_CXX_COMPILER=/usr/bin/g++-9" + fi + DISTRO=${OS/:/1~} PACKAGE_RELEASE="-DPACKAGE_RELEASE=${RELEASE_NO}.${DISTRO}" elif [[ ${OS} =~ "centos" ]]; then @@ -82,7 +102,7 @@ elif [[ ${OS} =~ "mariner" ]]; then DISTRO=${OS/:/.} PACKAGE_RELEASE="-DPACKAGE_RELEASE=${RELEASE_NO}.${DISTRO}" elif [[ ${OS} =~ "azurelinux" ]]; then - tdnf update -y + retry_tdnf update -y || exit 1 if [[ ${OS} =~ "azurelinux:4" ]]; then # Azure Linux 4.0 removed the `ca-certificates-microsoft` and # `build-essential` packages that AL3 ships, and no longer pulls @@ -90,8 +110,8 @@ elif [[ ${OS} =~ "azurelinux" ]]; then # explicitly. (gcc/gcc-c++/make/binutils/glibc-devel/autoconf/ # automake/libtool are already installed on the next line, which is # everything `build-essential` provided.) - tdnf install -y libaio-devel libcurl-devel openssl-devel libnl3-devel e2fsprogs-devel glibc-devel libzstd-devel binutils ca-certificates zlib-devel - tdnf install -y rpm-build make git wget sudo tar gcc gcc-c++ autoconf automake libtool pkg-config + retry_tdnf install -y libaio-devel libcurl-devel openssl-devel libnl3-devel e2fsprogs-devel glibc-devel libzstd-devel binutils ca-certificates zlib-devel || exit 1 + retry_tdnf install -y rpm-build make git wget sudo tar gcc gcc-c++ autoconf automake libtool pkg-config || exit 1 # Azure Linux 4's rpmbuild runs check-rpaths which rejects the # /opt/overlaybd/lib RPATH baked into our binaries (they link against @@ -102,8 +122,8 @@ elif [[ ${OS} =~ "azurelinux" ]]; then export QA_RPATHS=0x0002 else # Azure Linux 3.0 (and earlier) -- unchanged from the original recipe. - tdnf install -y libaio-devel libcurl-devel openssl-devel libnl3-devel e2fsprogs-devel glibc-devel libzstd-devel binutils ca-certificates-microsoft build-essential - tdnf install -y rpm-build make git wget sudo tar gcc gcc-c++ autoconf automake libtool + retry_tdnf install -y libaio-devel libcurl-devel openssl-devel libnl3-devel e2fsprogs-devel glibc-devel libzstd-devel binutils ca-certificates-microsoft build-essential || exit 1 + retry_tdnf install -y rpm-build make git wget sudo tar gcc gcc-c++ autoconf automake libtool || exit 1 fi DISTRO=${OS/:/.} From 1bc83a8b1f359b920d4ba5f26f29ecf5ba2b8bc6 Mon Sep 17 00:00:00 2001 From: Lanzheng Liu Date: Fri, 28 Aug 2026 10:23:29 +0800 Subject: [PATCH 5/9] ci: enforce concurrent OverlayBD file-cache performance Signed-off-by: Lanzheng Liu --- .github/workflows/cmake.yml | 73 ++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index c72c8c6d..147c7b7c 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -26,8 +26,8 @@ jobs: run: | sudo apt-get update -y sudo apt-get install -y libgflags-dev libcurl4-openssl-dev libssl-dev libaio-dev libnl-3-dev libnl-genl-3-dev libzstd-dev - sudo apt-get install -y uuid-dev libjson-c-dev libkmod-dev libsystemd-dev autoconf automake libtool libpci-dev nasm - sudo apt-get install -y libext2fs-dev + sudo apt-get install -y uuid-dev libjson-c-dev libkmod-dev libsystemd-dev autoconf automake libtool libpci-dev nasm pkg-config help2man + sudo apt-get install -y libext2fs-dev fio jq lsscsi wget https://github.com/google/googletest/archive/refs/tags/release-1.11.0.tar.gz tar -zxvf release-1.11.0.tar.gz cd googletest-release-1.11.0/ @@ -57,6 +57,9 @@ jobs: lscpu sudo make install sudo cp ${{github.workspace}}/src/example_config/overlaybd-registryv2.json /etc/overlaybd/overlaybd.json + grep -Eq '"cacheType"[[:space:]]*:[[:space:]]*"file"' /etc/overlaybd/overlaybd.json + sudo install -d -m 0755 /opt/overlaybd/registry_cache + sudo find /opt/overlaybd/registry_cache -mindepth 1 -delete sudo systemctl enable /opt/overlaybd/overlaybd-tcmu.service sudo systemctl start overlaybd-tcmu sudo systemctl status overlaybd-tcmu @@ -68,12 +71,62 @@ jobs: echo -n "naa.123456789abcdef" | sudo tee /sys/kernel/config/target/loopback/naa.123456789abcdef/tpgt_1/nexus sudo ln -s /sys/kernel/config/target/core/user_1/vol1 /sys/kernel/config/target/loopback/naa.123456789abcdef/tpgt_1/lun/lun_0/vol1 lsblk - sudo apt-get install -y lsscsi - dev=`lsscsi | grep TCMU | awk '{print $7}'` - echo $dev + dev=$(lsscsi | awk '/TCMU/ {print $7; exit}') + test -b "$dev" + echo "$dev" mkdir obd_mp - sudo mount -o ro $dev obd_mp + sudo mount -o ro "$dev" obd_mp ls obd_mp + + # Exercise OverlayBD while the filesystem is mounted. direct=1 opens the + # raw block device with O_DIRECT, so Linux's block/page cache cannot + # satisfy these reads before they reach OverlayBD. + cache_blocks_before=$(sudo find /opt/overlaybd/registry_cache -type f -printf '%b\n' | awk '{blocks += $1} END {print blocks + 0}') + sudo fio \ + --name=overlaybd-file-cache-fill \ + --filename="$dev" \ + --readonly \ + --direct=1 \ + --ioengine=libaio \ + --rw=randread \ + --bs=4k \ + --iodepth=32 \ + --numjobs=8 \ + --time_based=1 \ + --runtime=20 \ + --randrepeat=1 \ + --randseed=20260821 \ + --group_reporting=1 + cache_blocks_after=$(sudo find /opt/overlaybd/registry_cache -type f -printf '%b\n' | awk '{blocks += $1} END {print blocks + 0}') + test "$cache_blocks_after" -gt "$cache_blocks_before" + + # Repeat the same high-concurrency direct-I/O workload so the requests + # traverse OverlayBD again and hit the file cache populated above. + fio_result="$RUNNER_TEMP/overlaybd-file-cache-hit.json" + sudo fio \ + --name=overlaybd-file-cache-hit \ + --filename="$dev" \ + --readonly \ + --direct=1 \ + --ioengine=libaio \ + --rw=randread \ + --bs=4k \ + --iodepth=32 \ + --numjobs=8 \ + --time_based=1 \ + --runtime=20 \ + --randrepeat=1 \ + --randseed=20260821 \ + --group_reporting=1 \ + --output-format=json \ + --output="$fio_result" + iops=$(jq -r '.jobs[0].read.iops | floor' "$fio_result") + echo "OverlayBD file-cache random-read IOPS: $iops" + if ((iops < 110000)); then + echo "::error title=OverlayBD IOPS regression::Expected at least 110000 IOPS, got $iops" + exit 1 + fi + sudo systemctl is-active --quiet overlaybd-tcmu sudo umount obd_mp ls -l /sys/kernel/config/target/loopback/naa.123456789abcdef/tpgt_1/lun/lun_0/vol1 sudo unlink /sys/kernel/config/target/loopback/naa.123456789abcdef/tpgt_1/lun/lun_0/vol1 @@ -111,11 +164,11 @@ jobs: echo -n "naa.987654321abcdef" | sudo tee /sys/kernel/config/target/loopback/naa.987654321abcdef/tpgt_1/nexus sudo ln -s /sys/kernel/config/target/core/user_1/vol2 /sys/kernel/config/target/loopback/naa.987654321abcdef/tpgt_1/lun/lun_0/vol2 lsblk - sudo apt-get install -y lsscsi - dev=`lsscsi | grep TCMU | awk '{print $7}'` - echo $dev + dev=$(lsscsi | awk '/TCMU/ {print $7; exit}') + test -b "$dev" + echo "$dev" sudo mkdir foci_mp - sudo mount -o ro $dev foci_mp + sudo mount -o ro "$dev" foci_mp ls foci_mp sudo umount foci_mp sudo unlink /sys/kernel/config/target/loopback/naa.987654321abcdef/tpgt_1/lun/lun_0/vol2 From f30c8c03ca546d3b9af7509121bbacd8f7f8265b Mon Sep 17 00:00:00 2001 From: Lanzheng Liu Date: Fri, 28 Aug 2026 10:23:29 +0800 Subject: [PATCH 6/9] chore: ignore macOS metadata Signed-off-by: Lanzheng Liu --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 74b2c70b..bdcf000d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ releases .vscode .idea deps/ +.DS_Store From 063fea778765552dad5dff6eaf981c3ca6de65c4 Mon Sep 17 00:00:00 2001 From: Lanzheng Liu Date: Fri, 28 Aug 2026 10:23:44 +0800 Subject: [PATCH 7/9] ci: benchmark TCMU WorkPool performance Signed-off-by: Lanzheng Liu --- .github/scripts/summarize-tcmu-performance.py | 115 +++++++++ .github/scripts/tcmu-performance.sh | 223 ++++++++++++++++++ .github/workflows/tcmu-performance.yml | 145 ++++++++++++ 3 files changed, 483 insertions(+) create mode 100644 .github/scripts/summarize-tcmu-performance.py create mode 100644 .github/scripts/tcmu-performance.sh create mode 100644 .github/workflows/tcmu-performance.yml diff --git a/.github/scripts/summarize-tcmu-performance.py b/.github/scripts/summarize-tcmu-performance.py new file mode 100644 index 00000000..4ff0aece --- /dev/null +++ b/.github/scripts/summarize-tcmu-performance.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 + +import csv +import json +import os +import re +import statistics +import sys +from pathlib import Path + + +def load_runs(result_dir: Path): + rows = [] + for path in sorted(result_dir.glob("*.json")): + match = re.fullmatch(r"(baseline|current)-(.+)-(\d+)", path.stem) + if not match: + continue + label, profile, run = match.groups() + with path.open() as file: + job = json.load(file)["jobs"][0]["read"] + rows.append( + { + "label": label, + "profile": profile, + "run": int(run), + "iops": float(job["iops"]), + "bw_bytes": float(job["bw_bytes"]), + "clat_us": float(job["clat_ns"]["mean"]) / 1000, + } + ) + return rows + + +def median(rows, label, profile, metric): + return statistics.median( + row[metric] + for row in rows + if row["label"] == label and row["profile"] == profile + ) + + +def delta(current, baseline): + return (current / baseline - 1) * 100 + + +result_dir = Path(sys.argv[1]) +result_dir.mkdir(parents=True, exist_ok=True) +rows = load_runs(result_dir) +profiles = ("qd1-j1", "qd32-j8") +minimum_iops_ratio = { + "qd1-j1": 0.90, + "qd32-j8": 3.50, +} +failures = [] + +if rows: + with (result_dir / "raw-results.csv").open("w", newline="") as file: + writer = csv.DictWriter(file, fieldnames=rows[0].keys()) + writer.writeheader() + writer.writerows(rows) + +lines = [ + "# OverlayBD TCMU performance comparison", + "", + f"- Baseline: `{os.getenv('BASELINE_SHA', 'unknown')}` with its checked-in configuration", + f"- Current: `{os.getenv('GITHUB_SHA', 'working tree')}` with " + "`workpoolSize=8` and batched dispatcher migration across WorkPool vCPUs", + f"- Build type: `{os.getenv('BUILD_TYPE', 'unknown')}` for both versions", + "- I/O: 4 KiB random reads, `libaio`, `O_DIRECT`; profile names encode queue depth and job count", + "- Required current/baseline IOPS: `qd1-j1 >= 90%`, `qd32-j8 >= 350%`", + "", + "Both versions ran sequentially on the same GitHub runner. Values are the median of three 15-second fio runs after a 45-second cache warmup.", + "", + "| fio profile | baseline IOPS | current IOPS | baseline ratio | IOPS change | baseline latency | current latency | latency change |", + "| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |", +] +for profile in profiles: + baseline_runs = [row for row in rows if row["label"] == "baseline" and row["profile"] == profile] + current_runs = [row for row in rows if row["label"] == "current" and row["profile"] == profile] + if baseline_runs and current_runs: + baseline_iops = median(rows, "baseline", profile, "iops") + current_iops = median(rows, "current", profile, "iops") + baseline_lat = median(rows, "baseline", profile, "clat_us") + current_lat = median(rows, "current", profile, "clat_us") + iops_ratio = current_iops / baseline_iops + lines.append( + f"| `{profile}` | {baseline_iops:,.0f} | {current_iops:,.0f} | " + f"{iops_ratio:.1%} | {delta(current_iops, baseline_iops):+.1f}% | " + f"{baseline_lat:,.1f} us | " + f"{current_lat:,.1f} us | {delta(current_lat, baseline_lat):+.1f}% |" + ) + if iops_ratio < minimum_iops_ratio[profile]: + failures.append( + f"{profile} current/baseline IOPS is {iops_ratio:.1%}; " + f"required >= {minimum_iops_ratio[profile]:.0%}" + ) + else: + baseline_iops = f"{median(rows, 'baseline', profile, 'iops'):,.0f}" if baseline_runs else "timeout/no data" + current_iops = f"{median(rows, 'current', profile, 'iops'):,.0f}" if current_runs else "timeout/no data" + baseline_lat = f"{median(rows, 'baseline', profile, 'clat_us'):,.1f} us" if baseline_runs else "—" + current_lat = f"{median(rows, 'current', profile, 'clat_us'):,.1f} us" if current_runs else "—" + lines.append( + f"| `{profile}` | {baseline_iops} | {current_iops} | — | — | " + f"{baseline_lat} | {current_lat} | — |" + ) + failures.append(f"{profile} has incomplete benchmark data") + +summary = "\n".join(lines) + "\n" +(result_dir / "summary.md").write_text(summary) +print(summary) + +if failures: + for failure in failures: + print(f"::error title=TCMU performance regression::{failure}") + sys.exit(1) diff --git a/.github/scripts/tcmu-performance.sh b/.github/scripts/tcmu-performance.sh new file mode 100644 index 00000000..64e4fc5f --- /dev/null +++ b/.github/scripts/tcmu-performance.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +if [[ $# -ne 5 ]]; then + echo "usage: $0