diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..1fb97fc --- /dev/null +++ b/.clang-format @@ -0,0 +1,127 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +... + diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..4f80d14 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,30 @@ +--- +Checks: [ + '-*', + 'clang-diagnostic-*', + 'clang-analyzer-*', + 'bugprone-*', + 'concurrency-*', + 'cppcoreguidelines-*', + 'modernize-*', + 'readability-*', + '-readability-redundant-access-specifiers', + '-readability-function-cognitive-complexity' + ] +ExtraArgs: ['-Wno-unknown-warning-option'] +FormatStyle: none +HeaderFileExtensions: ['', 'h','hh','hpp','hxx'] +HeaderFilterRegex: '' +ImplementationFileExtensions: ['c','cc','cpp','cxx'] +CheckOptions: + - key: MinimumExceptionNameLength + value: 1 + - key: MinimumLoopCounterNameLength + value: 3 + - key: MinimumParameterNameLength + value: 3 + - key: MinimumVariableNameLength + value: 3 +User: 'scott.e.graves@protonmail.com' +WarningsAsErrors: '' +... diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..37b844d --- /dev/null +++ b/.clangd @@ -0,0 +1,5 @@ +{ + "CompileFlags": { + "Remove": ["-flarge-source-files", "-fext-numeric-literals"] + } +} diff --git a/.cspell/words.txt b/.cspell/words.txt new file mode 100644 index 0000000..ee530b0 --- /dev/null +++ b/.cspell/words.txt @@ -0,0 +1,225 @@ +aarch64 +advapi32 +armv8 +autogen +backwardcpp_project +bcrypt +binutils_version +bitcoinsystem +boost_asio_has_std_string_view +bugprone +cflags +chrono +cmake_current_source_dir +cmake_host_win32 +comdlg32 +cpp_httplib_hash +cppcoreguidelines +cppdbg +cppflags +cpphttplib +cppvsdbg +crypto_aead_xchacha20poly1305_ietf_npubbytes +cstdint +cxxflags +cxxopts_project +cxxstd +d_largefile64_source +d_largefile_source +d_ndebug +dbackward_has_dwarf +dbackward_shared +dbghelp +dbinaries +dboost_root +dbuild_shared_libs +dcli11_warnings_as_errors +dcmake_cxx_flags +dcpphttplib_openssl_support +dcpphttplib_tcp_nodelay +dcurl_ca_bundle +dcurl_ca_fallback +dcurl_disable_ldap +dcurl_staticlib +dcurl_use_libpsl +dcurl_use_libssh2 +dcurl_zlib +dcxxopts_build_examples +dcxxopts_build_tests +dcxxopts_enable_install +dcxxopts_enable_warnings +dcxxopts_use_unicode_help +ddebug +ddefault_library +decmult_gen_prec_bits +decmult_window_size +degl +denable_threaded_resolver +deps +devent__disable_openssl +devent__disable_tests +devent__library_type +dfail_on_warnings +dfuse_use_version +dgles1 +dgles2 +dhas_setxattr +dhttplib_require_brotli +dhttplib_require_openssl +dhttplib_require_zlib +dinstall_manpages +djson_buildtests +dnana_cmake_enable_audio +dnana_cmake_enable_jpeg +dnana_cmake_enable_png +dnana_cmake_install +dndebug +dopengl +dopenssl_root_dir +dopenssl_use_static_libs +dpng_shared +dpng_static +dpng_tests +dpng_tools +dportable +dproject_enable_backward_cpp +dproject_static_link +drocksdb_install_on_windows +dshared +dspdlog_fmt_external +dthreads_prefer_pthread_flag +duse_libidn2 +duuid_build_tests +dwith_benchmark +dwith_gflags +dwith_iostats_context +dwxuse_opengl +dzlib_use_static_libs +ecdh +endforeach +endfunction +eventlib +fext +fifthgrid +flac_version +flarge +fontconfig_version +freetype2_version +glapi +gmock +googletest +gpath +gtest_version +has_setxattr +httpapi +httplib +icudata +icui18n +icuuc +iostreams +iphlpapi +istream_reader +libbitcoin +libbitcoinsystem +libcurl +libevent +libexample +libfuse3 +libgmock +libgtest +libiconv_version +libjpeg +libmonitarr +libpcre2_version +libpng_project +librocksdb +libsecp256k1 +libsfml +libsodium_type +libspdlog +libuuid +libuuid_include_dirs +libvlc_include_dirs +libvlc_libraries +libvlccore +libvorbis +linkflags +mbig +monitarr +msvc +msvcr120 +msvcr90 +mswsock +mtune +mwindows +nana +ncrypt +nlohmann_json +nmakeprg +nominmax +nuspell_version +oleaut32 +openal_version +openssldir +ostream_writer +pkgconfig +println +project_enable_cxxopts +project_enable_fontconfig +project_enable_gtkmm +project_enable_libdsm +project_enable_libiconv +project_enable_libtasn1 +project_enable_winfsp +propgrid +pugi +pugixml_project +richtext +rocksdb_library +rpcrt4 +sdl2_no_mwindows +secp256k1 +secur32 +sfml_project +shlwapi +smb_stat_wtime +source_subdir +spdlog +spdlog_project +st_ctim +static-libgcc +static-libstdc++ +stduuid_project +stod +strequal +uring +userenv +vorbis_version +wall +wcast-align +wconversion +wdouble-promotion +wduplicated-branches +wduplicated-cond +wextra +wformat=2 +win32_find_dataa +winfsp +winhttp +wininet +winspool +wlogical-op +wmisleading-indentation +wno-useless-cast +wnon-virtual-dtor +wnull-dereference +wold-style-cast +woverloaded-virtual +wpedantic +wserialization +wshadow +wsign-conversion +wunused +wxwidgets_version +xattr +xcursor \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..486ec65 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.cache/ +.vs/ +build*/ +compile_commands.json +cspell.json +deps/ +dist/ +override.sh +scripts/cleanup.cmd +scripts/cleanup.sh +support/Dockerfile +version.cpp +version.rc diff --git a/.jenkins_builds b/.jenkins_builds new file mode 100644 index 0000000..97f6e38 --- /dev/null +++ b/.jenkins_builds @@ -0,0 +1,69 @@ +#!groovy + +pipeline { + agent none + + environment { + PROJECT_TEST_CONFIG_DIR = "/.ci/monitarr/test_config" + } + + options { + disableConcurrentBuilds() + retry(2) + } + + stages { + stage('linux_x86_64') { + agent any + + steps { + retry(2) { + sleep time: 5, unit: 'SECONDS' + sh 'scripts/make_unix.sh' + } + } + } + + stage('mingw64') { + agent any + + steps { + retry(2) { + sleep time: 5, unit: 'SECONDS' + sh 'scripts/make_win32.sh' + } + } + } + + stage('linux_aarch64') { + agent any + + steps { + retry(2) { + sleep time: 5, unit: 'SECONDS' + sh 'scripts/make_unix.sh aarch64' + } + } + } + + stage('test') { + agent any + + steps { + sh 'scripts/test.sh' + sh 'scripts/test.sh "" "" "" "" 1 1' + sh 'scripts/test.sh aarch64' + } + } + + stage('deliver') { + agent any + + steps { + sh 'scripts/deliver.sh /mnt/monitarr "" "" "" "" 1 1' + sh 'scripts/deliver.sh /mnt/monitarr "" aarch64' + sh 'scripts/deliver.sh /mnt/monitarr' + } + } + } +} diff --git a/.nvimrc b/.nvimrc new file mode 100644 index 0000000..b6f0cb0 --- /dev/null +++ b/.nvimrc @@ -0,0 +1,47 @@ +if has('win32') || has('win64') + let &makeprg=".\\scripts\\make_win32.cmd" + let g:nmakeprg=".\\scripts\\make_win32.cmd" + " let g:gtest#gtest_command = "cd build2 && .\\unittests" +else + let &makeprg="./scripts/make_unix.sh" + let g:nmakeprg="./scripts/make_unix.sh" + " let g:gtest#gtest_command = "cd build && ./unittests" +endif +set path+=.,monitarr/** + +lua << EOF +if vim.env.NV_DARCULA_ENABLE_DAP then + local dap = require("dap") + local g = require("nvim-goodies") + local gos = require("nvim-goodies.os") + local gpath = require("nvim-goodies.path") + + local externalConsole = gos.is_windows + local type = "cppdbg" + local cwd = gpath.create_path("./build") + dap.configurations.cpp = { + { + name = "Main", + type = type, + request = "launch", + program = function() + return gpath.create_path(cwd, "monitarr") + end, + cwd = cwd, + stopAtEntry = true, + externalConsole=externalConsole, + }, + { + name = "Test", + type = type, + request = "launch", + program = function() + return gpath.create_path(cwd, "monitarr_tests") + end, + cwd = cwd, + stopAtEntry = true, + externalConsole=externalConsole, + } + } +end +EOF diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1b80e84 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,177 @@ +cmake_minimum_required(VERSION 3.27) + +cmake_policy(SET CMP0135 NEW) +cmake_policy(SET CMP0144 NEW) + +if (NOT PROJECT_INTERFACE) + message(FATAL_ERROR "Project must be compiled via 'make_win32.cmd'/'make_win32.sh' or 'make_unix.sh' build scripts. Invoking 'cmake' directly is not supported.") +endif() + +project(${PROJECT_NAME} + DESCRIPTION ${PROJECT_DESC} + HOMEPAGE_URL ${PROJECT_URL} + VERSION ${PROJECT_MAJOR_VERSION}.${PROJECT_MINOR_VERSION}.${PROJECT_REVISION_VERSION} +) + +include(CheckIncludeFileCXX) +include(CheckIncludeFiles) +include(ExternalProject) + +check_include_files(sys/xattr.h HAS_SETXATTR) +if(HAS_SETXATTR) + add_definitions(-DHAS_SETXATTR) +endif() + +include(cmake/hashes.cmake) + +include(cmake/versions.cmake) +include(cmake/arch.cmake) +include(cmake/os.cmake) +include(cmake/options.cmake) + +option(PROJECT_REQUIRE_ALPINE "Require Alpine Linux as build system" OFF) +option(PROJECT_STATIC_LINK "Static link executables" OFF) + +if(PROJECT_STATIC_LINK) + add_definitions(-DPROJECT_STATIC_LINK) +endif() + +if(PROJECT_IS_MINGW) + add_definitions(-DPROJECT_IS_MINGW) + if(PROJECT_IS_MINGW_UNIX) + add_definitions(-DPROJECT_IS_MINGW_UNIX) + endif() +endif() + +if(PROJECT_REQUIRE_ALPINE) + add_definitions(-DPROJECT_REQUIRE_ALPINE) +endif() + +if(PROJECT_IS_ARM64) + add_definitions(-DPROJECT_IS_ARM64) +endif() + +if(PROJECT_IS_MINGW) + option(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES "Enable path sizes of 32767 characters on Windows" OFF) + if(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) + add_definitions(-DPROJECT_ENABLE_WIN32_LONG_PATH_NAMES) + endif() +endif() + +include(cmake/settings.cmake) + +include(cmake/flags.cmake) +include(cmake/functions.cmake) + +include(cmake/libraries.cmake) + +if(PROJECT_BUILD) + file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/version.cpp "") + + if (PROJECT_IS_MINGW) + set(PROJECT_WINDOWS_VERSION_RC ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/version.rc) + file(WRITE ${PROJECT_WINDOWS_VERSION_RC} "") + endif() + + file(GLOB_RECURSE ADDITIONAL_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/version.cpp + ${PROJECT_SUPPORT_DIR}/src/*.c + ${PROJECT_SUPPORT_DIR}/src/*.cpp + ) + + list(APPEND PROJECT_ADDITIONAL_SOURCES + ${ADDITIONAL_SOURCES} + ) + + include(project.cmake) + + string(REPLACE "\\<" "<" PROJECT_COMPANY_NAME "${PROJECT_COMPANY_NAME}") + string(REPLACE "\\<" "<" PROJECT_COPYRIGHT "${PROJECT_COPYRIGHT}") + string(REPLACE "\\<" "<" PROJECT_DESC "${PROJECT_DESC}") + string(REPLACE "\\>" ">" PROJECT_COMPANY_NAME "${PROJECT_COMPANY_NAME}") + string(REPLACE "\\>" ">" PROJECT_COPYRIGHT "${PROJECT_COPYRIGHT}") + string(REPLACE "\\>" ">" PROJECT_DESC "${PROJECT_DESC}") + + if (PROJECT_IS_MINGW) + if ("${PROJECT_RELEASE_ITER}" STREQUAL "release") + set(PROJECT_PRERELEASE 0) + else() + set(PROJECT_PRERELEASE VS_FF_PRERELEASE) + endif() + + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/version.rc.in + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/version.rc + @ONLY + ) + endif() + + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/version.cpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/version.cpp + @ONLY + ) +else() + message(STATUS "-=[CMake Settings]=-") + message(STATUS " C standard: ${CMAKE_C_STANDARD}") + message(STATUS " C++ standard: ${CMAKE_CXX_STANDARD}") + message(STATUS " CPU architecture: ${PROJECT_MARCH}") +if(PROJECT_ENABLE_FUSE) + message(STATUS " FUSE version: ${PROJECT_FUSE}") +endif() + + string(REPLACE "<" "\\<" PROJECT_COMPANY_NAME "${PROJECT_COMPANY_NAME}") + string(REPLACE "<" "\\<" PROJECT_COPYRIGHT "${PROJECT_COPYRIGHT}") + string(REPLACE "<" "\\<" PROJECT_DESC "${PROJECT_DESC}") + string(REPLACE ">" "\\>" PROJECT_COMPANY_NAME "${PROJECT_COMPANY_NAME}") + string(REPLACE ">" "\\>" PROJECT_COPYRIGHT "${PROJECT_COPYRIGHT}") + string(REPLACE ">" "\\>" PROJECT_DESC "${PROJECT_DESC}") + + add_custom_target(project ALL + DEPENDS ${PROJECT_DEPENDENCIES} + COMMAND cd build && cmake + ${CMAKE_CURRENT_SOURCE_DIR} + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + -DPROJECT_3RD_PARTY_DIR=${PROJECT_3RD_PARTY_DIR} + -DPROJECT_BUILD=ON + -DPROJECT_BUILD_ARCH=${PROJECT_BUILD_ARCH} + -DPROJECT_BUILD_DIR=${PROJECT_BUILD_DIR} + -DPROJECT_BUILD_SHARED_LIBS=${PROJECT_BUILD_SHARED_LIBS} + -DPROJECT_CMAKE_BUILD_TYPE=${PROJECT_CMAKE_BUILD_TYPE} + -DPROJECT_COMPANY_NAME=${PROJECT_COMPANY_NAME} + -DPROJECT_COPYRIGHT=${PROJECT_COPYRIGHT} + -DPROJECT_DESC=${PROJECT_DESC} + -DPROJECT_DIST_DIR=${PROJECT_DIST_DIR} + -DPROJECT_ENABLE_WIN32_LONG_PATH_NAMES=${PROJECT_ENABLE_WIN32_LONG_PATH_NAMES} + -DPROJECT_ENABLE_BACKWARD_CPP=${PROJECT_ENABLE_BACKWARD_CPP} + -DPROJECT_ENABLE_CPP_HTTPLIB=${PROJECT_ENABLE_CPP_HTTPLIB} + -DPROJECT_ENABLE_CURL=${PROJECT_ENABLE_CURL} + -DPROJECT_ENABLE_JSON=${PROJECT_ENABLE_JSON} + -DPROJECT_ENABLE_OPENSSL=${PROJECT_ENABLE_OPENSSL} + -DPROJECT_ENABLE_ROCKSDB=${PROJECT_ENABLE_ROCKSDB} + -DPROJECT_ENABLE_SPDLOG=${PROJECT_ENABLE_SPDLOG} + -DPROJECT_ENABLE_TESTING=${PROJECT_ENABLE_TESTING} + -DPROJECT_EXTERNAL_BUILD_ROOT=${PROJECT_EXTERNAL_BUILD_ROOT} + -DPROJECT_FUSE=${PROJECT_FUSE} + -DPROJECT_FUSE_INCLUDE_DIRS=${PROJECT_FUSE_INCLUDE_DIRS} + -DPROJECT_GIT_REV=${PROJECT_GIT_REV} + -DPROJECT_INTERFACE=1 + -DPROJECT_IS_ALPINE=${PROJECT_IS_ALPINE} + -DPROJECT_IS_ARM64=${PROJECT_IS_ARM64} + -DPROJECT_IS_MINGW=${PROJECT_IS_MINGW} + -DPROJECT_IS_MINGW_UNIX=${PROJECT_IS_MINGW_UNIX} + -DPROJECT_MAJOR_VERSION=${PROJECT_MAJOR_VERSION} + -DPROJECT_MINOR_VERSION=${PROJECT_MINOR_VERSION} + -DPROJECT_NAME=${PROJECT_NAME} + -DPROJECT_RELEASE_ITER=${PROJECT_RELEASE_ITER} + -DPROJECT_RELEASE_NUM=${PROJECT_RELEASE_NUM} + -DPROJECT_REQUIRE_ALPINE=${PROJECT_REQUIRE_ALPINE} + -DPROJECT_REVISION_VERSION=${PROJECT_REVISION_VERSION} + -DPROJECT_STATIC_LINK=${PROJECT_STATIC_LINK} + -DPROJECT_SUPPORT_DIR=${PROJECT_SUPPORT_DIR} + -DPROJECT_TOOLCHAIN_FILE_CMAKE=${PROJECT_TOOLCHAIN_FILE_CMAKE} + -DPROJECT_TOOLCHAIN_FILE_MESON=${PROJECT_TOOLCHAIN_FILE_MESON} + -DPROJECT_URL=${PROJECT_URL} + ) +endif() diff --git a/cmake/arch.cmake b/cmake/arch.cmake new file mode 100644 index 0000000..36b8aa1 --- /dev/null +++ b/cmake/arch.cmake @@ -0,0 +1,16 @@ +if("${CMAKE_SIZEOF_VOID_P}" EQUAL "4") + message(FATAL_ERROR "32-bit compilation is not supported") +endif() + +if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64.*|AARCH64.*|arm64.*|ARM64.*)") + set(IS_ARM_SYSTEM 1) +endif() + +if(PROJECT_IS_ARM64 AND NOT IS_ARM_SYSTEM AND NOT PROJECT_REQUIRE_ALPINE) + message(FATAL_ERROR "aarch64 cross-compile is not supported") +endif() + +string(REPLACE "_" "-" PROJECT_MARCH ${PROJECT_BUILD_ARCH}) +if("${PROJECT_BUILD_ARCH}" STREQUAL "aarch64") + set(PROJECT_MARCH armv8-a) +endif() diff --git a/cmake/flags.cmake b/cmake/flags.cmake new file mode 100644 index 0000000..b6691f5 --- /dev/null +++ b/cmake/flags.cmake @@ -0,0 +1,114 @@ +list(APPEND PROJECT_COMMON_FLAG_LIST + -D_FILE_OFFSET_BITS=64 + -D_GNU_SOURCE + -D_LARGEFILE64_SOURCE + -D_LARGEFILE_SOURCE + -D_TIME_BITS=64 + -march=${PROJECT_MARCH} + -mtune=generic +) + +if(PROJECT_IS_MINGW) + list(APPEND PROJECT_COMMON_FLAG_LIST + ${PROJECT_COMMON_FLAG_LIST} + -Wa,-mbig-obj + -flarge-source-files + ) +endif() + +list(APPEND PROJECT_DEBUG_FLAG_LIST + -DDEBUG + -D_DEBUG + -Og + -fno-omit-frame-pointer + -g +) + +list(APPEND PROJECT_RELEASE_FLAG_LIST + -DNDEBUG + -D_NDEBUG + -O3 +) + +list(APPEND PROJECT_CXXFLAGS_LIST + -Wall + -Wcast-align + -Wconversion + -Wdouble-promotion + -Wduplicated-branches + -Wduplicated-cond + -Wextra + -Wformat=2 + -Wlogical-op + -Wmisleading-indentation + -Wno-useless-cast + -Wnon-virtual-dtor + -Wnull-dereference + -Wold-style-cast + -Woverloaded-virtual + -Wpedantic + -Wshadow + -Wsign-conversion + -Wunused +) + +list(APPEND PROJECT_CFLAGS_LIST + ${PROJECT_COMMON_FLAG_LIST} + -std=c${CMAKE_C_STANDARD} +) + +list(APPEND PROJECT_CXXFLAGS_LIST + ${PROJECT_COMMON_FLAG_LIST} + -std=gnu++${CMAKE_CXX_STANDARD} +) + +if(PROJECT_STATIC_LINK) + list(APPEND PROJECT_CMAKE_EXE_LINKER_FLAGS + -static-libgcc + -static-libstdc++ + -static + ) +endif() + +list(JOIN PROJECT_COMMON_FLAG_LIST " " PROJECT_COMMON_FLAG_LIST) +list(JOIN PROJECT_DEBUG_FLAG_LIST " " PROJECT_DEBUG_FLAG_LIST) +list(JOIN PROJECT_RELEASE_FLAG_LIST " " PROJECT_RELEASE_FLAG_LIST) + +list(JOIN PROJECT_CFLAGS_LIST " " PROJECT_CFLAGS_LIST) +list(JOIN PROJECT_CXXFLAGS_LIST " " PROJECT_CXXFLAGS_LIST) + +list(JOIN PROJECT_CMAKE_EXE_LINKER_FLAGS " " PROJECT_CMAKE_EXE_LINKER_FLAGS) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${PROJECT_CFLAGS_LIST}") +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${PROJECT_DEBUG_FLAG_LIST}") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${PROJECT_RELEASE_FLAG_LIST}") + +set(CMAKE_CXX_FLAGS "-include cstdint -include utility ${CMAKE_CXX_FLAGS} ${PROJECT_CXXFLAGS_LIST}") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${PROJECT_DEBUG_FLAG_LIST}") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${PROJECT_RELEASE_FLAG_LIST}") + +set(CMAKE_EXE_LINKER_FLAGS "${PROJECT_CMAKE_EXE_LINKER_FLAGS}") + +set(EXTERNAL_CMAKE_CXX_FLAGS "-include cstdint -include utility -fext-numeric-literals ${PROJECT_COMMON_FLAG_LIST}") +list(APPEND PROJECT_EXTERNAL_CMAKE_FLAGS + -DCMAKE_BUILD_TYPE=${PROJECT_CMAKE_BUILD_TYPE} + -DCMAKE_COLOR_MAKEFILE=${CMAKE_COLOR_MAKEFILE} + -DCMAKE_CXX_FLAGS=${EXTERNAL_CMAKE_CXX_FLAGS} + -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} + -DCMAKE_C_FLAGS=${PROJECT_COMMON_FLAG_LIST} + -DCMAKE_C_STANDARD=${CMAKE_C_STANDARD} + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_INSTALL_PREFIX=${PROJECT_EXTERNAL_BUILD_ROOT} + -DCMAKE_POSITION_INDEPENDENT_CODE=${CMAKE_POSITION_INDEPENDENT_CODE} +) + +if(CMAKE_TOOLCHAIN_FILE) + list(APPEND PROJECT_EXTERNAL_CMAKE_FLAGS + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + ) +endif() + +string(REPLACE ";" "|" CMAKE_PREFIX_PATH_ALT_SEP "${CMAKE_PREFIX_PATH}") +list(APPEND PROJECT_EXTERNAL_CMAKE_FLAGS + -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH_ALT_SEP} +) diff --git a/cmake/functions.cmake b/cmake/functions.cmake new file mode 100644 index 0000000..2bcedf8 --- /dev/null +++ b/cmake/functions.cmake @@ -0,0 +1,153 @@ +function(set_common_target_options name) + target_compile_definitions(${name} PUBLIC + ${PROJECT_DEFINITIONS} + ${${name}_DEFINITIONS} + ) + + target_include_directories(${name} BEFORE PUBLIC + ${PROJECT_EXTERNAL_BUILD_ROOT}/include + ) + + target_link_directories(${name} BEFORE PUBLIC + ${PROJECT_EXTERNAL_BUILD_ROOT}/lib + ) + + target_include_directories(${name} AFTER PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/include + ${name}_INCLUDES + ) + + if(PROJECT_DEPENDENCIES) + add_dependencies(${name} ${PROJECT_DEPENDENCIES}) + endif() + + if(PROJECT_STATIC_LINK) + set_property(TARGET ${name} PROPERTY LINK_SEARCH_START_STATIC 1) + endif() +endfunction(set_common_target_options) + +function(add_project_executable2 name dependencies libraries headers sources is_win32) + if (PROJECT_WINDOWS_VERSION_RC) + list(APPEND sources ${PROJECT_WINDOWS_VERSION_RC}) + endif() + + add_executable(${name} + ${headers} + ${sources} + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/main.cpp + ) + + foreach(dependency ${dependencies}) + set_common_target_options(${dependency}) + endforeach() + + set_common_target_options(${name}) + + if(dependencies) + add_dependencies(${name} ${dependencies}) + endif() + + target_link_libraries(${name} PRIVATE ${libraries}) + + if(PROJECT_ENABLE_SDL AND PROJECT_IS_MINGW) + target_link_libraries(${name} PRIVATE SDL2::SDL2main) + endif () + + if (is_win32 AND PROJECT_IS_MINGW) + target_link_options(${name} PRIVATE -mwindows) + endif() +endfunction(add_project_executable2) + +function(add_project_executable name dependencies libraries) + file(GLOB_RECURSE headers + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/include/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/include/*.hh + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/include/*.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/include/*.hxx + ) + + file(GLOB_RECURSE sources + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/src/*.c + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/src/*.cc + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/src/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/src/*.cxx + ) + + if(PROJECT_ENABLE_WXWIDGETS OR PROJECT_ENABLE_SDL OR PROJECT_ENABLE_SFML OR PROJECT_ENABLE_NANA) + set(IS_WIN32 ON) + endif() + + add_project_executable2(${name} "${dependencies}" "${libraries}" "${headers}" "${sources}" "${IS_WIN32}") + + if(PROJECT_ENABLE_WXWIDGETS) + target_link_libraries(${name} PRIVATE ${wxWidgets_LIBRARIES}) + endif() +endfunction(add_project_executable) + +function(add_project_library name dependencies libraries additional_sources) + file(GLOB_RECURSE headers + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/include/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/include/*.hh + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/include/${name}/include/*.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/include/${name}/include/${name}/include/*.hxx + ) + + file(GLOB_RECURSE sources + ${additional_sources} + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/src/*.c + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/src/*.cc + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/src/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/src/*.cxx + ) + + add_library(${name} STATIC + ${headers} + ${sources} + ) + + set_common_target_options(${name}) + + set_target_properties(${name} PROPERTIES PREFIX "") + target_link_libraries(${name} PRIVATE ${libraries}) +endfunction(add_project_library) + +function(add_project_test_executable name dependencies libraries) + if(PROJECT_ENABLE_TESTING) + find_package(GTest ${GTEST_VERSION} REQUIRED) + enable_testing() + + file(GLOB_RECURSE headers + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/include/${name}/include/${name}/include/*.hxx + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/include/${name}/include/*.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/include/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/include/*.hh + ${PROJECT_SUPPORT_DIR}/test/include/*.hpp + ) + + file(GLOB_RECURSE sources + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/src/*.c + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/src/*.cc + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/src/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/src/*.cxx + ${PROJECT_SUPPORT_DIR}/test/src/*.cpp + ${additional_sources} + ) + + add_project_executable2(${name} "${dependencies}" "${libraries}" "${headers}" "${sources}" OFF) + + target_compile_definitions(${name} PRIVATE -DPROJECT_TESTING) + + target_include_directories(${name} SYSTEM BEFORE + ${GTEST_INCLUDE_DIRS} + ) + + target_include_directories(${name} AFTER PRIVATE + ${PROJECT_SUPPORT_DIR}/test/include + ) + + target_link_libraries(${name} PRIVATE + GTest::gtest + GTest::gmock + ) + endif() +endfunction() diff --git a/cmake/hashes.cmake b/cmake/hashes.cmake new file mode 100644 index 0000000..4d404bc --- /dev/null +++ b/cmake/hashes.cmake @@ -0,0 +1,14 @@ +set(BINUTILS_HASH b53606f443ac8f01d1d5fc9c39497f2af322d99e14cea5c0b4b124d630379365) +set(CPP_HTTPLIB_HASH 405abd8170f2a446fc8612ac635d0db5947c0d2e156e32603403a4496255ff00) +set(CURL_HASH 5a231145114589491fc52da118f9c7ef8abee885d1cb1ced99c7290e9a352f07) +set(EXPAT_HASH 372b18f6527d162fa9658f1c74d22a37429b82d822f5a1e1fc7e00f6045a06a2) +set(GCC_HASH 7d376d445f93126dc545e2c0086d0f647c3094aae081cdb78f42ce2bc25e7293) +set(GTEST_HASH 7b42b4d6ed48810c5362c265a17faebe90dc2373c885e5216439d37927f02926) +set(ICU_HASH a2c443404f00098e9e90acf29dc318e049d2dc78d9ae5f46efb261934a730ce2) +set(JSON_HASH 0d8ef5af7f9794e3263480193c491549b2ba6cc74bb018906202ada498a79406) +set(MINGW_HASH 30e5aad2c48dd318150f79cff47661232c4175876d6b4d6b270961cf2b49a48b) +set(OPENSSL_HASH e15dda82fe2fe8139dc2ac21a36d4ca01d5313c75f99f46c4e8a27709b7294bf) +set(PKG_CONFIG_HASH 6fc69c01688c9458a57eb9a1664c9aba372ccda420a02bf4429fe610e7e7d591) +set(ROCKSDB_HASH 9b810c81731835fda0d4bbdb51d3199d901fa4395733ab63752d297da84c5a47) +set(SPDLOG_HASH 9962648c9b4f1a7bbc76fd8d9172555bad1871fdb14ff4f842ef87949682caa5) +set(ZLIB_HASH 17e88863f3600672ab49182f217281b6fc4d3c762bde361935e436a95214d05c) diff --git a/cmake/libraries.cmake b/cmake/libraries.cmake new file mode 100644 index 0000000..eac474a --- /dev/null +++ b/cmake/libraries.cmake @@ -0,0 +1,61 @@ +find_package(PkgConfig REQUIRED) + +set(Boost_USE_STATIC_LIBS ${PROJECT_STATIC_LINK}) +set(CURL_USE_STATIC_LIBS ${PROJECT_STATIC_LINK}) +set(OPENSSL_USE_STATIC_LIBS ${PROJECT_STATIC_LINK}) +set(SFML_STATIC_LIBRARIES ${PROJECT_STATIC_LINK}) +set(ZLIB_USE_STATIC_LIBS ${PROJECT_STATIC_LINK}) +set(wxWidgets_USE_STATIC ${PROJECT_STATIC_LINK}) + +include(cmake/libraries/openssl.cmake) + + +include(cmake/libraries/backward_cpp.cmake) +include(cmake/libraries/cpp_httplib.cmake) +include(cmake/libraries/curl.cmake) +include(cmake/libraries/json.cmake) +include(cmake/libraries/rocksdb.cmake) +include(cmake/libraries/spdlog.cmake) +include(cmake/libraries/testing.cmake) + +if(PROJECT_BUILD) + find_package(Threads REQUIRED) + find_package(ZLIB REQUIRED) + + include_directories(BEFORE SYSTEM ${ZLIB_INCLUDE_DIRS}) + link_libraries(${ZLIB_LIBRARIES}) + + if(PROJECT_IS_MINGW) + link_libraries( + advapi32 + bcrypt + comdlg32 + crypt32 + dbghelp + gdi32 + httpapi + iphlpapi + kernel32 + mswsock + ncrypt + ole32 + oleaut32 + rpcrt4 + secur32 + shell32 + shlwapi + user32 + userenv + uuid + version + winhttp + wininet + winspool + ws2_32 + ) + else() + link_libraries( + uring + ) + endif() +endif() diff --git a/cmake/libraries/backward_cpp.cmake b/cmake/libraries/backward_cpp.cmake new file mode 100644 index 0000000..0b22e8c --- /dev/null +++ b/cmake/libraries/backward_cpp.cmake @@ -0,0 +1,11 @@ +if(PROJECT_ENABLE_BACKWARD_CPP AND PROJECT_BUILD) + if(PROJECT_IS_MINGW) + add_definitions(-DPROJECT_ENABLE_BACKWARD_CPP) + + link_libraries(msvcr90) + else() + add_definitions(-DBACKWARD_HAS_BFD) + + link_libraries(bfd) + endif() +endif() diff --git a/cmake/libraries/cpp_httplib.cmake b/cmake/libraries/cpp_httplib.cmake new file mode 100644 index 0000000..014fc9a --- /dev/null +++ b/cmake/libraries/cpp_httplib.cmake @@ -0,0 +1,32 @@ +if(PROJECT_ENABLE_CPP_HTTPLIB) + if(PROJECT_BUILD) + add_definitions( + -DCPPHTTPLIB_OPENSSL_SUPPORT + -DCPPHTTPLIB_TCP_NODELAY=true + -DCPPHTTPLIB_ZLIB_SUPPORT + -DPROJECT_ENABLE_CPP_HTTPLIB + ) + elseif(NOT PROJECT_IS_MINGW OR CMAKE_HOST_WIN32) + ExternalProject_Add(cpphttplib_project + PREFIX external + URL ${PROJECT_3RD_PARTY_DIR}/cpp-httplib-${CPP_HTTPLIB_VERSION}.tar.gz + URL_HASH SHA256=${CPP_HTTPLIB_HASH} + LIST_SEPARATOR | + CMAKE_ARGS ${PROJECT_EXTERNAL_CMAKE_FLAGS} + -DBUILD_SHARED_LIBS=${PROJECT_BUILD_SHARED_LIBS} + -DBUILD_STATIC_LIBS=ON + -DHTTPLIB_REQUIRE_OPENSSL=${PROJECT_ENABLE_OPENSSL} + -DHTTPLIB_REQUIRE_ZLIB=ON + -DHTTPLIB_REQUIRE_BROTLI=OFF + -DHTTPLIB_TEST=OFF + -DOPENSSL_USE_STATIC_LIBS=${OPENSSL_USE_STATIC_LIBS} + ) + + list(APPEND PROJECT_DEPENDENCIES cpphttplib_project) + + add_dependencies(cpphttplib_project curl_project) + if (NOT CMAKE_HOST_WIN32) + add_dependencies(cpphttplib_project openssl_project) + endif() + endif() +endif() diff --git a/cmake/libraries/curl.cmake b/cmake/libraries/curl.cmake new file mode 100644 index 0000000..17be921 --- /dev/null +++ b/cmake/libraries/curl.cmake @@ -0,0 +1,52 @@ +if(PROJECT_ENABLE_CURL) + if(PROJECT_BUILD) + add_definitions(-DPROJECT_ENABLE_CURL) + + find_package(CURL ${CURL_VERSION} REQUIRED) + + include_directories(BEFORE SYSTEM ${CURL_INCLUDE_DIRS}) + + link_libraries(CURL::libcurl) + + if (EXISTS ${PROJECT_DIST_DIR}/cacert.pem) + file(REMOVE ${PROJECT_DIST_DIR}/cacert.pem) + endif() + file(DOWNLOAD https://curl.haxx.se/ca/cacert.pem ${PROJECT_DIST_DIR}/cacert.pem) + elseif(NOT PROJECT_IS_MINGW OR CMAKE_HOST_WIN32) + ExternalProject_Add(curl_project + PREFIX external + URL ${PROJECT_3RD_PARTY_DIR}/curl-${CURL_VERSION}.tar.gz + URL_HASH SHA256=${CURL_HASH} + LIST_SEPARATOR | + CMAKE_ARGS + ${PROJECT_EXTERNAL_CMAKE_FLAGS} + -DBUILD_CURL_EXE=OFF + -DBUILD_LIBCURL_DOCS=OFF + -DBUILD_MISC_DOCS=OFF + -DBUILD_SHARED_LIBS=${PROJECT_BUILD_SHARED_LIBS} + -DBUILD_STATIC_CURL=ON + -DBUILD_STATIC_LIBS=ON + -DBUILD_STATIC_LIBS=ON + -DBUILD_TESTING=OFF + -DCURL_CA_BUNDLE=./cacert.pem + -DCURL_CA_FALLBACK=ON + -DCURL_DISABLE_LDAP=ON + -DCURL_USE_LIBPSL=OFF + -DCURL_USE_LIBSSH2=OFF + -DCURL_USE_OPENSSL=${PROJECT_ENABLE_OPENSSL} + -DCURL_ZLIB=ON + -DENABLE_CURL_MANUAL=OFF + -DENABLE_THREADED_RESOLVER=ON + -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} + -DOPENSSL_USE_STATIC_LIBS=${OPENSSL_USE_STATIC_LIBS} + -DUSE_LIBIDN2=OFF + -DZLIB_USE_STATIC_LIBS=${ZLIB_USE_STATIC_LIBS} + ) + + list(APPEND PROJECT_DEPENDENCIES curl_project) + + if (NOT CMAKE_HOST_WIN32) + add_dependencies(curl_project openssl_project) + endif() + endif() +endif() diff --git a/cmake/libraries/json.cmake b/cmake/libraries/json.cmake new file mode 100644 index 0000000..632e6ba --- /dev/null +++ b/cmake/libraries/json.cmake @@ -0,0 +1,28 @@ +if(PROJECT_ENABLE_JSON) + if(PROJECT_BUILD) + add_definitions(-DPROJECT_ENABLE_JSON) + + find_package(nlohmann_json ${JSON_VERSION} REQUIRED) + + if(PROJECT_IS_MINGW AND PROJECT_IS_MINGW_UNIX) + include_directories(BEFORE SYSTEM /mingw64/include/nlohmann) + else() + include_directories(BEFORE SYSTEM ${PROJECT_EXTERNAL_BUILD_ROOT}/include/nlohmann) + endif() + elseif(NOT PROJECT_IS_MINGW OR CMAKE_HOST_WIN32) + ExternalProject_Add(json_project + PREFIX external + URL ${PROJECT_3RD_PARTY_DIR}/json-${JSON_VERSION}.tar.gz + URL_HASH SHA256=${JSON_HASH} + LIST_SEPARATOR | + CMAKE_ARGS ${PROJECT_EXTERNAL_CMAKE_FLAGS} + -DBUILD_SHARED_LIBS=${PROJECT_BUILD_SHARED_LIBS} + -DBUILD_STATIC_LIBS=ON + -DJSON_BuildTests=OFF + -DJSON_Install=ON + -DJSON_MultipleHeaders=OFF + ) + + list(APPEND PROJECT_DEPENDENCIES json_project) + endif() +endif() diff --git a/cmake/libraries/openssl.cmake b/cmake/libraries/openssl.cmake new file mode 100644 index 0000000..942aa06 --- /dev/null +++ b/cmake/libraries/openssl.cmake @@ -0,0 +1,53 @@ +set(OPENSSL_ROOT_DIR $ENV{OPENSSL_ROOT_DIR}) + +if(PROJECT_ENABLE_OPENSSL) + if(PROJECT_BUILD) + add_definitions(-DPROJECT_ENABLE_OPENSSL) + + find_package(OpenSSL ${OPENSSL_VERSION} REQUIRED) + + include_directories(BEFORE SYSTEM ${OPENSSL_INCLUDE_DIR}) + + link_libraries( + OpenSSL::Crypto + OpenSSL::SSL + ) + elseif(NOT PROJECT_IS_MINGW) + if(PROJECT_IS_MINGW) + set(OPENSSL_COMPILE_TYPE mingw64) + elseif(PROJECT_IS_ARM64) + set(OPENSSL_COMPILE_TYPE linux-aarch64) + else() + set(OPENSSL_COMPILE_TYPE linux-x86_64) + endif() + + if(PROJECT_IS_MINGW_UNIX) + set(OPENSSL_CROSS_PREFIX "--cross-compile-prefix=x86_64-w64-mingw32-") + endif() + + if(PROJECT_BUILD_SHARED_LIBS) + set(OPENSSL_NO_SHARED shared) + else() + set(OPENSSL_NO_SHARED no-shared) + endif() + + ExternalProject_Add(openssl_project + PREFIX external + URL ${PROJECT_3RD_PARTY_DIR}/openssl-${OPENSSL_VERSION}.tar.gz + URL_HASH SHA256=${OPENSSL_HASH} + BUILD_IN_SOURCE 1 + LIST_SEPARATOR | + CONFIGURE_COMMAND ./Configure + ${OPENSSL_COMPILE_TYPE} + ${OPENSSL_CROSS_PREFIX} + --prefix=${PROJECT_EXTERNAL_BUILD_ROOT} + no-apps + no-docs + ${OPENSSL_NO_SHARED} + BUILD_COMMAND make -j1 + INSTALL_COMMAND make install + ) + + list(APPEND PROJECT_DEPENDENCIES openssl_project) + endif() +endif() diff --git a/cmake/libraries/rocksdb.cmake b/cmake/libraries/rocksdb.cmake new file mode 100644 index 0000000..92b2396 --- /dev/null +++ b/cmake/libraries/rocksdb.cmake @@ -0,0 +1,34 @@ +if(PROJECT_ENABLE_ROCKSDB) + if(PROJECT_BUILD) + add_definitions(-DPROJECT_ENABLE_ROCKSDB) + find_library(ROCKSDB_LIBRARY NAMES librocksdb.a REQUIRED) + link_libraries(${ROCKSDB_LIBRARY}) + elseif(NOT PROJECT_IS_MINGW OR CMAKE_HOST_WIN32) + ExternalProject_Add(rocksdb_project + PREFIX external + URL ${PROJECT_3RD_PARTY_DIR}/rocksdb-${ROCKSDB_VERSION}.tar.gz + URL_HASH SHA256=${ROCKSDB_HASH} + LIST_SEPARATOR | + CMAKE_ARGS ${PROJECT_EXTERNAL_CMAKE_FLAGS} + -DBUILD_SHARED_LIBS=OFF + -DBUILD_STATIC_LIBS=ON + -DFAIL_ON_WARNINGS=OFF + -DPORTABLE=1 + -DROCKSDB_BUILD_SHARED=OFF + -DROCKSDB_INSTALL_ON_WINDOWS=ON + -DWITH_BENCHMARK=OFF + -DWITH_BENCHMARK_TOOLS=OFF + -DWITH_CORE_TOOLS=OFF + -DWITH_EXAMPLES=OFF + -DWITH_GFLAGS=OFF + -DWITH_IOSTATS_CONTEXT=OFF + -DWITH_PERF_CONTEXT=OFF + -DWITH_TESTS=OFF + -DWITH_TOOLS=OFF + -DWITH_TRACE_TOOLS=OFF + -DWITH_ZLIB=ON + ) + + list(APPEND PROJECT_DEPENDENCIES rocksdb_project) + endif() +endif() \ No newline at end of file diff --git a/cmake/libraries/spdlog.cmake b/cmake/libraries/spdlog.cmake new file mode 100644 index 0000000..21972d5 --- /dev/null +++ b/cmake/libraries/spdlog.cmake @@ -0,0 +1,25 @@ +if(PROJECT_ENABLE_SPDLOG) + if(PROJECT_BUILD) + add_definitions(-DPROJECT_ENABLE_SPDLOG) + + find_package(spdlog ${SPDLOG_VERSION} REQUIRED) + + include_directories(BEFORE SYSTEM ${SPDLOG_INCLUDE_DIRS}) + + link_libraries(spdlog::spdlog) + elseif(NOT PROJECT_IS_MINGW OR CMAKE_HOST_WIN32) + ExternalProject_Add(spdlog_project + PREFIX external + URL ${PROJECT_3RD_PARTY_DIR}/spdlog-${SPDLOG_VERSION}.tar.gz + URL_HASH SHA256=${SPDLOG_HASH} + LIST_SEPARATOR | + CMAKE_ARGS ${PROJECT_EXTERNAL_CMAKE_FLAGS} + -DBUILD_SHARED_LIBS=${PROJECT_BUILD_SHARED_LIBS} + -DSPDLOG_BUILD_EXAMPLE=OFF + -DSPDLOG_FMT_EXTERNAL=OFF + -DSPDLOG_FMT_EXTERNAL_HO=OFF + ) + + list(APPEND PROJECT_DEPENDENCIES spdlog_project) + endif() +endif() diff --git a/cmake/libraries/testing.cmake b/cmake/libraries/testing.cmake new file mode 100644 index 0000000..5161d42 --- /dev/null +++ b/cmake/libraries/testing.cmake @@ -0,0 +1,17 @@ +if (PROJECT_ENABLE_TESTING) + if(PROJECT_BUILD) + add_definitions(-DPROJECT_ENABLE_TESTING) + elseif(NOT PROJECT_IS_MINGW) + ExternalProject_Add(gtest_project + PREFIX external + URL ${PROJECT_3RD_PARTY_DIR}/googletest-${GTEST_VERSION}.tar.gz + URL_HASH SHA256=${GTEST_HASH} + LIST_SEPARATOR | + CMAKE_ARGS ${PROJECT_EXTERNAL_CMAKE_FLAGS} + -DBUILD_SHARED_LIBS=${PROJECT_BUILD_SHARED_LIBS} + -DBUILD_STATIC_LIBS=ON + ) + + list(APPEND PROJECT_DEPENDENCIES gtest_project) + endif() +endif() diff --git a/cmake/options.cmake b/cmake/options.cmake new file mode 100644 index 0000000..018d096 --- /dev/null +++ b/cmake/options.cmake @@ -0,0 +1,8 @@ +option(PROJECT_ENABLE_BACKWARD_CPP "Enable backward-cpp" ON) +option(PROJECT_ENABLE_CPP_HTTPLIB "Enable cpp-httplib" ON) +option(PROJECT_ENABLE_CURL "Enable curl library" ON) +option(PROJECT_ENABLE_JSON "Enable JSON for Modern C++ library" ON) +option(PROJECT_ENABLE_OPENSSL "Enable OpenSSL library" ON) +option(PROJECT_ENABLE_ROCKSDB "Enable RocksDB library" ON) +option(PROJECT_ENABLE_SPDLOG "Enable spdlog library" ON) +option(PROJECT_ENABLE_TESTING "Enable building unit tests" ON) diff --git a/cmake/os.cmake b/cmake/os.cmake new file mode 100644 index 0000000..9604d74 --- /dev/null +++ b/cmake/os.cmake @@ -0,0 +1,15 @@ +if(MSVC) + message(FATAL_ERROR "MSVC will not be supported") +endif() + +if(UNIX AND APPLE) + message(FATAL_ERROR "Apple is not currently supported") +endif() + +if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + message(FATAL_ERROR "FreeBSD is not currently supported") +endif() + +if(PROJECT_REQUIRE_ALPINE AND NOT PROJECT_IS_ALPINE AND PROJECT_IS_MINGW AND PROJECT_IS_MINGW_UNIX) + message(FATAL_ERROR "Project requires Alpine Linux to build") +endif() diff --git a/cmake/settings.cmake b/cmake/settings.cmake new file mode 100644 index 0000000..8a0898b --- /dev/null +++ b/cmake/settings.cmake @@ -0,0 +1,51 @@ +set(CMAKE_COLOR_MAKEFILE OFF) +set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +if(PROJECT_STATIC_LINK) + set(CMAKE_POSITION_INDEPENDENT_CODE OFF) +else() + set(CMAKE_POSITION_INDEPENDENT_CODE ON) +endif() + +set(CMAKE_BUILD_TYPE ${PROJECT_CMAKE_BUILD_TYPE}) + +file(MAKE_DIRECTORY ${PROJECT_EXTERNAL_BUILD_ROOT}/lib) +file(CREATE_LINK ${PROJECT_EXTERNAL_BUILD_ROOT}/lib ${PROJECT_EXTERNAL_BUILD_ROOT}/lib64 SYMBOLIC) + +list(PREPEND CMAKE_PREFIX_PATH + ${PROJECT_EXTERNAL_BUILD_ROOT} + ${PROJECT_EXTERNAL_BUILD_ROOT}/share +) + +if(PROJECT_IS_MINGW) + list(PREPEND CMAKE_PREFIX_PATH + /mingw64 + ) + + include_directories(BEFORE + /mingw64/include + ) + + link_directories(BEFORE + /mingw64/lib + /mingw64/lib64 + ) +endif() + +include_directories(BEFORE SYSTEM + ${PROJECT_SUPPORT_DIR}/include + ${PROJECT_EXTERNAL_BUILD_ROOT}/include +) + +link_directories(BEFORE + ${PROJECT_EXTERNAL_BUILD_ROOT}/lib + ${PROJECT_EXTERNAL_BUILD_ROOT}/lib64 +) diff --git a/cmake/versions.cmake b/cmake/versions.cmake new file mode 100644 index 0000000..61c7be1 --- /dev/null +++ b/cmake/versions.cmake @@ -0,0 +1,23 @@ +set(BINUTILS_VERSION 2.43) +set(BOOST2_MAJOR_VERSION 1) +set(BOOST2_MINOR_VERSION 76) +set(BOOST2_PATCH_VERSION 0) +set(BOOST_MAJOR_VERSION 1) +set(BOOST_MINOR_VERSION 87) +set(BOOST_PATCH_VERSION 0) +set(CPP_HTTPLIB_VERSION 0.18.1) +set(CURL2_VERSION 8_11_0) +set(CURL_VERSION 8.11.0) +set(EXPAT2_VERSION 2_6_4) +set(EXPAT_VERSION 2.6.4) +set(GCC_VERSION 14.2.0) +set(GTEST_VERSION 1.15.2) +set(ICU_VERSION 76-1) +set(JSON_VERSION 3.11.3) +set(MESA_VERSION 23.3.3) +set(MINGW_VERSION 12.0.0) +set(OPENSSL_VERSION 3.4.0) +set(PKG_CONFIG_VERSION 0.29.2) +set(ROCKSDB_VERSION 9.7.4) +set(SPDLOG_VERSION 1.15.0) +set(ZLIB_VERSION 1.3.1) diff --git a/config.sh b/config.sh new file mode 100644 index 0000000..487709d --- /dev/null +++ b/config.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +PROJECT_NAME="monitarr" + +PROJECT_COMPANY_NAME="" +PROJECT_COPYRIGHT="" +PROJECT_DESC="" +PROJECT_URL="" + +PROJECT_MAJOR_VERSION=0 +PROJECT_MINOR_VERSION=0 +PROJECT_REVISION_VERSION=1 +PROJECT_RELEASE_NUM=0 +PROJECT_RELEASE_ITER=alpha + +PROJECT_APP_LIST=(${PROJECT_NAME}) + +PROJECT_PRIVATE_KEY=${DEVELOPER_PRIVATE_KEY} +PROJECT_PUBLIC_KEY=${DEVELOPER_PUBLIC_KEY} + +PROJECT_STATIC_LINK=ON + +PROJECT_MINGW64_COPY_DEPENDENCIES+=() +PROJECT_MSYS2_PACKAGE_LIST+=() + +PROJECT_ENABLE_WIN32_LONG_PATH_NAMES=OFF + +PROJECT_ENABLE_BACKWARD_CPP=OFF +PROJECT_ENABLE_BOOST=OFF +PROJECT_ENABLE_CLI11=OFF +PROJECT_ENABLE_CPP_HTTPLIB=ON +PROJECT_ENABLE_CURL=ON +PROJECT_ENABLE_CXXOPTS=OFF +PROJECT_ENABLE_DTL=OFF +PROJECT_ENABLE_FLAC=OFF +PROJECT_ENABLE_FMT=OFF +PROJECT_ENABLE_FONTCONFIG=OFF +PROJECT_ENABLE_FREETYPE2=OFF +PROJECT_ENABLE_FUSE=OFF +PROJECT_ENABLE_FZF=OFF +PROJECT_ENABLE_GTKMM=OFF +PROJECT_ENABLE_JSON=ON +PROJECT_ENABLE_LIBBITCOIN_SYSTEM=OFF +PROJECT_ENABLE_LIBDSM=OFF +PROJECT_ENABLE_LIBEVENT=OFF +PROJECT_ENABLE_LIBICONV=OFF +PROJECT_ENABLE_LIBJPEG_TURBO=OFF +PROJECT_ENABLE_LIBPNG=OFF +PROJECT_ENABLE_LIBSODIUM=OFF +PROJECT_ENABLE_LIBTASN=OFF +PROJECT_ENABLE_NANA=OFF +PROJECT_ENABLE_NUSPELL=OFF +PROJECT_ENABLE_OGG=OFF +PROJECT_ENABLE_OPENAL=OFF +PROJECT_ENABLE_OPENSSL=ON +PROJECT_ENABLE_PUGIXML=OFF +PROJECT_ENABLE_ROCKSDB=ON +PROJECT_ENABLE_SAGO_PLATFORM_FOLDERS=OFF +PROJECT_ENABLE_SDL=OFF +PROJECT_ENABLE_SECP256K1=OFF +PROJECT_ENABLE_SFML=OFF +PROJECT_ENABLE_SPDLOG=ON +PROJECT_ENABLE_SQLITE=OFF +PROJECT_ENABLE_STDUUID=OFF +PROJECT_ENABLE_TESTING=ON +PROJECT_ENABLE_TPL=OFF +PROJECT_ENABLE_VLC=OFF +PROJECT_ENABLE_VORBIS=OFF +PROJECT_ENABLE_WINFSP=OFF +PROJECT_ENABLE_WXWIDGETS=OFF + +PROJECT_KEEP_BACKWARD_CPP=1 + +if [ "${PROJECT_ENABLE_TESTING}" == "ON" ]; then + PROJECT_APP_LIST+=(${PROJECT_NAME}_test) +fi + +if [ -f "./override.sh" ]; then + . ./override.sh +fi diff --git a/docker/aarch64/alpine b/docker/aarch64/alpine new file mode 100644 index 0000000..6b580f0 --- /dev/null +++ b/docker/aarch64/alpine @@ -0,0 +1,86 @@ +#comment +FROM arm64v8/alpine:3.21.3 +MAINTAINER Scott E. Graves +CMD bash + +RUN apk update +RUN apk upgrade +RUN apk add \ + autoconf \ + automake \ + bash \ + binutils \ + binutils-dev \ + bison \ + boost-dev \ + bzip2-static \ + cmake \ + diffutils \ + elfutils-dev \ + file \ + fontconfig-dev \ + fontconfig-static \ + freetype \ + freetype-dev \ + freetype-static \ + fuse3 \ + fuse3-dev \ + fuse3-static \ + g++ \ + gcc \ + gflags \ + gflags-dev \ + git \ + icu-dev \ + icu-libs \ + icu-static \ + libogg-dev \ + libogg-static \ + libtool \ + libudev-zero \ + libudev-zero-dev \ + libunwind \ + libunwind-dev \ + libunwind-static \ + liburing \ + liburing-dev \ + libvorbis-dev \ + libvorbis-static \ + libx11-dev \ + libx11-static \ + libxcursor \ + libxcursor-dev \ + libxrandr \ + libxrandr-dev \ + linux-headers \ + make \ + mesa \ + mesa-dev \ + meson \ + musl-dev \ + ninja \ + openal-soft-dev \ + openal-soft-libs \ + openssl \ + patch \ + perl \ + pkgconfig \ + rsync \ + tcl \ + tcl-dev \ + texinfo \ + wget \ + xz \ + xz-dev \ + xz-libs \ + zlib \ + zlib-dev \ + zlib-static \ + zstd \ + zstd-dev \ + zstd-libs \ + zstd-static \ + xz-static + +RUN ln -sf /usr/bin/aclocal-1.17 /usr/bin/aclocal-1.16 +RUN ln -sf /usr/bin/automake-1.17 /usr/bin/automake-1.16 diff --git a/docker/x86_64/alpine b/docker/x86_64/alpine new file mode 100644 index 0000000..3346e7b --- /dev/null +++ b/docker/x86_64/alpine @@ -0,0 +1,86 @@ +#comment +FROM alpine:3.21.3 +MAINTAINER Scott E. Graves +CMD bash + +RUN apk update +RUN apk upgrade +RUN apk add \ + autoconf \ + automake \ + bash \ + binutils \ + binutils-dev \ + bison \ + boost-dev \ + bzip2-static \ + cmake \ + diffutils \ + elfutils-dev \ + file \ + fontconfig-dev \ + fontconfig-static \ + freetype \ + freetype-dev \ + freetype-static \ + fuse3 \ + fuse3-dev \ + fuse3-static \ + g++ \ + gcc \ + gflags \ + gflags-dev \ + git \ + icu-dev \ + icu-libs \ + icu-static \ + libogg-dev \ + libogg-static \ + libtool \ + libudev-zero \ + libudev-zero-dev \ + libunwind \ + libunwind-dev \ + libunwind-static \ + liburing \ + liburing-dev \ + libvorbis-dev \ + libvorbis-static \ + libx11-dev \ + libx11-static \ + libxcursor \ + libxcursor-dev \ + libxrandr \ + libxrandr-dev \ + linux-headers \ + make \ + mesa \ + mesa-dev \ + meson \ + musl-dev \ + ninja \ + openal-soft-dev \ + openal-soft-libs \ + openssl \ + patch \ + perl \ + pkgconfig \ + rsync \ + tcl \ + tcl-dev \ + texinfo \ + wget \ + xz \ + xz-dev \ + xz-libs \ + zlib \ + zlib-dev \ + zlib-static \ + zstd \ + zstd-dev \ + zstd-libs \ + zstd-static \ + xz-static + +RUN ln -sf /usr/bin/aclocal-1.17 /usr/bin/aclocal-1.16 +RUN ln -sf /usr/bin/automake-1.17 /usr/bin/automake-1.16 diff --git a/docker/x86_64/mingw64 b/docker/x86_64/mingw64 new file mode 100644 index 0000000..b790cc0 --- /dev/null +++ b/docker/x86_64/mingw64 @@ -0,0 +1,1119 @@ +#comment +FROM alpine:3.21.3 + +RUN apk update +RUN apk upgrade +RUN apk add \ + autoconf \ + automake \ + bash \ + binutils \ + bison \ + bzip2 \ + clang17-extra-tools \ + cmake \ + file \ + flex \ + g++ \ + gcc \ + gettext \ + git \ + gmp \ + gmp-dev \ + gperf \ + gtkmm3-dev \ + intltool \ + isl-dev \ + libtool \ + linux-headers \ + make \ + mpc1 \ + mpc1-dev \ + mpfr \ + mpfr-dev \ + ninja \ + openssl \ + p7zip \ + patch \ + perl \ + py3-pip \ + python3 \ + rsync \ + ruby \ + texinfo \ + unzip \ + wget \ + wine \ + xz \ + zlib \ + zlib-dev + +WORKDIR /mnt +ENV MY_WORKDIR=/mnt + +ARG MINGW_DIR=/mingw64 +ENV MY_MINGW_DIR=${MINGW_DIR} + +ARG MINGW_PREFIX=x86_64-w64-mingw32 +ENV MY_MINGW_PREFIX=${MINGW_PREFIX} + +ARG NUM_JOBS=2 +ENV MY_NUM_JOBS=${NUM_JOBS} + +ARG CXX_STANDARD=20 +ENV MY_CXX_STANDARD=${CXX_STANDARD} + +ARG TOOLCHAIN_FILE_CMAKE=/cmake_toolchain.cmake +ENV MY_TOOLCHAIN_FILE_CMAKE=${TOOLCHAIN_FILE_CMAKE} +RUN echo -e \ + "set(CMAKE_SYSTEM_NAME Windows)\n"\ + "set(CMAKE_CXX_COMPILER ${MY_MINGW_PREFIX}-g++)\n"\ + "set(CMAKE_C_COMPILER ${MY_MINGW_PREFIX}-gcc)\n"\ + "set(CMAKE_FIND_ROOT_PATH ${MY_MINGW_DIR})\n"\ + "set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\n"\ + "set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\n"\ + "set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\n"\ + "set(CMAKE_Fortran_COMPILER ${MY_MINGW_PREFIX}-gfortran)\n"\ + "set(CMAKE_INSTALL_PREFIX ${MY_MINGW_DIR})\n"\ + "set(CMAKE_RC_COMPILER ${MY_MINGW_PREFIX}-windres)\n"\ + > ${MY_TOOLCHAIN_FILE_CMAKE} + +ARG TOOLCHAIN_FILE_MESON=/meson_cross_file.txt +ENV MY_TOOLCHAIN_FILE_MESON=${TOOLCHAIN_FILE_MESON} +RUN echo -e \ + "[binaries]\n"\ + "ar = '${MY_MINGW_PREFIX}-ar'\n"\ + "c = '${MY_MINGW_PREFIX}-gcc'\n"\ + "cpp = '${MY_MINGW_PREFIX}-g++'\n"\ + "ld = '${MY_MINGW_PREFIX}-ld'\n"\ + "objcopy = '${MY_MINGW_PREFIX}-objcopy'\n"\ + "pkg-config = 'pkg-config'\n"\ + "strip = '${MY_MINGW_PREFIX}-strip'\n"\ + "windres = '${MY_MINGW_PREFIX}-windres'\n"\ + "exe_wrapper = 'wine64'\n"\ + "[properties]\n"\ + "c_args = ['-I${MY_MINGW_DIR}/include']\n"\ + "c_link_args = ['-L${MY_MINGW_DIR}/lib', '-L${MY_MINGW_DIR}/lib64']\n"\ + "[host_machine]\n"\ + "cpu = 'x86_64'\n"\ + "cpu_family = 'x86_64'\n"\ + "endian = 'little'\n"\ + "system = 'windows'\n"\ + > ${MY_TOOLCHAIN_FILE_MESON} + +SHELL [ "/bin/bash", "-c" ] + +RUN mkdir -p \ + ${MY_MINGW_DIR}/bin \ + ${MY_MINGW_DIR}/include \ + ${MY_MINGW_DIR}/lib/pkgconfig \ + ${MY_MINGW_DIR}/lib64/pkgconfig + +ADD ./3rd_party /3rd_party + +ARG BINUTILS_VERSION +ENV MY_BINUTILS_VERSION=${BINUTILS_VERSION} +RUN cd /3rd_party/mingw64 && sha256sum -c ./binutils-${MY_BINUTILS_VERSION}.tar.xz.sha256 && cd - \ + && tar xvJf /3rd_party/mingw64/binutils-${MY_BINUTILS_VERSION}.tar.xz \ + && cd binutils-${MY_BINUTILS_VERSION} \ + && ./configure \ + --disable-lto \ + --disable-multilib \ + --disable-nls \ + --disable-plugins \ + --disable-shared \ + --disable-werror \ + --enable-static \ + --prefix=/usr/local \ + --target=${MY_MINGW_PREFIX} \ + --with-system-zlib \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r binutils-${MY_BINUTILS_VERSION} + +ARG MINGW_VERSION +ENV MY_MINGW_VERSION=${MINGW_VERSION} +RUN cd /3rd_party/mingw64 && sha256sum -c ./mingw-w64-v${MY_MINGW_VERSION}.tar.bz2.sha256 && cd - \ + && tar xvjf /3rd_party/mingw64/mingw-w64-v${MY_MINGW_VERSION}.tar.bz2 \ + && mkdir mingw-w64 \ + && cd mingw-w64 \ + && ../mingw-w64-v${MY_MINGW_VERSION}/mingw-w64-headers/configure \ + --enable-sdk=all \ + --host=${MY_MINGW_PREFIX} \ + --prefix=/usr/local/${MY_MINGW_PREFIX} \ + && make install + +ARG GCC_VERSION +ENV MY_GCC_VERSION=${GCC_VERSION} +RUN cd /3rd_party/mingw64 && sha256sum -c ./gcc-${MY_GCC_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/mingw64/gcc-${MY_GCC_VERSION}.tar.gz \ + && (mv gcc-releases-gcc-${MY_GCC_VERSION} gcc-${MY_GCC_VERSION} || echo "") \ + && mkdir gcc \ + && cd gcc \ + && ../gcc-${MY_GCC_VERSION}/configure \ + --disable-libssp \ + --disable-libstdcxx-debug \ + --disable-libstdcxx-pch \ + --disable-multilib \ + --disable-nls \ + --disable-rpath \ + --disable-symvers \ + --disable-werror \ + --disable-win32-registry \ + --enable-checking=release \ + --enable-fully-dynamic-string \ + --enable-graphite \ + --enable-languages=c,c++ \ + --enable-libatomic \ + --enable-libgomp \ + --enable-libstdcxx-filesystem-ts \ + --enable-libstdcxx-time \ + --enable-lto \ + --enable-shared \ + --enable-static \ + --enable-threads=posix \ + --libexecdir=/usr/local/lib \ + --prefix=/usr/local \ + --target=${MY_MINGW_PREFIX} \ + --with-arch=nocona \ + --with-boot-ldflags="-static-libstdc++" \ + --with-gnu-as \ + --with-gnu-ld \ + --with-libiconv \ + --with-native-system-header-dir=/usr/local/include \ + --with-stage1-ldflags="-static-libstdc++" \ + --with-system-zlib \ + --with-tune=generic \ + --with-{gmp,mpfr,mpc,isl}=/usr/local \ + && make -j${MY_NUM_JOBS} all-gcc \ + && make install-gcc + +RUN cd mingw-w64 \ + && ../mingw-w64-v${MY_MINGW_VERSION}/mingw-w64-crt/configure \ + --disable-lib32 \ + --enable-lib64 \ + --enable-wildcard \ + --host=${MY_MINGW_PREFIX} \ + --prefix=/usr/local/${MY_MINGW_PREFIX} \ + && (make || make || make || make) \ + && make install + +RUN cd mingw-w64 \ + && ../mingw-w64-v${MY_MINGW_VERSION}/mingw-w64-libraries/winpthreads/configure \ + --enable-shared \ + --enable-static \ + --host=${MY_MINGW_PREFIX} \ + --prefix=/usr/local/${MY_MINGW_PREFIX} \ + && make -j${MY_NUM_JOBS} \ + && make install + +RUN cd gcc \ + && make -j${MY_NUM_JOBS} \ + && make install + +RUN cp /usr/local/${MY_MINGW_PREFIX}/lib/*.dll ${MY_MINGW_DIR}/bin \ + && cp /usr/local/${MY_MINGW_PREFIX}/bin/*.dll ${MY_MINGW_DIR}/bin \ + && rm -r gcc gcc-${MY_GCC_VERSION} \ + && rm -r mingw-w64 mingw-w64-v${MY_MINGW_VERSION} + +ARG PKG_CONFIG_VERSION +ENV MY_PKG_CONFIG_VERSION=${PKG_CONFIG_VERSION} +RUN cd /3rd_party/mingw64 && sha256sum -c ./pkg-config-${MY_PKG_CONFIG_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/mingw64/pkg-config-${MY_PKG_CONFIG_VERSION}.tar.gz \ + && cd pkg-config-${MY_PKG_CONFIG_VERSION} \ + && ./configure \ + --disable-nls \ + --disable-shared \ + --prefix=/usr/local \ + --with-internal-glib \ + --with-pc-path=${MY_MINGW_DIR}/lib/pkgconfig \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r pkg-config-${MY_PKG_CONFIG_VERSION} + +RUN python3 -m pip install --break-system-packages -U mako +RUN python3 -m pip install --break-system-packages -U meson +RUN python3 -m pip install --break-system-packages -U packaging + +ENV CXXFLAGS="-std=gnu++20" +ENV LDFLAGS="-L${MY_MINGW_DIR}/lib -L${MY_MINGW_DIR}/lib64" +ENV PATH="${MY_MINGW_DIR}/bin:/usr/local/bin:${PATH}" +ENV PKG_CONFIG_PATH="${MY_MINGW_DIR}/lib/pkgconfig:${MY_MINGW_DIR}/lib64/pkgconfig" + +ARG ZLIB_VERSION +ENV MY_ZLIB_VERSION=${ZLIB_VERSION} +RUN cd /3rd_party/mingw64 && sha256sum -c ./zlib-${MY_ZLIB_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/mingw64/zlib-${MY_ZLIB_VERSION}.tar.gz \ + && cd zlib-${MY_ZLIB_VERSION} \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_STATIC_LIBS=ON \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r zlib-${MY_ZLIB_VERSION} + +ARG LIBJPEG_TURBO_VERSION +ENV MY_LIBJPEG_TURBO_VERSION=${LIBJPEG_TURBO_VERSION} +RUN if [ -f "/3rd_party/libjpeg_turbo-${MY_LIBJPEG_TURBO_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./libjpeg_turbo-${MY_LIBJPEG_TURBO_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/libjpeg_turbo-${MY_LIBJPEG_TURBO_VERSION}.tar.gz \ + && cd libjpeg-turbo-${MY_LIBJPEG_TURBO_VERSION} \ + && mkdir _build \ + && cd _build \ + && cmake .. \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_STATIC_LIBS=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_SYSTEM_PROCESSOR=AMD64 \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DENABLE_SHARED=ON \ + -DENABLE_STATIC=ON \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r libjpeg-turbo-${MY_LIBJPEG_TURBO_VERSION} \ + ; fi + +ARG LIBPNG_VERSION +ENV MY_LIBPNG_VERSION=${LIBPNG_VERSION} +RUN if [ -f "/3rd_party/libpng-v${MY_LIBPNG_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./libpng-v${MY_LIBPNG_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/libpng-v${MY_LIBPNG_VERSION}.tar.gz \ + && cd libpng-${MY_LIBPNG_VERSION} \ + && mkdir _build \ + && cd _build \ + && cmake .. \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_STATIC_LIBS=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DPNG_SHARED=ON \ + -DPNG_STATIC=ON \ + -DPNG_TESTS=OFF \ + -DPNG_TOOLS=OFF \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r libpng-${MY_LIBPNG_VERSION} \ + ; fi + +ARG FREETYPE2_VERSION +ENV MY_FREETYPE2_VERSION=${FREETYPE2_VERSION} +RUN if [ -f "/3rd_party/freetype-${MY_FREETYPE2_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./freetype-${MY_FREETYPE2_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/freetype-${MY_FREETYPE2_VERSION}.tar.gz \ + && cd freetype-${MY_FREETYPE2_VERSION} \ + && meson setup \ + --cross-file ${MY_TOOLCHAIN_FILE_MESON} \ + --prefix=${MY_MINGW_DIR} \ + _build \ + && meson compile \ + -C _build \ + && meson install \ + -C _build \ + && cd ${MY_WORKDIR} \ + && rm -r freetype-${MY_FREETYPE2_VERSION} \ + ; fi + +ARG EXPAT_VERSION +ENV MY_EXPAT_VERSION=${EXPAT_VERSION} +RUN cd /3rd_party/mingw64 && sha256sum -c ./expat-${MY_EXPAT_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/mingw64/expat-${MY_EXPAT_VERSION}.tar.gz \ + && cd libexpat-*/expat \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_STATIC_LIBS=ON \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DEXPAT_BUILD_DOCS=OFF \ + -DEXPAT_BUILD_EXAMPLES=OFF \ + -DEXPAT_BUILD_TESTS=OFF \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r libexpat-* + +ARG FONTCONFIG_VERSION +ENV MY_FONTCONFIG_VERSION=${FONTCONFIG_VERSION} +RUN if [ -f "/3rd_party/fontconfig-${MY_FONTCONFIG_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./fontconfig-${MY_FONTCONFIG_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/fontconfig-${MY_FONTCONFIG_VERSION}.tar.gz \ + && cd fontconfig-${MY_FONTCONFIG_VERSION} \ + && meson setup \ + --cross-file ${MY_TOOLCHAIN_FILE_MESON} \ + --prefix=${MY_MINGW_DIR} \ + -Ddoc=disabled \ + -Dtests=disabled \ + -Dtools=disabled \ + _build \ + && meson compile \ + -C _build \ + && meson install \ + -C _build \ + && cd ${MY_WORKDIR} \ + && rm -r fontconfig-${MY_FONTCONFIG_VERSION} \ + ; fi + +ARG OPENAL_VERSION +ENV MY_OPENAL_VERSION=${OPENAL_VERSION} +RUN if [ -f "/3rd_party/openal-${MY_OPENAL_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./openal-${MY_OPENAL_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/openal-${MY_OPENAL_VERSION}.tar.gz \ + && cd openal-soft-${MY_OPENAL_VERSION} \ + && mkdir _build \ + && cd _build \ + && cmake .. \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && mv ${MY_MINGW_DIR}/bin/OpenAL32.dll ${MY_MINGW_DIR}/bin/openal32.dll \ + && cd ${MY_WORKDIR} \ + && rm -r openal-soft-${MY_OPENAL_VERSION} \ + ; fi + +ARG ICU_VERSION +ENV MY_ICU_VERSION=${ICU_VERSION} +RUN cd /3rd_party/mingw64 && sha256sum -c ./icu-release-${MY_ICU_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/mingw64/icu-release-${MY_ICU_VERSION}.tar.gz \ + && cp -r icu-release-${MY_ICU_VERSION} icu-release-${MY_ICU_VERSION}-cross \ + && cd icu-release-${MY_ICU_VERSION}-cross/icu4c/source \ + && CXXFLAGS="-std=gnu++17" ./configure \ + --disable-samples \ + --disable-tests \ + --enable-shared \ + --enable-static \ + --prefix=/usr/local \ + && make -j${MY_NUM_JOBS} \ + && make install + +RUN cd ${MY_WORKDIR} \ + && cd icu-release-${MY_ICU_VERSION}/icu4c/source \ + && CXXFLAGS="-std=gnu++17" ./configure \ + --build=x86_64-alpine-linux-musl \ + --disable-samples \ + --disable-tests \ + --enable-shared \ + --enable-static \ + --host=${MY_MINGW_PREFIX} \ + --prefix=${MY_MINGW_DIR} \ + --with-cross-build=${MY_WORKDIR}/icu-release-${MY_ICU_VERSION}-cross/icu4c/source \ + && make -j${MY_NUM_JOBS} \ + && make install + +RUN cd ${MY_WORKDIR} \ + && rm -r icu-release-${MY_ICU_VERSION} \ + && rm -r icu-release-${MY_ICU_VERSION}-cross + +ARG OPENSSL_VERSION +ENV MY_OPENSSL_VERSION=${OPENSSL_VERSION} +RUN if [ -f "/3rd_party/openssl-${MY_OPENSSL_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./openssl-${MY_OPENSSL_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/openssl-${MY_OPENSSL_VERSION}.tar.gz \ + && cd openssl-${MY_OPENSSL_VERSION} \ + && ./Configure \ + --cross-compile-prefix=${MY_MINGW_PREFIX}- \ + --prefix=${MY_MINGW_DIR} \ + mingw64 \ + no-shared \ + no-apps \ + no-docs \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r openssl-${MY_OPENSSL_VERSION} \ + ; fi + +ARG BOOST2_MAJOR_VERSION +ENV MY_BOOST2_MAJOR_VERSION=${BOOST2_MAJOR_VERSION} +ARG BOOST2_MINOR_VERSION +ENV MY_BOOST2_MINOR_VERSION=${BOOST2_MINOR_VERSION} +ARG BOOST2_PATCH_VERSION +ENV MY_BOOST2_PATCH_VERSION=${BOOST2_PATCH_VERSION} +ENV MY_BOOST2_VERSION=${MY_BOOST2_MAJOR_VERSION}_${MY_BOOST2_MINOR_VERSION}_${MY_BOOST2_PATCH_VERSION} +RUN if [ -f "/3rd_party/boost_${MY_BOOST2_MAJOR_VERSION}_${MY_BOOST2_MINOR_VERSION}_${MY_BOOST2_PATCH_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./boost_${MY_BOOST2_MAJOR_VERSION}_${MY_BOOST2_MINOR_VERSION}_${MY_BOOST2_PATCH_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/boost_${MY_BOOST2_MAJOR_VERSION}_${MY_BOOST2_MINOR_VERSION}_${MY_BOOST2_PATCH_VERSION}.tar.gz \ + && cd boost_${MY_BOOST2_VERSION} \ + && ./bootstrap.sh \ + --with-libraries=atomic,chrono,date_time,filesystem,iostreams,locale,log,program_options,random,regex,serialization,system,test,thread \ + address-model=64 \ + cxxstd=${MY_CXX_STANDARD} \ + cxxstd-dialect=gnu \ + architecture=x86 \ + link=static,shared \ + target-os=windows \ + threading=multi \ + variant=release \ + && echo "using gcc : mingw : ${MY_MINGW_PREFIX}-g++ ;" \ + >> ./project-config.jam \ + && ./b2 \ + --openssldir=${MY_MINGW_DIR} \ + --prefix=${MY_MINGW_DIR} \ + -j${MY_NUM_JOBS} \ + address-model=64 \ + cxxstd=${MY_CXX_STANDARD} \ + cxxstd-dialect=gnu \ + architecture=x86 \ + link=static,shared \ + target-os=windows \ + toolset=gcc-mingw \ + threading=multi \ + variant=release \ + install \ + && cd ${MY_WORKDIR} \ + && rm -r boost_${MY_BOOST2_VERSION} \ + ; fi + +ARG BOOST_MAJOR_VERSION +ENV MY_BOOST_MAJOR_VERSION=${BOOST_MAJOR_VERSION} +ARG BOOST_MINOR_VERSION +ENV MY_BOOST_MINOR_VERSION=${BOOST_MINOR_VERSION} +ARG BOOST_PATCH_VERSION +ENV MY_BOOST_PATCH_VERSION=${BOOST_PATCH_VERSION} +ENV MY_BOOST_VERSION=${MY_BOOST_MAJOR_VERSION}_${MY_BOOST_MINOR_VERSION}_${MY_BOOST_PATCH_VERSION} +RUN if [ -f "/3rd_party/boost_${MY_BOOST_MAJOR_VERSION}_${MY_BOOST_MINOR_VERSION}_${MY_BOOST_PATCH_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./boost_${MY_BOOST_MAJOR_VERSION}_${MY_BOOST_MINOR_VERSION}_${MY_BOOST_PATCH_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/boost_${MY_BOOST_MAJOR_VERSION}_${MY_BOOST_MINOR_VERSION}_${MY_BOOST_PATCH_VERSION}.tar.gz \ + && cd boost_${MY_BOOST_VERSION} \ + && echo "using gcc : gcc : ${MY_MINGW_PREFIX}-g++ ;" \ + >./user-config.jam \ + && ./bootstrap.sh \ + --with-libraries=atomic,chrono,date_time,filesystem,iostreams,locale,log,program_options,random,regex,serialization,system,test,thread \ + address-model=64 \ + architecture=x86 \ + cxxstd=${MY_CXX_STANDARD} \ + cxxstd-dialect=gnu \ + link=static,shared \ + target-os=windows \ + threading=multi \ + variant=release \ + && ./b2 \ + --openssldir=${MY_MINGW_DIR} \ + --prefix=${MY_MINGW_DIR} \ + --user-config=./user-config.jam \ + -j${MY_NUM_JOBS} \ + address-model=64 \ + architecture=x86 \ + cxxstd=${MY_CXX_STANDARD} \ + cxxstd-dialect=gnu \ + link=static,shared \ + target-os=windows \ + threading=multi \ + variant=release \ + install \ + && cd ${MY_WORKDIR} \ + && rm -r boost_${MY_BOOST_VERSION} \ + ; fi + +ARG OGG_VERSION +ENV MY_OGG_VERSION=${OGG_VERSION} +RUN if [ -f "/3rd_party/ogg-v${MY_OGG_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./ogg-v${MY_OGG_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/ogg-v${MY_OGG_VERSION}.tar.gz \ + && cd libogg-${MY_OGG_VERSION} \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_STATIC_LIBS=ON \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r libogg-${MY_OGG_VERSION} \ + ; fi + +ARG VORBIS_VERSION +ENV MY_VORBIS_VERSION=${VORBIS_VERSION} +RUN if [ -f "/3rd_party/vorbis-v${MY_VORBIS_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./vorbis-v${MY_VORBIS_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/vorbis-v${MY_VORBIS_VERSION}.tar.gz \ + && cd vorbis-${MY_VORBIS_VERSION} \ + && /3rd_party/vorbis_patch.sh /3rd_party . \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r vorbis-${MY_VORBIS_VERSION} \ + ; fi + +ARG FLAC_VERSION +ENV MY_FLAC_VERSION=${FLAC_VERSION} +RUN if [ -f "/3rd_party/flac-${MY_FLAC_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./flac-${MY_FLAC_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/flac-${MY_FLAC_VERSION}.tar.gz \ + && cd flac-${MY_FLAC_VERSION} \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DBUILD_DOCS=OFF \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_PROGRAMS=OFF \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_STATIC_LIBS=ON \ + -DBUILD_TESTING=OFF \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DINSTALL_MANPAGES=OFF \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r flac-${MY_FLAC_VERSION} \ + ; fi + +ARG SFML_VERSION +ENV MY_SFML_VERSION=${SFML_VERSION} +RUN if [ -f "/3rd_party/SFML-${MY_SFML_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./SFML-${MY_SFML_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/SFML-${MY_SFML_VERSION}.tar.gz \ + && cd SFML-${MY_SFML_VERSION} \ + && sed -i s/set_target_properties\(\$\{target\}\ PROPERTIES\ PREFIX\ \"\"\)// \ + cmake/Macros.cmake \ + && sed -i s/set_target_properties\(\$\{target\}\ PROPERTIES\ IMPORT_SUFFIX\ \"\.a\"\)// \ + cmake/Macros.cmake \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_STATIC_LIBS=ON \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_SYSTEM_PROCESSOR=AMD64 \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r SFML-${MY_SFML_VERSION} \ + ; fi + +ARG SPDLOG_VERSION +ENV MY_SPDLOG_VERSION=${SPDLOG_VERSION} +RUN if [ -f "/3rd_party/spdlog-${MY_SPDLOG_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./spdlog-${MY_SPDLOG_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/spdlog-${MY_SPDLOG_VERSION}.tar.gz \ + && cd spdlog-${MY_SPDLOG_VERSION} \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DSPDLOG_BUILD_EXAMPLE=OFF \ + -DSPDLOG_FMT_EXTERNAL=OFF \ + -DSPDLOG_FMT_EXTERNAL_HO=OFF \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r spdlog-${MY_SPDLOG_VERSION} \ + ; fi + +ARG CLI11_VERSION +ENV MY_CLI11_VERSION=${CLI11_VERSION} +RUN if [ -f "/3rd_party/CLI11-${MY_CLI11_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./CLI11-${MY_CLI11_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/CLI11-${MY_CLI11_VERSION}.tar.gz \ + && cd CLI11-${MY_CLI11_VERSION} \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DCLI11_BUILD_DOCS=OFF \ + -DCLI11_BUILD_EXAMPLES=OFF \ + -DCLI11_BUILD_TESTS=OFF \ + -DCLI11_INSTALL=ON \ + -DCLI11_SINGLE_FILE=ON \ + -DCLI11_WARNINGS_AS_ERRORS=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r CLI11-${MY_CLI11_VERSION} \ + ; fi + +ARG CURL_VERSION +ENV MY_CURL_VERSION=${CURL_VERSION} +ARG CURL2_VERSION +ENV MY_CURL2_VERSION=${CURL2_VERSION} +RUN if [ -f "/3rd_party/curl-${MY_CURL_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./curl-${MY_CURL_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/curl-${MY_CURL_VERSION}.tar.gz \ + && cd curl-curl-${MY_CURL2_VERSION} \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DBUILD_CURL_EXE=ON \ + -DBUILD_LIBCURL_DOCS=OFF \ + -DBUILD_MISC_DOCS=OFF \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_STATIC_LIBS=ON \ + -DBUILD_TESTING=OFF \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DCURL_CA_BUNDLE=./cacert.pem \ + -DCURL_CA_FALLBACK=ON \ + -DCURL_DISABLE_LDAP=ON \ + -DCURL_USE_LIBPSL=OFF \ + -DCURL_USE_LIBSSH2=OFF \ + -DCURL_USE_OPENSSL=ON \ + -DCURL_ZLIB=ON \ + -DENABLE_CURL_MANUAL=OFF \ + -DENABLE_THREADED_RESOLVER=ON \ + -DOPENSSL_USE_STATIC_LIBS=ON \ + -DUSE_LIBIDN2=OFF \ + -DUSE_WIN32_CRYPTO=OFF \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r curl-curl-${MY_CURL2_VERSION} \ + ; fi + +ARG CPP_HTTPLIB_VERSION +ENV MY_CPP_HTTPLIB_VERSION=${CPP_HTTPLIB_VERSION} +RUN if [ -f "/3rd_party/cpp-httplib-${MY_CPP_HTTPLIB_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./cpp-httplib-${MY_CPP_HTTPLIB_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/cpp-httplib-${MY_CPP_HTTPLIB_VERSION}.tar.gz \ + && cd cpp-httplib-${MY_CPP_HTTPLIB_VERSION} \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DHTTPLIB_REQUIRE_BROTLI=OFF \ + -DHTTPLIB_REQUIRE_OPENSSL=ON \ + -DHTTPLIB_REQUIRE_ZLIB=ON \ + -DHTTPLIB_TEST=OFF \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r cpp-httplib-${MY_CPP_HTTPLIB_VERSION} \ + ; fi + +ARG FMT_VERSION +ENV MY_FMT_VERSION=${FMT_VERSION} +RUN if [ -f "/3rd_party/fmt-${MY_FMT_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./fmt-${MY_FMT_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/fmt-${MY_FMT_VERSION}.tar.gz \ + && cd fmt-${MY_FMT_VERSION} \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_STATIC_LIBS=ON \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DFMT_DOC=OFF \ + -DFMT_TEST=OFF \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r fmt-${MY_FMT_VERSION} \ + ; fi + +ARG GTEST_VERSION +ENV MY_GTEST_VERSION=${GTEST_VERSION} +RUN if [ -f "/3rd_party/googletest-${MY_GTEST_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./googletest-${MY_GTEST_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/googletest-${MY_GTEST_VERSION}.tar.gz \ + && cd googletest-${MY_GTEST_VERSION} \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r googletest-${MY_GTEST_VERSION} \ + ; fi + +ARG JSON_VERSION +ENV MY_JSON_VERSION=${JSON_VERSION} +RUN if [ -f "/3rd_party/json-${MY_JSON_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./json-${MY_JSON_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/json-${MY_JSON_VERSION}.tar.gz \ + && cd json-${MY_JSON_VERSION} \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DJSON_BuildTests=OFF \ + -DJSON_Install=ON \ + -DJSON_MultipleHeaders=OFF \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r json-${MY_JSON_VERSION} \ + ; fi + +ARG LIBEVENT_VERSION +ENV MY_LIBEVENT_VERSION=${LIBEVENT_VERSION} +RUN if [ -f "/3rd_party/libevent-${MY_LIBEVENT_VERSION}-stable.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./libevent-${MY_LIBEVENT_VERSION}-stable.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/libevent-${MY_LIBEVENT_VERSION}-stable.tar.gz \ + && cd libevent-release-${MY_LIBEVENT_VERSION}-stable \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DEVENT__DISABLE_OPENSSL=ON \ + -DEVENT__DISABLE_SAMPLES=ON \ + -DEVENT__DISABLE_TESTS=ON \ + -DEVENT__LIBRARY_TYPE=BOTH \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r libevent-release-${MY_LIBEVENT_VERSION}-stable \ + ; fi + +ARG LIBSODIUM_VERSION +ENV MY_LIBSODIUM_VERSION=${LIBSODIUM_VERSION} +RUN if [ -f "/3rd_party/libsodium-${MY_LIBSODIUM_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./libsodium-${MY_LIBSODIUM_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/libsodium-${MY_LIBSODIUM_VERSION}.tar.gz \ + && cd libsodium-${MY_LIBSODIUM_VERSION}-RELEASE \ + && CFLAGS="-O3 -fomit-frame-pointer -m64 -mtune=generic" ./configure \ + --enable-shared=yes \ + --enable-static=yes \ + --host=${MY_MINGW_PREFIX} \ + --prefix=${MY_MINGW_DIR} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r libsodium-${MY_LIBSODIUM_VERSION}-RELEASE \ + ; fi + +ARG NUSPELL_VERSION +ENV MY_NUSPELL_VERSION=${NUSPELL_VERSION} +RUN if [ -f "/3rd_party/nuspell-v${MY_NUSPELL_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./nuspell-v${MY_NUSPELL_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/nuspell-v${MY_NUSPELL_VERSION}.tar.gz \ + && cd nuspell-${MY_NUSPELL_VERSION} \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DBUILD_DOCS=OFF \ + -DBUILD_TESTING=OFF \ + -DBUILD_TOOLS=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r nuspell-${MY_NUSPELL_VERSION} \ + ; fi + +ARG PUGIXML_VERSION +ENV MY_PUGIXML_VERSION=${PUGIXML_VERSION} +RUN if [ -f "/3rd_party/pugixml-${MY_PUGIXML_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./pugixml-${MY_PUGIXML_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/pugixml-${MY_PUGIXML_VERSION}.tar.gz \ + && cd pugixml-${MY_PUGIXML_VERSION} \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r pugixml-${MY_PUGIXML_VERSION} \ + ; fi + +ARG ROCKSDB_VERSION +ENV MY_ROCKSDB_VERSION=${ROCKSDB_VERSION} +RUN if [ -f "/3rd_party/rocksdb-${MY_ROCKSDB_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./rocksdb-${MY_ROCKSDB_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/rocksdb-${MY_ROCKSDB_VERSION}.tar.gz \ + && cd rocksdb-${MY_ROCKSDB_VERSION} \ + && echo -e "add_definitions(-include cstdint)">>CMakeLists.txt \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DFAIL_ON_WARNINGS=OFF \ + -DPORTABLE=1 \ + -DROCKSDB_INSTALL_ON_WINDOWS=ON \ + -DWITH_BENCHMARK=OFF \ + -DWITH_BENCHMARK_TOOLS=OFF \ + -DWITH_CORE_TOOLS=OFF \ + -DWITH_EXAMPLES=OFF \ + -DWITH_GFLAGS=OFF \ + -DWITH_IOSTATS_CONTEXT=OFF \ + -DWITH_PERF_CONTEXT=OFF \ + -DWITH_TESTS=OFF \ + -DWITH_TOOLS=OFF \ + -DWITH_TRACE_TOOLS=OFF \ + -DWITH_ZLIB=ON \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r rocksdb-${MY_ROCKSDB_VERSION} \ + ; fi + +ARG SECP256K1_VERSION +ENV MY_SECP256K1_VERSION=${SECP256K1_VERSION} +RUN if [ -f "/3rd_party/secp256k1-${MY_SECP256K1_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./secp256k1-${MY_SECP256K1_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/secp256k1-${MY_SECP256K1_VERSION}.tar.gz \ + && cd secp256k1-${MY_SECP256K1_VERSION} \ + && ./autogen.sh && ./configure \ + --host=${MY_MINGW_PREFIX} \ + --enable-shared=no \ + --enable-static=yes \ + --enable-module-ecdh \ + --enable-module-recovery \ + --prefix=${MY_MINGW_DIR} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r secp256k1-${MY_SECP256K1_VERSION} \ + ; fi + +ARG STDUUID_VERSION +ENV MY_STDUUID_VERSION=${STDUUID_VERSION} +RUN if [ -f "/3rd_party/stduuid-${MY_STDUUID_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./stduuid-${MY_STDUUID_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/stduuid-${MY_STDUUID_VERSION}.tar.gz \ + && cd stduuid-${MY_STDUUID_VERSION} \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DUUID_BUILD_TESTS=OFF \ + -DUUID_ENABLE_INSTALL=ON \ + -DUUID_USING_CXX20_SPAN=ON \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r stduuid-${MY_STDUUID_VERSION} \ + ; fi + +RUN if [ -f "/3rd_party/tiny-process-library.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./tiny-process-library.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/tiny-process-library.tar.gz \ + && cd tiny-process-library-master \ + && mkdir build \ + && cd build \ + && cmake .. \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_STATIC_LIBS=ON \ + -DBUILD_TESTING=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r tiny-process-library-master \ + ; fi + +ARG WXWIDGETS_VERSION +ENV MY_WXWIDGETS_VERSION=${WXWIDGETS_VERSION} +RUN if [ -f "/3rd_party/wxWidgets-${MY_WXWIDGETS_VERSION}.tar.bz2" ]; then \ + cd /3rd_party && sha256sum -c ./wxWidgets-${MY_WXWIDGETS_VERSION}.tar.bz2.sha256 && cd - \ + && tar xvjf /3rd_party/wxWidgets-${MY_WXWIDGETS_VERSION}.tar.bz2 \ + && cd wxWidgets-${MY_WXWIDGETS_VERSION} \ + && mkdir _build \ + && cd _build \ + && cmake .. \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_STATIC_LIBS=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DwxBUILD_MONOLITHIC=OFF \ + -DwxBUILD_SHARED=ON \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r wxWidgets-${MY_WXWIDGETS_VERSION} \ + ; fi + +ARG CXXOPTS_VERSION +ENV MY_CXXOPTS_VERSION=${CXXOPTS_VERSION} +RUN if [ -f "/3rd_party/cxxopts-v${MY_CXXOPTS_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./cxxopts-v${MY_CXXOPTS_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/cxxopts-v${MY_CXXOPTS_VERSION}.tar.gz \ + && cd cxxopts-${MY_CXXOPTS_VERSION} \ + && mkdir _build \ + && cd _build \ + && cmake .. \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_STATIC_LIBS=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DCXXOPTS_BUILD_EXAMPLES=OFF \ + -DCXXOPTS_BUILD_TESTS=OFF \ + -DCXXOPTS_ENABLE_INSTALL=ON \ + -DCXXOPTS_ENABLE_WARNINGS=OFF \ + -DCXXOPTS_USE_UNICODE_HELP=ON \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r cxxopts-${MY_CXXOPTS_VERSION} \ + ; fi + +ARG NANA_VERSION +ENV MY_NANA_VERSION=${NANA_VERSION} +RUN if [ -f "/3rd_party/nana-v${MY_NANA_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./nana-v${MY_NANA_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/nana-v${MY_NANA_VERSION}.tar.gz \ + && cd nana-${MY_NANA_VERSION} \ + && /3rd_party/nana_patch.sh /3rd_party . \ + && mkdir _build \ + && cd _build \ + && cmake .. \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_STATIC_LIBS=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=${MY_CXX_STANDARD} \ + -DCMAKE_INSTALL_PREFIX=${MY_MINGW_DIR} \ + -DCMAKE_TOOLCHAIN_FILE=${MY_TOOLCHAIN_FILE_CMAKE} \ + -DNANA_CMAKE_ENABLE_AUDIO=OFF \ + -DNANA_CMAKE_ENABLE_JPEG=ON \ + -DNANA_CMAKE_ENABLE_PNG=ON \ + -DNANA_CMAKE_INSTALL=ON \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r nana-${MY_NANA_VERSION} \ + ; fi + +ARG SDL_VERSION +ENV MY_SDL_VERSION=${SDL_VERSION} +RUN if [ -f "/3rd_party/sdl-${MY_SDL_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./sdl-${MY_SDL_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/sdl-${MY_SDL_VERSION}.tar.gz \ + && cd SDL-release-${MY_SDL_VERSION} \ + && ./configure \ + --host=${MY_MINGW_PREFIX} \ + --enable-shared=yes \ + --enable-static=yes \ + --prefix=${MY_MINGW_DIR} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r SDL-release-${MY_SDL_VERSION} \ + ; fi + +ARG LIBTASN_VERSION +ENV MY_LIBTASN_VERSION=${LIBTASN_VERSION} +RUN if [ -f "/3rd_party/libtasn1-${MY_LIBTASN_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./libtasn1-${MY_LIBTASN_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/libtasn1-${MY_LIBTASN_VERSION}.tar.gz \ + && cd libtasn1-${MY_LIBTASN_VERSION} \ + && ./configure \ + --disable-doc \ + --enable-static=yes \ + --enable-shared=no \ + --host=${MY_MINGW_PREFIX} \ + --prefix=${MY_MINGW_DIR} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r libtasn1-${MY_LIBTASN_VERSION} \ + && echo -e \ + "#!/bin/bash\n"\ + "\n"\ + "wine ${MY_MINGW_DIR}/bin/asn1Parser.exe \$@\n"\ + > ${MY_MINGW_DIR}/bin/asn1Parser \ + && chmod +x ${MY_MINGW_DIR}/bin/asn1Parser \ + ; fi + +ARG LIBICONV_VERSION +ENV MY_LIBICONV_VERSION=${LIBICONV_VERSION} +RUN if [ -f "/3rd_party/libiconv-${MY_LIBICONV_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./libiconv-${MY_LIBICONV_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/libiconv-${MY_LIBICONV_VERSION}.tar.gz \ + && cd libiconv-${MY_LIBICONV_VERSION} \ + && ./configure \ + --enable-static=yes \ + --enable-shared=no \ + --host=${MY_MINGW_PREFIX} \ + --prefix=${MY_MINGW_DIR} \ + && make -j${MY_NUM_JOBS} \ + && make install \ + && cd ${MY_WORKDIR} \ + && rm -r libiconv-${MY_LIBICONV_VERSION} \ + ; fi + +ARG LIBDSM_VERSION +ENV MY_LIBDSM_VERSION=${LIBDSM_VERSION} +RUN if [ -f "/3rd_party/libdsm-${MY_LIBDSM_VERSION}.tar.gz" ]; then \ + cd /3rd_party && sha256sum -c ./libdsm-${MY_LIBDSM_VERSION}.tar.gz.sha256 && cd - \ + && tar xvzf /3rd_party/libdsm-${MY_LIBDSM_VERSION}.tar.gz \ + && cd libdsm-${MY_LIBDSM_VERSION} \ + && /3rd_party/libdsm_patch.sh /3rd_party . \ + && meson setup \ + --bindir=${MY_MINGW_DIR}/bin \ + --cross-file ${MY_TOOLCHAIN_FILE_MESON} \ + --prefix=${MY_MINGW_DIR} \ + -Dbinaries=false \ + -Ddefault_library=static \ + _build \ + && meson compile \ + -C _build \ + && meson install \ + -C _build \ + && cd ${MY_WORKDIR} \ + && rm -r libdsm-${MY_LIBDSM_VERSION} \ + ; fi + +RUN (mv ${MY_MINGW_DIR}/lib/*.dll ${MY_MINGW_DIR}/bin || echo "no dll's found") \ + && chmod 0777 -R ${MY_MINGW_DIR} \ + && rm -rf /3rd_party diff --git a/monitarr/libmonitarr/include/data_db.hpp b/monitarr/libmonitarr/include/data_db.hpp new file mode 100644 index 0000000..bb333cb --- /dev/null +++ b/monitarr/libmonitarr/include/data_db.hpp @@ -0,0 +1,67 @@ +#ifndef LIBMONITARR_INCLUDE_DATA_DB_HPP_ +#define LIBMONITARR_INCLUDE_DATA_DB_HPP_ + +#include "utils/config.hpp" + +namespace monitarr { +struct data_entry final { + std::string download_id; + std::uint64_t last_check{}; + std::uint64_t size_left{}; +}; + +class data_db final { +public: + data_db() noexcept = default; + data_db(const data_db &) noexcept = default; + data_db(data_db &&) noexcept = default; + + auto operator=(const data_db &) noexcept -> data_db & = default; + auto operator=(data_db &&) noexcept -> data_db & = default; + + ~data_db() noexcept; + +private: + std::shared_ptr db_; + +private: + [[nodiscard]] auto + perform_action(std::string_view function_name, + std::function + action) const -> bool; + + [[nodiscard]] auto perform_action( + std::string_view function_name, + std::function action) -> bool; + +public: + void close(); + + [[nodiscard]] auto get(std::string_view download_id) const + -> std::optional; + + void open(std::string_view data_dir); + + void remove(std::string_view download_id); + + void set(const data_entry &entry); +}; +} // namespace monitarr + +NLOHMANN_JSON_NAMESPACE_BEGIN template <> +struct adl_serializer { + static void to_json(json &data, const monitarr::data_entry &value) { + data["download_id"] = value.download_id; + data["last_check"] = value.last_check; + data["size_left"] = value.size_left; + } + + static void from_json(const json &data, monitarr::data_entry &value) { + data.at("download_id").get_to(value.download_id); + data.at("last_check").get_to(value.last_check); + data.at("size_left").get_to(value.size_left); + } +}; +NLOHMANN_JSON_NAMESPACE_END + +#endif // LIBMONITARR_INCLUDE_DATA_DB_HPP_ diff --git a/monitarr/libmonitarr/include/initialize.hpp b/monitarr/libmonitarr/include/initialize.hpp new file mode 100644 index 0000000..243287e --- /dev/null +++ b/monitarr/libmonitarr/include/initialize.hpp @@ -0,0 +1,10 @@ +#ifndef LIBMONITARR_INCLUDE_INITIALIZE_HPP_ +#define LIBMONITARR_INCLUDE_INITIALIZE_HPP_ + +namespace monitarr { +void project_cleanup(); + +[[nodiscard]] auto project_initialize() -> bool; +} // namespace monitarr + +#endif // LIBMONITARR_INCLUDE_INITIALIZE_HPP_ diff --git a/monitarr/libmonitarr/include/settings.hpp b/monitarr/libmonitarr/include/settings.hpp new file mode 100644 index 0000000..830588f --- /dev/null +++ b/monitarr/libmonitarr/include/settings.hpp @@ -0,0 +1,89 @@ +#ifndef LIBMONITARR_INCLUDE_SETTINGS_HPP_ +#define LIBMONITARR_INCLUDE_SETTINGS_HPP_ + +#include "utils/config.hpp" + +namespace monitarr { +inline constexpr const auto default_interval{ + std::chrono::minutes{5U}, +}; + +inline constexpr const auto default_timeout{ + std::chrono::minutes{24U * 60U}, +}; + +struct server_cfg final { + std::string id; + std::string api_key; + std::string api_version; + std::chrono::minutes timeout{default_timeout}; + std::string url; +}; + +struct app_config final { + std::chrono::minutes check_interval{default_interval}; + std::vector server_list{ + server_cfg{ + "lidarr", + "7228e4739091469db81acfe2b97aa973", + "v1", + std::chrono::minutes(1440), + "http://192.168.1.60:8686", + }, + server_cfg{ + "radarr", + "affa8c547cee48e0b8c49994c0aaa931", + "v3", + std::chrono::minutes(1440), + "http://192.168.1.60:7878", + }, + server_cfg{ + "sonarr", + "ead2cdf34f774ad1b8185271c733c5ad", + "v3", + std::chrono::minutes(1440), + "http://192.168.1.60:8989", + }, + }; + + void load(std::string_view file_path); + + void save(std::string_view file_path) const; +}; +} // namespace monitarr + +NLOHMANN_JSON_NAMESPACE_BEGIN +template <> struct adl_serializer { + static void to_json(json &data, const monitarr::server_cfg &value) { + data["id"] = value.id; + data["api_key"] = value.api_key; + data["api_version"] = value.api_version; + data["timeout_minutes"] = value.timeout.count(); + data["url"] = value.url; + } + + static void from_json(const json &data, monitarr::server_cfg &value) { + data.at("id").get_to(value.id); + data.at("api_key").get_to(value.api_key); + data.at("api_version").get_to(value.api_version); + value.timeout = + std::chrono::minutes(data.at("timeout_minutes").get()); + data.at("url").get_to(value.url); + } +}; + +template <> struct adl_serializer { + static void to_json(json &data, const monitarr::app_config &value) { + data["check_interval_minutes"] = value.check_interval.count(); + data["server_list"] = value.server_list; + } + + static void from_json(const json &data, monitarr::app_config &value) { + value.check_interval = std::chrono::minutes( + data.at("check_interval_minutes").get()); + data.at("server_list").get_to(value.server_list); + } +}; +NLOHMANN_JSON_NAMESPACE_END + +#endif // LIBMONITARR_INCLUDE_SETTINGS_HPP_ diff --git a/monitarr/libmonitarr/include/version.hpp b/monitarr/libmonitarr/include/version.hpp new file mode 100644 index 0000000..e841751 --- /dev/null +++ b/monitarr/libmonitarr/include/version.hpp @@ -0,0 +1,12 @@ +#ifndef LIBMONITARR_INCLUDE_VERSION_HPP_ +#define LIBMONITARR_INCLUDE_VERSION_HPP_ + +#include + +namespace monitarr { +[[nodiscard]] auto project_get_git_rev() -> std::string_view; + +[[nodiscard]] auto project_get_version() -> std::string_view; +} // namespace monitarr + +#endif // LIBMONITARR_INCLUDE_VERSION_HPP_ diff --git a/monitarr/libmonitarr/src/data_db.cpp b/monitarr/libmonitarr/src/data_db.cpp new file mode 100644 index 0000000..5ff6ec5 --- /dev/null +++ b/monitarr/libmonitarr/src/data_db.cpp @@ -0,0 +1,131 @@ +#include "data_db.hpp" + +#include "utils/error.hpp" +#include "utils/path.hpp" + +namespace monitarr { +data_db::~data_db() noexcept { close(); } + +void data_db::close() { + if (db_) { + db_->Close(); + db_.reset(); + } +} + +auto data_db::get(std::string_view download_id) const + -> std::optional { + MONITARR_USES_FUNCTION_NAME(); + + std::optional ret; + if (not perform_action( + function_name, + [&download_id, &ret](rocksdb::TransactionDB *txn_db) -> auto { + std::string value; + auto res = txn_db->Get(rocksdb::ReadOptions{}, download_id, &value); + if (res.ok()) { + ret = nlohmann::json::parse(value).get(); + } + + return res.IsNotFound() ? rocksdb::Status{} : res; + })) { + fmt::println("failed to get|{}", download_id); + } + + return ret; +} + +void data_db::open(std::string_view data_dir) { + auto db_path = utils::path::combine(data_dir, {"state_db"}); + fmt::println("opening database|{}", db_path); + + rocksdb::Options options{}; + options.create_if_missing = true; + options.create_missing_column_families = true; + options.db_log_dir = data_dir; + options.keep_log_file_num = 10; + + rocksdb::TransactionDB *ptr{}; + auto status = rocksdb::TransactionDB::Open( + options, rocksdb::TransactionDBOptions{}, db_path, &ptr); + if (not status.ok()) { + throw std::runtime_error(fmt::format("failed to open database|{}|{}", + db_path, status.ToString())); + } + + db_ = std::shared_ptr(ptr); +} + +auto data_db::perform_action( + std::string_view function_name, + std::function action) const + -> bool { + try { + auto res = action(db_.get()); + if (not res.ok()) { + utils::error::handle_error(function_name, res.ToString()); + } + + return res.ok(); + } catch (const std::exception &ex) { + utils::error::handle_exception(function_name, ex); + } + + return false; +} + +auto data_db::perform_action( + std::string_view function_name, + std::function action) -> bool { + std::unique_ptr txn{ + db_->BeginTransaction(rocksdb::WriteOptions{}, + rocksdb::TransactionOptions{}), + }; + + try { + auto res = action(txn.get()); + if (res.ok()) { + auto commit_res = txn->Commit(); + if (commit_res.ok()) { + return true; + } + + utils::error::handle_error(function_name, + "rocksdb commit failed|" + res.ToString()); + return false; + } + + utils::error::handle_error(function_name, + "rocksdb action failed|" + res.ToString()); + } catch (const std::exception &ex) { + utils::error::handle_exception(function_name, ex); + } + + auto rollback_res = txn->Rollback(); + utils::error::handle_error(function_name, "rocksdb rollback failed|" + + rollback_res.ToString()); + return false; +} + +void data_db::remove(std::string_view download_id) { + MONITARR_USES_FUNCTION_NAME(); + + if (not perform_action(function_name, + [&download_id](rocksdb::Transaction *txn) -> auto { + return txn->Delete(download_id); + })) { + fmt::println("failed to remove|{}", download_id); + } +} + +void data_db::set(const data_entry &entry) { + MONITARR_USES_FUNCTION_NAME(); + + if (not perform_action( + function_name, [&entry](rocksdb::Transaction *txn) -> auto { + return txn->Put(entry.download_id, nlohmann::json(entry).dump()); + })) { + fmt::println("failed to set|{}", nlohmann::json(entry).dump(2)); + } +} +} // namespace monitarr diff --git a/monitarr/libmonitarr/src/initialize.cpp b/monitarr/libmonitarr/src/initialize.cpp new file mode 100644 index 0000000..78e5601 --- /dev/null +++ b/monitarr/libmonitarr/src/initialize.cpp @@ -0,0 +1,90 @@ +#if defined(PROJECT_ENABLE_CURL) +#include "curl/curl.h" +#endif // defined(PROJECT_ENABLE_CURL) + +#if defined(PROJECT_ENABLE_OPENSSL) +#include "openssl/ssl.h" +#endif // defined(PROJECT_ENABLE_OPENSSL) + +#if defined(PROJECT_REQUIRE_ALPINE) && !defined(PROJECT_IS_MINGW) +#include +#include +#include +#endif // defined(PROJECT_REQUIRE_ALPINE) && !defined(PROJECT_IS_MINGW) + +#if defined(PROJECT_ENABLE_LIBSODIUM) +#include "sodium.h" +#endif // defined(PROJECT_ENABLE_LIBSODIUM) + +#if defined(PROJECT_ENABLE_SQLITE) +#include "sqlite3.h" +#endif // defined(PROJECT_ENABLE_SQLITE) + +#include "initialize.hpp" + +#if defined(PROJECT_REQUIRE_ALPINE) && !defined(PROJECT_IS_MINGW) +#include "utils/path.hpp" +#endif // defined(PROJECT_REQUIRE_ALPINE) && !defined (PROJECT_IS_MINGW) + +namespace monitarr { +auto project_initialize() -> bool { +#if defined(PROJECT_REQUIRE_ALPINE) && !defined(PROJECT_IS_MINGW) + { + static constexpr const auto guard_size{4096U}; + static constexpr const auto stack_size{8U * 1024U * 1024U}; + + pthread_attr_t attr{}; + pthread_attr_setstacksize(&attr, stack_size); + pthread_attr_setguardsize(&attr, guard_size); + pthread_setattr_default_np(&attr); + + setenv("ICU_DATA", utils::path::combine(".", {"/icu"}).c_str(), 1); + } +#endif // defined(PROJECT_REQUIRE_ALPINE) && !defined(PROJECT_IS_MINGW) + +#if defined(PROJECT_ENABLE_LIBSODIUM) + { + if (sodium_init() == -1) { + return false; + } + } +#endif // defined(PROJECT_ENABLE_LIBSODIUM) + +#if defined(PROJECT_ENABLE_OPENSSL) + { SSL_library_init(); } +#endif // defined(PROJECT_ENABLE_OPENSSL) + +#if defined(PROJECT_ENABLE_CURL) + { + auto res = curl_global_init(CURL_GLOBAL_ALL); + if (res != 0) { + return false; + } + } +#endif // defined(PROJECT_ENABLE_CURL) + +#if defined(PROJECT_ENABLE_SQLITE) + { + auto res = sqlite3_initialize(); + if (res != SQLITE_OK) { +#if defined(PROJECT_ENABLE_CURL) + curl_global_cleanup(); +#endif // defined(PROJECT_ENABLE_CURL) + return false; + } + } +#endif // defined(PROJECT_ENABLE_SQLITE) + + return true; +} + +void project_cleanup() { +#if defined(PROJECT_ENABLE_CURL) + curl_global_cleanup(); +#endif // defined(PROJECT_ENABLE_CURL) + +#if defined(PROJECT_ENABLE_SQLITE) + sqlite3_shutdown(); +#endif // defined(PROJECT_ENABLE_SQLITE) +} +} // namespace monitarr diff --git a/monitarr/libmonitarr/src/settings.cpp b/monitarr/libmonitarr/src/settings.cpp new file mode 100644 index 0000000..8db26ba --- /dev/null +++ b/monitarr/libmonitarr/src/settings.cpp @@ -0,0 +1,18 @@ +#include "settings.hpp" + +#include "utils/file.hpp" + +namespace monitarr { +void app_config::load(std::string_view file_path) { + nlohmann::json data; + if (utils::file::read_json_file(file_path, data)) { + *this = data.get(); + } +} + +void app_config::save(std::string_view file_path) const { + if (utils::file::write_json_file(file_path, nlohmann::json(*this))) { + return; + } +} +} // namespace monitarr diff --git a/monitarr/monitarr/main.cpp b/monitarr/monitarr/main.cpp new file mode 100644 index 0000000..db6b4a5 --- /dev/null +++ b/monitarr/monitarr/main.cpp @@ -0,0 +1,260 @@ +#if defined(PROJECT_ENABLE_BACKWARD_CPP) +#include "backward.hpp" +#endif // defined(PROJECT_ENABLE_BACKWARD_CPP) + +#include + +#include "initialize.hpp" + +#include "data_db.hpp" +#include "settings.hpp" +#include "utils/common.hpp" +#include "utils/config.hpp" +#include "utils/file.hpp" +#include "utils/path.hpp" +#include "utils/time.hpp" +#include "utils/unix.hpp" +#include "utils/windows.hpp" + +namespace monitarr { +static void remove_stalled(std::string_view download_id, std::string_view title, + std::uint64_t episode_id, std::uint64_t movie_id, + const server_cfg &server, data_db &state_db) { + fmt::println("remove and block {}|{}", download_id, title); + state_db.remove(download_id); + + httplib::Client cli{server.url}; + cli.set_default_headers({ + {"X-Api-Key", server.api_key}, + }); + + auto response = cli.Delete( + fmt::format("/api/{}/queue/{}?blocklist=true&skipRedownload=false", + server.api_version, + utils::string::split(download_id, '/', false).at(1U))); + if (response->status != httplib::StatusCode::OK_200) { + fmt::println("remove and block result|{}|{}", server.url, response->status); + return; + } + + if (utils::string::contains("radarr", server.id)) { + nlohmann::json data({ + {"name", "MoviesSearch"}, + {"movieIds", {movie_id}}, + }); + + response = cli.Post("/api/{}/command", data.dump(), "application/json"); + if (response->status != httplib::StatusCode::OK_200) { + fmt::println("failed to search radarr|{}|{}", server.url, + response->status); + } + return; + } + + if (utils::string::contains("sonarr", server.id)) { + nlohmann::json data({ + {"name", "EpisodeSearch"}, + {"episodeIds", {episode_id}}, + }); + + response = cli.Post("/api/{}/command", data.dump(), "application/json"); + if (response->status != httplib::StatusCode::OK_200) { + fmt::println("failed to search sonarr|{}|{}", server.url, + response->status); + } + return; + } +} + +static void check_server(const server_cfg &server, data_db &state_db) { + httplib::Client cli{server.url}; + cli.set_default_headers({ + {"X-Api-Key", server.api_key}, + }); + + std::uint16_t page{0U}; + while (++page != 0U) { + httplib::Params params; + params.emplace("page", std::to_string(page)); + params.emplace("pageSize", "50"); + + auto response = + cli.Get(fmt::format("/api/{}/queue", server.api_version), params, {}); + if (response->status != httplib::StatusCode::OK_200) { + fmt::println("check server request failed|{}|{}", server.url, + response->status); + break; + } + + auto json_data = nlohmann::json::parse(response->body); + if (json_data.at("page").get() != page) { + break; + } + + auto now = utils::time::get_time_now(); + + for (const auto &record : json_data.at("records")) { + auto download_id = fmt::format( + "{}/{}", server.id, record.at("downloadId").get()); + auto episode_id = record.contains("episodeId") + ? record["episodeId"].get() + : std::uint64_t{0U}; + auto movie_id = record.contains("movieId") + ? record["movieId"].get() + : std::uint64_t{0U}; + auto size_left = record.at("sizeleft").get(); + auto title = record.at("title").get(); + + auto data = state_db.get(download_id); + const auto update_entry = [&download_id, &now, &size_left, &state_db, + &title, url = server.url]() { + if (size_left == 0U) { + state_db.remove(download_id); + return; + } + + fmt::println("updating {}|{}|{}|{}", download_id, title, now, + size_left); + state_db.set(data_entry{ + download_id, + now, + size_left, + }); + }; + + if (data.has_value()) { + if (std::chrono::nanoseconds(now - data->last_check) >= + server.timeout) { + if (size_left == data->size_left) { + remove_stalled(download_id, title, episode_id, movie_id, server, + state_db); + } else { + update_entry(); + } + } else if (size_left == 0U) { + state_db.remove(download_id); + } + } else { + update_entry(); + } + } + } +} + +[[nodiscard]] static auto load_config(std::string &cfg_file) -> app_config { + auto cfg_dir = utils::get_environment_variable("MONITARR_CFG_DIR"); + if (cfg_dir.empty()) { + cfg_dir = utils::path::combine(".", {"config"}); + } + + if (not utils::file::directory{cfg_dir}.create_directory()) { + throw std::runtime_error(fmt::format("failed to create config dir|{}", + cfg_dir, + utils::get_last_error_code())); + } + + cfg_file = utils::path::combine(cfg_dir, {"monitarr.json"}); + + fmt::println("loading config|{}", cfg_file); + app_config cfg{}; + cfg.load(cfg_file); + return cfg; +} + +[[nodiscard]] static auto load_db() -> data_db { + auto data_dir = utils::get_environment_variable("MONITARR_DATA_DIR"); + if (data_dir.empty()) { + data_dir = utils::path::combine(".", {"data"}); + } + + if (not utils::file::directory{data_dir}.create_directory()) { + throw std::runtime_error(fmt::format("failed to create data dir|{}", + data_dir, + utils::get_last_error_code())); + } + + data_db state_db{}; + state_db.open(data_dir); + return state_db; +} +} // namespace monitarr + +using namespace monitarr; + +auto main(int /* argc */, char ** /* argv */) -> int { + MONITARR_USES_FUNCTION_NAME(); + +#if defined(PROJECT_ENABLE_BACKWARD_CPP) + static backward::SignalHandling sh; +#endif // defined(PROJECT_ENABLE_BACKWARD_CPP) + + if (not monitarr::project_initialize()) { + return -1; + } + + static std::mutex mtx; + static std::condition_variable notify; + static stop_type stop_requested{false}; + + static const auto quit_handler = [](int sig) { + fmt::println("stop requested|{}", sig); + stop_requested = true; + + mutex_lock lock(mtx); + notify.notify_all(); + }; + + std::signal(SIGINT, quit_handler); + std::signal(SIGQUIT, quit_handler); + std::signal(SIGTERM, quit_handler); + + auto ret{0}; + + try { + std::string cfg_file; + auto cfg{load_config(cfg_file)}; + auto state_db{load_db()}; + + if (cfg.server_list.empty()) { + fmt::println("no servers have been configured"); + ret = 3; + } else { + while (not stop_requested) { + std::for_each(std::execution::par, cfg.server_list.begin(), + cfg.server_list.end(), [&state_db](auto &&server) { + if (stop_requested) { + return; + } + + try { + check_server(server, state_db); + } catch (const std::exception &ex) { + utils::error::handle_exception(function_name, ex); + } catch (...) { + utils::error::handle_exception(function_name); + } + }); + unique_mutex_lock lock(mtx); + if (stop_requested) { + continue; + } + + fmt::println("waiting for next check|{}", cfg.check_interval); + notify.wait_for(lock, cfg.check_interval); + } + } + + cfg.save(cfg_file); + state_db.close(); + } catch (const std::exception &ex) { + utils::error::handle_exception(function_name, ex); + ret = 2; + } catch (...) { + utils::error::handle_exception(function_name); + ret = 2; + } + + fmt::println("terminating application|{}", ret); + monitarr::project_cleanup(); + return ret; +} diff --git a/monitarr/monitarr_test/main.cpp b/monitarr/monitarr_test/main.cpp new file mode 100644 index 0000000..13b8a17 --- /dev/null +++ b/monitarr/monitarr_test/main.cpp @@ -0,0 +1,29 @@ +#if defined(PROJECT_ENABLE_BACKWARD_CPP) +#include "backward.hpp" +#endif // defined(PROJECT_ENABLE_BACKWARD_CPP) + +#include "gtest/gtest.h" + +#include "initialize.hpp" +#include "utils/config.hpp" + +using namespace monitarr; + +int PROJECT_TEST_RESULT{0}; + +auto main(int argc, char **argv) -> int { +#if defined(PROJECT_ENABLE_BACKWARD_CPP) + static backward::SignalHandling sh; +#endif + + if (not monitarr::project_initialize()) { + return -1; + } + + ::testing::InitGoogleTest(&argc, argv); + PROJECT_TEST_RESULT = RUN_ALL_TESTS(); + + monitarr::project_cleanup(); + + return PROJECT_TEST_RESULT; +} diff --git a/monitarr/version.cpp.in b/monitarr/version.cpp.in new file mode 100644 index 0000000..017ed9b --- /dev/null +++ b/monitarr/version.cpp.in @@ -0,0 +1,14 @@ +#include "version.hpp" + +namespace { +static constexpr const std::string_view git_rev = "@PROJECT_GIT_REV@"; +static constexpr const std::string_view version = + "@PROJECT_MAJOR_VERSION@.@PROJECT_MINOR_VERSION@.@PROJECT_REVISION_VERSION@" + "-@PROJECT_RELEASE_ITER@"; +} // namespace + +namespace monitarr { + auto project_get_git_rev() -> std::string_view { return git_rev; } + + auto project_get_version() -> std::string_view { return version; } +} // namespace monitarr diff --git a/monitarr/version.rc.in b/monitarr/version.rc.in new file mode 100644 index 0000000..be5b7fd --- /dev/null +++ b/monitarr/version.rc.in @@ -0,0 +1,64 @@ +#include +#define VER_FILEVERSION @PROJECT_MAJOR_VERSION@,@PROJECT_MINOR_VERSION@,@PROJECT_REVISION_VERSION@,@PROJECT_RELEASE_NUM@ +#define VER_FILEVERSION_STR "@PROJECT_MAJOR_VERSION@.@PROJECT_MINOR_VERSION@.@PROJECT_REVISION_VERSION@-@PROJECT_RELEASE_ITER@_@PROJECT_GIT_REV@\0" + +#define VER_PRODUCTVERSION @PROJECT_MAJOR_VERSION@,@PROJECT_MINOR_VERSION@,@PROJECT_REVISION_VERSION@,@PROJECT_RELEASE_NUM@ +#define VER_PRODUCTVERSION_STR "@PROJECT_MAJOR_VERSION@.@PROJECT_MINOR_VERSION@.@PROJECT_REVISION_VERSION@-@PROJECT_RELEASE_ITER@_@PROJECT_GIT_REV@\0" + +#define VER_COMPANYNAME_STR "@PROJECT_COMPANY_NAME@\0" +#define VER_INTERNALNAME_STR "@PROJECT_NAME@ @PROJECT_MAJOR_VERSION@.@PROJECT_MINOR_VERSION@.@PROJECT_REVISION_VERSION@-@PROJECT_RELEASE_ITER@_@PROJECT_GIT_REV@\0" +#define VER_LEGALCOPYRIGHT_STR "@PROJECT_COPYRIGHT@\0" +#define VER_ORIGINALFILENAME_STR "@PROJECT_NAME@.exe\0" +#define VER_LEGALTRADEMARKS1_STR "\0" +#define VER_LEGALTRADEMARKS2_STR "\0" +#define VER_FILEDESCRIPTION_STR "@PROJECT_DESC@\0" +#define VER_PRODUCTNAME_STR "@PROJECT_NAME@ @PROJECT_MAJOR_VERSION@.@PROJECT_MINOR_VERSION@.@PROJECT_REVISION_VERSION@-@PROJECT_RELEASE_ITER@_@PROJECT_GIT_REV@\0" + +#ifdef DEBUG +#define VER_DEBUG VS_FF_DEBUG +#else +#define VER_DEBUG 0 +#endif + +#define VER_PRERELEASE @PROJECT_PRERELEASE@ + + +VS_VERSION_INFO VERSIONINFO +FILEVERSION VER_FILEVERSION +PRODUCTVERSION VER_PRODUCTVERSION +FILEFLAGSMASK (VS_FF_DEBUG|VS_FF_PRERELEASE) +FILEFLAGS (VER_DEBUG|VER_PRERELEASE) +FILEOS VOS__WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", VER_COMPANYNAME_STR + VALUE "FileDescription", VER_FILEDESCRIPTION_STR + VALUE "FileVersion", VER_FILEVERSION_STR + VALUE "InternalName", VER_INTERNALNAME_STR + VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR + VALUE "LegalTrademarks1", VER_LEGALTRADEMARKS1_STR + VALUE "LegalTrademarks2", VER_LEGALTRADEMARKS2_STR + VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR + VALUE "ProductName", VER_PRODUCTNAME_STR + VALUE "ProductVersion", VER_PRODUCTVERSION_STR + END + END + + BLOCK "VarFileInfo" + BEGIN + /* The following line should only be modified for localized versions. */ + /* It consists of any number of WORD,WORD pairs, with each pair */ + /* describing a language,codepage combination supported by the file. */ + /* */ + /* For example, a file might have values "0x409,1252" indicating that it */ + /* supports English language (0x409) in the Windows ANSI codepage (1252). */ + + VALUE "Translation", 0x409, 1252 + + END +END diff --git a/project.cmake b/project.cmake new file mode 100644 index 0000000..7727aab --- /dev/null +++ b/project.cmake @@ -0,0 +1,5 @@ +add_project_library(lib${PROJECT_NAME} "" "" "${PROJECT_ADDITIONAL_SOURCES}") + +add_project_executable(${PROJECT_NAME} lib${PROJECT_NAME} lib${PROJECT_NAME}) + +add_project_test_executable(${PROJECT_NAME}_test lib${PROJECT_NAME} lib${PROJECT_NAME}) diff --git a/scripts/copy_mingw64_deps.sh b/scripts/copy_mingw64_deps.sh new file mode 100755 index 0000000..b8bb231 --- /dev/null +++ b/scripts/copy_mingw64_deps.sh @@ -0,0 +1,206 @@ +#!/bin/bash + +PROJECT_MINGW64_COPY_DEPENDENCIES=() + +PROJECT_SCRIPTS_DIR=$(realpath "$0") +PROJECT_SCRIPTS_DIR=$(dirname "${PROJECT_SCRIPTS_DIR}") +. "${PROJECT_SCRIPTS_DIR}/env.sh" "$1" "$2" "$3" "$4" "$5" 1>/dev/null 2>&1 + +if [ "${PROJECT_IS_MINGW}" == "1" ] && [ "${PROJECT_STATIC_LINK}" == "OFF" ]; then + mkdir -p "${PROJECT_DIST_DIR}" + rm -f ${PROJECT_DIST_DIR}/*.dll + + PROJECT_MINGW64_COPY_DEPENDENCIES+=( + /mingw64/bin/libgcc_s_seh-1.dll + /mingw64/bin/libstdc++-6.dll + /mingw64/bin/libwinpthread-1.dll + /mingw64/bin/libzlib1.dll + /mingw64/bin/zlib1.dll + ) + + if [ "${PROJECT_ENABLE_BACKWARD_CPP}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/msvcr90.dll) + fi + + if [ "${PROJECT_ENABLE_BOOST}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libboost*.dll) + fi + + if [ "${PROJECT_ENABLE_CLI11}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libcli11*.dll) + fi + + if [ "${PROJECT_ENABLE_CURL}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=( + /mingw64/bin/libcurl*.dll + /mingw64/bin/libnghttp2-*.dll + ) + fi + + if [ "${PROJECT_ENABLE_FLAC}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libFLAC*.dll) + fi + + if [ "${PROJECT_ENABLE_FMT}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libfmt*.dll) + fi + + if [ "${PROJECT_ENABLE_FONTCONFIG}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=( + /mingw64/bin/libexpat*.dll + /mingw64/bin/libfontconfig*.dll + ) + fi + + if [ "${PROJECT_ENABLE_FREETYPE2}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libfreetype*.dll) + fi + + if [ "${PROJECT_ENABLE_LIBDSM}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libdsm.dll) + fi + + if [ "${PROJECT_ENABLE_LIBEVENT}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libevent*.dll) + fi + + if [ "${PROJECT_ENABLE_LIBICONV}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libiconv.dll) + fi + + if [ "${PROJECT_ENABLE_LIBJPEG_TURBO}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libjpeg*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libturbo*.dll) + fi + + if [ "${PROJECT_ENABLE_LIBPNG}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libpng*.dll) + fi + + if [ "${PROJECT_ENABLE_LIBSODIUM}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libsodium*.dll) + fi + + if [ "${PROJECT_ENABLE_NANA}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libnana*.dll) + fi + + if [ "${PROJECT_ENABLE_NUSPELL}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libnuspell-*.dll) + fi + + if [ "${PROJECT_ENABLE_OGG}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libogg*.dll) + fi + + if [ "${PROJECT_ENABLE_OPENAL}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libopenal*.dll) + fi + + if [ "${PROJECT_ENABLE_OPENSSL}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=( + /mingw64/bin/libssl*.dll + /mingw64/bin/libcrypt*.dll + ) + fi + + if [ "${PROJECT_ENABLE_PUGIXML}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libpugi*.dll) + fi + + if [ "${PROJECT_ENABLE_SDL}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/SDL2*.dll) + fi + + if [ "${PROJECT_ENABLE_SECP256K1}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libsecp256k1*.dll) + fi + + if [ "${PROJECT_ENABLE_SFML}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libsfml*.dll) + fi + + if [ "${PROJECT_ENABLE_SPDLOG}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libspdlog*.dll) + fi + + if [ "${PROJECT_ENABLE_SQLITE}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libsqlite3-*.dll) + fi + + if [ "${PROJECT_ENABLE_TASN}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libtasn1.dll) + fi + + if [ "${PROJECT_ENABLE_TESTING}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=( + /mingw64/bin/libgtest*.dll + /mingw64/bin/libgmock*.dll + ) + fi + + if [ "${PROJECT_ENABLE_TPL}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libtiny-process*.dll) + fi + + if [ "${PROJECT_ENABLE_VORBIS}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libvorbis*.dll) + fi + + if [ "${PROJECT_ENABLE_WXWIDGETS}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libLerc.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libsharpyuv-*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libdeflate.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libglib-2*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libgraphite2.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libharfbuzz-*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libjbig-*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libpcre2-*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libtiff-*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libwebp-*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libwxbase*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/libwxm*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/wxbase*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/bin/wxm*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/lib/gcc_x64_dll/libwxbase*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/lib/gcc_x64_dll/libwxm*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/lib/gcc_x64_dll/wxbase*.dll) + PROJECT_MINGW64_COPY_DEPENDENCIES+=(/mingw64/lib/gcc_x64_dll/wxm*.dll) + fi + + if [ "${PROJECT_ENABLE_SDL}" == "ON" ] || [ "${PROJECT_ENABLE_SFML}" == "ON" ] || + [ "${PROJECT_ENABLE_WXWIDGETS}" == "ON" ] || [ "${PROJECT_ENABLE_NANA}" == "ON" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=( + /mingw64/bin/libOpenCL*.dll + /mingw64/bin/libopencl*.dll + /mingw64/bin/libgallium_wgl.dll + /mingw64/bin/libva*.dll + /mingw64/bin/opengl*.dll + /mingw64/bin/vulkan*.dll + ) + fi + + rsync -av --progress ${PROJECT_EXTERNAL_BUILD_ROOT}/bin/*.dll "${PROJECT_DIST_DIR}/" + rsync -av --progress ${PROJECT_EXTERNAL_BUILD_ROOT}/lib/*.dll "${PROJECT_DIST_DIR}/" + if [ "${PROJECT_ENABLE_WXWIDGETS}" == "ON" ]; then + rsync -av --progress ${PROJECT_EXTERNAL_BUILD_ROOT}/lib/gcc_x64_dll/*.dll "${PROJECT_DIST_DIR}/" + fi +fi + +if [ "${PROJECT_ENABLE_WINFSP}" == "ON" ]; then + if [ "${PROJECT_BUILD_ARCH}" == "x86_64" ]; then + WINFSP_DLL_PART=x64 + fi + + if [ "${WINFSP_DLL_PART}" != "" ]; then + PROJECT_MINGW64_COPY_DEPENDENCIES+=(${PROJECT_3RD_PARTY_DIR}/winfsp-2.0/bin/winfsp-${WINFSP_DLL_PART}.dll) + fi +fi + +if [ "${PROJECT_ENABLE_VLC}" == "ON" ]; then + rsync -av --progress ${PROJECT_3RD_PARTY_DIR}/vlc/ "${PROJECT_DIST_DIR}/vlc/" +fi + +for PROJECT_DEPENDENCY in "${PROJECT_MINGW64_COPY_DEPENDENCIES[@]}"; do + rsync -av --progress ${PROJECT_DEPENDENCY} "${PROJECT_DIST_DIR}/" +done diff --git a/scripts/create_containers.sh b/scripts/create_containers.sh new file mode 100755 index 0000000..78155f7 --- /dev/null +++ b/scripts/create_containers.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +PROJECT_SCRIPTS_DIR=$(realpath "$0") +PROJECT_SCRIPTS_DIR=$(dirname "${PROJECT_SCRIPTS_DIR}") +. "${PROJECT_SCRIPTS_DIR}/env.sh" "$1" "$2" "$3" "$4" "$5" 1 1>/dev/null 2>&1 + +if [ -f "${PROJECT_SCRIPTS_DIR}/cleanup.sh" ]; then + . "${PROJECT_SCRIPTS_DIR}/cleanup.sh" "$1" "$2" "$3" "$4" "$5" + rm ${PROJECT_SCRIPTS_DIR}/cleanup.* +fi + +function create_containers() { + BUILD_TYPE=$1 + + for FILE in "${PROJECT_SOURCE_DIR}/docker/${BUILD_TYPE}/*"; do + DOCKER_CREATE_ONLY=1 + DOCKER_NAME=$(basename ${FILE}) + DOCKER_TAG=${PROJECT_NAME}:${DOCKER_NAME} + + . "${PROJECT_SCRIPTS_DIR}/docker_common.sh" + done +} + +create_containers aarch64 +create_containers x86_64 diff --git a/scripts/deliver.cmd b/scripts/deliver.cmd new file mode 100644 index 0000000..9fe7bf0 --- /dev/null +++ b/scripts/deliver.cmd @@ -0,0 +1,13 @@ +@echo off + +setlocal + +set DEST=%~1 +set DIST=%~2 +set ARG1=%~3 +set ARG2=%~4 +set ARG3=%~5 + +pushd "%~dp0%" + call mingw64 -no-start ./deliver.sh "%DEST%" "%DIST%" "%ARG1%" "%ARG2%" "%ARG3%" 1 0 || exit 1 +popd diff --git a/scripts/deliver.sh b/scripts/deliver.sh new file mode 100755 index 0000000..a9d9e5c --- /dev/null +++ b/scripts/deliver.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +DEST_DIR=$1 +DIST_DIR=$2 + +PROJECT_SCRIPTS_DIR=$(realpath "$0") +PROJECT_SCRIPTS_DIR=$(dirname "${PROJECT_SCRIPTS_DIR}") +. "${PROJECT_SCRIPTS_DIR}/env.sh" "$3" "$4" "$5" "$6" "$7" + +function error_exit() { + echo $1 + exit $2 +} + +if [ ! -d "${PROJECT_DIST_DIR}" ]; then + error_exit "dist directory not found: ${PROJECT_DIST_DIR}" 2 +fi + +if [ "${DIST_DIR}" == "" ]; then + DIST_DIR="${PROJECT_DIST_DIR}" +fi +DIST_DIR=$(realpath "${DIST_DIR}") + +if [ ! -d "${DIST_DIR}" ]; then + error_exit "dist directory not found: ${DIST_DIR}" 2 +fi + +if [ "${DEST_DIR}" == "" ]; then + error_exit "dest directory not set" 2 +fi + +if [ ! -d "${DEST_DIR}" ]; then + error_exit "dest directory not found: ${DEST_DIR}" 2 +fi + +pushd "${PROJECT_SOURCE_DIR}" +BRANCH=$(git branch --show-current) +RELEASE=$(grep PROJECT_RELEASE_ITER= ./config.sh | sed s/PROJECT_RELEASE_ITER=//g) +popd + +if [ "${BRANCH}" == "master" ] || [ "${BRANCH}" == "alpha" ] || [ "${BRANCH}" == "beta" ] || [ "${BRANCH}" == "rc" ]; then + DEST_DIR=${DEST_DIR}/${RELEASE} +else + DEST_DIR=${DEST_DIR}/nightly +fi + +echo ${DEST_DIR} + +pushd "${DIST_DIR}" +if [ ! -f "./${PROJECT_OUT_FILE}" ]; then + error_exit "failed to find file: ${PROJECT_OUT_FILE}" 1 +fi + +if [ ! -f "./${PROJECT_OUT_FILE}.sha256" ]; then + error_exit "failed to find file: ${PROJECT_OUT_FILE}.sha256" 1 +fi + +if [ "${PROJECT_PRIVATE_KEY}" != "" ] && [ ! -f "./${PROJECT_OUT_FILE}.sig" ]; then + error_exit "failed to find file: ${PROJECT_OUT_FILE}.sig" 1 +fi + +cp -f ./${PROJECT_OUT_FILE} ${DEST_DIR} || error_exit "failed to deliver file: ${PROJECT_OUT_FILE}" 1 +cp -f ./${PROJECT_OUT_FILE}.sha256 ${DEST_DIR} || error_exit "failed to deliver file: ${PROJECT_OUT_FILE}.sha256" 1 +if [ "${PROJECT_PRIVATE_KEY}" != "" ]; then + cp -f ./${PROJECT_OUT_FILE}.sig ${DEST_DIR} || error_exit "failed to deliver file: ${PROJECT_OUT_FILE}.sig" 1 +fi +popd + +error_exit "delivered ${PROJECT_FILE_PART}" 0 diff --git a/scripts/docker_common.sh b/scripts/docker_common.sh new file mode 100755 index 0000000..2819b3d --- /dev/null +++ b/scripts/docker_common.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +if [ "${PROJECT_BUILD_ARCH}" == "aarch64" ]; then + docker run --rm --privileged multiarch/qemu-user-static --reset -p yes +fi + +if [ "${DOCKER_NAME}" == "mingw64" ]; then + APP_VERSION_BUILD_ARGS=${PROJECT_MINGW64_DOCKER_BUILD_ARGS} + IS_MINGW=1 + IS_UNIX=1 +else + IS_MINGW=0 + IS_UNIX=0 +fi + +if [ "${DOCKER_CREATE_ONLY}" != "1" ]; then + docker stop ${DOCKER_CONTAINER} + docker rm ${DOCKER_CONTAINER} +fi + +pushd "${PROJECT_SOURCE_DIR}/support" +cp -f ${PROJECT_SOURCE_DIR}/docker/${PROJECT_BUILD_ARCH}/${DOCKER_NAME} Dockerfile + +if [ "${PROJECT_BUILD_ARCH}" == "aarch64" ]; then + docker build ${APP_VERSION_BUILD_ARGS} \ + --platform linux/arm64 \ + --build-arg NUM_JOBS=${NUM_JOBS} \ + -t ${DOCKER_TAG} . || exit 1 +else + docker build ${APP_VERSION_BUILD_ARGS} \ + --build-arg NUM_JOBS=${NUM_JOBS} \ + -t ${DOCKER_TAG} . || exit 1 +fi +rm Dockerfile +popd + +if [ "${DOCKER_CREATE_ONLY}" != "1" ]; then + docker run -td \ + --env DEVELOPER_PRIVATE_KEY=${DEVELOPER_PRIVATE_KEY} \ + --env DEVELOPER_PUBLIC_KEY=${DEVELOPER_PUBLIC_KEY} \ + --env MY_NUM_JOBS=${NUM_JOBS} \ + --name ${DOCKER_CONTAINER} \ + -u $(id -u):$(id -g) \ + -v /.ci/${PROJECT_NAME}:/.ci/${PROJECT_NAME}:ro \ + -v ${PROJECT_SOURCE_DIR}:${PROJECT_SOURCE_DIR}:rw,z \ + -w ${PROJECT_SOURCE_DIR} \ + ${DOCKER_TAG} || exit 1 + + if [ "${DOCKER_SHELL}" == "1" ]; then + docker exec -it ${DOCKER_CONTAINER} /bin/bash + else + docker exec \ + ${DOCKER_CONTAINER} \ + /bin/bash -c \ + "${PROJECT_SOURCE_DIR}/scripts/make_common.sh \"${PROJECT_BUILD_ARCH}\" \"${PROJECT_CMAKE_BUILD_TYPE}\" \"${PROJECT_BUILD_CLEAN}\" ${IS_MINGW} ${IS_UNIX}" || exit 1 + fi + + docker stop ${DOCKER_CONTAINER} + docker rm ${DOCKER_CONTAINER} +fi diff --git a/scripts/env.sh b/scripts/env.sh new file mode 100755 index 0000000..d43bc1c --- /dev/null +++ b/scripts/env.sh @@ -0,0 +1,391 @@ +#!/bin/bash + +PROJECT_BUILD_ARCH=$1 +PROJECT_CMAKE_BUILD_TYPE=$2 +PROJECT_BUILD_CLEAN=$3 +PROJECT_IS_MINGW=$4 +PROJECT_IS_MINGW_UNIX=$5 +DISABLE_CREATE_DIRS=$6 + +PROJECT_SOURCE_DIR=${PROJECT_SCRIPTS_DIR}/.. +PROJECT_SOURCE_DIR=$(realpath "${PROJECT_SOURCE_DIR}") + +NUM_JOBS=${MY_NUM_JOBS} +if [[ -z "${NUM_JOBS}" ]]; then + NUM_JOBS=$(getconf _NPROCESSORS_ONLN 2>/dev/null || + getconf NPROCESSORS_ONLN 2>/dev/null || echo 1) + if [ "${NUM_JOBS}" -gt "4" ]; then + NUM_JOBS=$(expr ${NUM_JOBS} - 2) + elif [ "${NUM_JOBS}" -gt "1" ]; then + NUM_JOBS=$(expr ${NUM_JOBS} - 1) + fi +fi + +pushd "${PROJECT_SOURCE_DIR}" + +PROJECT_GIT_REV=$(git rev-parse --short HEAD) + +. "${PROJECT_SCRIPTS_DIR}/versions.sh" +. "${PROJECT_SCRIPTS_DIR}/libraries.sh" + +for PROJECT_LIBRARY in "${PROJECT_LIBRARIES[@]}"; do + ENABLE_NAME=PROJECT_ENABLE_${PROJECT_LIBRARY} + KEEP_NAME=PROJECT_KEEP_${PROJECT_LIBRARY} + if [ "${PROJECT_LIBRARY}" == "TESTING" ]; then + export ${ENABLE_NAME}=ON + else + export ${ENABLE_NAME}=OFF + fi + export ${KEEP_NAME}=0 +done + +PROJECT_APP_LIST=() +PROJECT_CMAKE_OPTS="" +PROJECT_ENABLE_WIN32_LONG_PATH_NAMES=OFF +PROJECT_IS_ALPINE=0 +PROJECT_IS_ARM64=0 +PROJECT_MINGW64_COPY_DEPENDENCIES=() +PROJECT_MSYS2_PACKAGE_LIST=() +PROJECT_REQUIRE_ALPINE=OFF +PROJECT_STATIC_LINK=OFF + +if [ "${PROJECT_BUILD_ARCH}" == "" ]; then + PROJECT_BUILD_ARCH=x86_64 +elif [ "${PROJECT_BUILD_ARCH}" == "aarch64" ]; then + PROJECT_IS_ARM64=1 +fi + +if [ "${PROJECT_BUILD_ARCH}" == "x86_64" ]; then + PROJECT_BUILD_ARCH2="x86-64" +else + PROJECT_BUILD_ARCH2="${PROJECT_BUILD_ARCH}" +fi + +if [ -f /etc/alpine-release ]; then + PROJECT_IS_ALPINE=1 +fi + +if [ "${PROJECT_IS_MINGW}" == "" ]; then + PROJECT_IS_MINGW=0 +fi + +if [ "${PROJECT_IS_MINGW_UNIX}" == "" ]; then + PROJECT_IS_MINGW_UNIX=0 +fi + +. "${PROJECT_SOURCE_DIR}/config.sh" + +if [ "${PROJECT_IS_MINGW}" == "0" ]; then + PROJECT_ENABLE_WIN32_LONG_PATH_NAMES=OFF +fi + +if [ "${PROJECT_ENABLE_SFML}" == "ON" ]; then + PROJECT_ENABLE_FLAC=ON + PROJECT_ENABLE_FONTCONFIG=ON + PROJECT_ENABLE_FREETYPE2=ON + PROJECT_ENABLE_OGG=ON + PROJECT_ENABLE_OPENAL=ON + PROJECT_ENABLE_VORBIS=ON + PROJECT_STATIC_LINK=OFF +fi + +if [ "${PROJECT_ENABLE_CPP_HTTPLIB}" == "ON" ]; then + PROJECT_ENABLE_CURL=ON + PROJECT_ENABLE_OPENSSL=ON +fi + +if [ "${PROJECT_ENABLE_CURL}" == "ON" ]; then + PROJECT_ENABLE_OPENSSL=ON +fi + +if [ "${PROJECT_ENABLE_LIBBITCOIN_SYSTEM}" == "ON" ]; then + PROJECT_ENABLE_BOOST=ON + PROJECT_ENABLE_SECP256K1=ON +fi + +if [ "${PROJECT_ENABLE_FONTCONFIG}" == "ON" ]; then + PROJECT_ENABLE_FREETYPE2=ON +fi + +if [ "${PROJECT_ENABLE_WXWIDGETS}" == "ON" ]; then + PROJECT_ENABLE_CURL=ON + PROJECT_STATIC_LINK=OFF +fi + +if [ "${PROJECT_ENABLE_SPDLOG}" == "ON" ]; then + PROJECT_ENABLE_FMT=OFF +fi + +if [ "${PROJECT_ENABLE_VORBIS}" == "ON" ]; then + PROJECT_ENABLE_OGG=ON +fi + +if [ "${PROJECT_ENABLE_FLAC}" == "ON" ]; then + PROJECT_ENABLE_LIBICONV=ON + PROJECT_ENABLE_OGG=ON + PROJECT_ENABLE_VORBIS=ON +fi + +if [ "${PROJECT_ENABLE_BOOST}" == "ON" ]; then + PROJECT_ENABLE_OPENSSL=ON +fi + +if [ "${PROJECT_ENABLE_FONTCONFIG}" == "ON" ] || [ "${PROJECT_ENABLE_NANA}" == "ON" ] || + [ "${PROJECT_ENABLE_SFML}" == "ON" ] || [ "${PROJECT_ENABLE_WXWIDGETS}" == "ON" ] || + [ "${PROJECT_ENABLE_SDL}" == "ON" ]; then + PROJECT_ENABLE_LIBJPEG_TURBO=ON + PROJECT_ENABLE_LIBPNG=ON +fi + +if [ "${PROJECT_IS_MINGW}" == "1" ]; then + PROJECT_ENABLE_BACKWARD_CPP=OFF +fi + +if [ "${PROJECT_ENABLE_LIBDSM}" == "ON" ]; then + PROJECT_ENABLE_LIBICONV=ON + PROJECT_ENABLE_LIBTASN=ON + PROJECT_ENABLE_OPENSSL=ON +fi + +if [ "${PROJECT_CMAKE_BUILD_TYPE}" == "" ]; then + PROJECT_CMAKE_BUILD_TYPE=RelWithDebInfo +fi + +PROJECT_CMAKE_BUILD_TYPE_LOWER=$(echo "${PROJECT_CMAKE_BUILD_TYPE}" | tr '[:upper:]' '[:lower:]') +if [ "${PROJECT_CMAKE_BUILD_TYPE_LOWER}" == "release" ]; then + PROJECT_CMAKE_BUILD_TYPE=RelWithDebInfo +elif [ "${PROJECT_CMAKE_BUILD_TYPE_LOWER}" == "debug" ]; then + PROJECT_CMAKE_BUILD_TYPE=Debug +fi + +if [ "${PROJECT_IS_MINGW}" == "1" ] && [ "${PROJECT_IS_MINGW_UNIX}" != "1" ]; then + PROJECT_STATIC_LINK=OFF +fi + +if [ "${PROJECT_STATIC_LINK}" == "ON" ]; then + PROJECT_BUILD_SHARED_LIBS=OFF + PROJECT_ENABLE_BACKWARD_CPP=OFF + PROJECT_LINK_TYPE=static + PROJECT_REQUIRE_ALPINE=ON +else + PROJECT_BUILD_SHARED_LIBS=ON + PROJECT_LINK_TYPE=shared + PROJECT_REQUIRE_ALPINE=OFF +fi + +PROJECT_BUILD_DIR=${PROJECT_SOURCE_DIR}/build/${PROJECT_CMAKE_BUILD_TYPE_LOWER}/${PROJECT_LINK_TYPE} +PROJECT_DIST_DIR=${PROJECT_SOURCE_DIR}/dist/${PROJECT_CMAKE_BUILD_TYPE_LOWER}/${PROJECT_LINK_TYPE} + +if [ "${PROJECT_IS_MINGW}" == "1" ]; then + PROJECT_DIST_DIR=${PROJECT_DIST_DIR}/win32 + PROJECT_BUILD_DIR=${PROJECT_BUILD_DIR}/win32 +else + PROJECT_DIST_DIR=${PROJECT_DIST_DIR}/linux + PROJECT_BUILD_DIR=${PROJECT_BUILD_DIR}/linux +fi + +if [ "${PROJECT_IS_ARM64}" == "1" ]; then + PROJECT_DIST_DIR=${PROJECT_DIST_DIR}/aarch64 + PROJECT_BUILD_DIR=${PROJECT_BUILD_DIR}/aarch64 +else + PROJECT_DIST_DIR=${PROJECT_DIST_DIR}/x86_64 + PROJECT_BUILD_DIR=${PROJECT_BUILD_DIR}/x86_64 +fi + +PROJECT_DIST_DIR=${PROJECT_DIST_DIR}/${PROJECT_NAME} +PROJECT_EXTERNAL_BUILD_ROOT=${PROJECT_BUILD_DIR}/deps + +PROJECT_SUPPORT_DIR=${PROJECT_SOURCE_DIR}/support +PROJECT_3RD_PARTY_DIR=${PROJECT_SUPPORT_DIR}/3rd_party + +if [ "${PROJECT_ENABLE_OPENSSL}" == "ON" ]; then + if [ "${PROJECT_IS_MINGW}" == "1" ]; then + OPENSSL_ROOT_DIR=/mingw64 + else + OPENSSL_ROOT_DIR=${PROJECT_EXTERNAL_BUILD_ROOT} + fi +fi + +if [ "${PROJECT_IS_MINGW}" == "1" ] && [ "${PROJECT_IS_MINGW_UNIX}" == "1" ]; then + PROJECT_TOOLCHAIN_FILE_CMAKE=/cmake_toolchain.cmake + PROJECT_TOOLCHAIN_FILE_MESON=/meson_cross_file.txt + PROJECT_CMAKE_OPTS="-DCMAKE_TOOLCHAIN_FILE=${PROJECT_TOOLCHAIN_FILE_CMAKE} ${PROJECT_CMAKE_OPTS}" +fi + +if [ -f "${PROJECT_SOURCE_DIR}/cmake/versions.cmake" ]; then + VERSIONS=($(sed -e s/\ /=/g -e s/set\(//g -e s/\)//g "${PROJECT_SOURCE_DIR}/cmake/versions.cmake")) + + PROJECT_MINGW64_DOCKER_BUILD_ARGS=() + + for VERSION in "${VERSIONS[@]}"; do + LOOKUP_NAME=$(echo ${VERSION} | sed s/_VERSION.*// | sed s/GTEST/TESTING/g) + ENABLE_NAME=PROJECT_ENABLE_${LOOKUP_NAME} + if [ "${!ENABLE_NAME}" != "OFF" ]; then + PROJECT_MINGW64_DOCKER_BUILD_ARGS+=("--build-arg ${VERSION}") + fi + done + + PROJECT_MINGW64_DOCKER_BUILD_ARGS=${PROJECT_MINGW64_DOCKER_BUILD_ARGS[*]} +fi + +PROJECT_CMAKE_OPTS="-DPROJECT_3RD_PARTY_DIR=${PROJECT_3RD_PARTY_DIR} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_BUILD_ARCH=${PROJECT_BUILD_ARCH} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_BUILD_DIR=${PROJECT_BUILD_DIR} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_BUILD_SHARED_LIBS=${PROJECT_BUILD_SHARED_LIBS} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_CMAKE_BUILD_TYPE=${PROJECT_CMAKE_BUILD_TYPE} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_DIST_DIR=${PROJECT_DIST_DIR} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_ENABLE_WIN32_LONG_PATH_NAMES=${PROJECT_ENABLE_WIN32_LONG_PATH_NAMES} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_EXTERNAL_BUILD_ROOT=${PROJECT_EXTERNAL_BUILD_ROOT} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_GIT_REV=${PROJECT_GIT_REV} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_IS_ALPINE=${PROJECT_IS_ALPINE} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_IS_ARM64=${PROJECT_IS_ARM64} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_IS_MINGW=${PROJECT_IS_MINGW} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_IS_MINGW_UNIX=${PROJECT_IS_MINGW_UNIX} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_MAJOR_VERSION=${PROJECT_MAJOR_VERSION} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_MINOR_VERSION=${PROJECT_MINOR_VERSION} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_NAME=${PROJECT_NAME} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_RELEASE_ITER=${PROJECT_RELEASE_ITER} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_RELEASE_NUM=${PROJECT_RELEASE_NUM} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_REQUIRE_ALPINE=${PROJECT_REQUIRE_ALPINE} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_REVISION_VERSION=${PROJECT_REVISION_VERSION} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_STATIC_LINK=${PROJECT_STATIC_LINK} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_SUPPORT_DIR=${PROJECT_SUPPORT_DIR} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_TOOLCHAIN_FILE_CMAKE=${PROJECT_TOOLCHAIN_FILE_CMAKE} ${PROJECT_CMAKE_OPTS}" +PROJECT_CMAKE_OPTS="-DPROJECT_TOOLCHAIN_FILE_MESON=${PROJECT_TOOLCHAIN_FILE_MESON} ${PROJECT_CMAKE_OPTS}" + +for PROJECT_LIBRARY in "${PROJECT_LIBRARIES[@]}"; do + ENABLE_NAME=PROJECT_ENABLE_${PROJECT_LIBRARY} + PROJECT_CMAKE_OPTS="${PROJECT_CMAKE_OPTS} -D${ENABLE_NAME}=${!ENABLE_NAME}" +done + +PKG_CONFIG_PATH="${PROJECT_EXTERNAL_BUILD_ROOT}/lib/pkgconfig:${PROJECT_EXTERNAL_BUILD_ROOT}/lib64/pkgconfig:${PROJECT_EXTERNAL_BUILD_ROOT}/shared/pkgconfig:${PKG_CONFIG_PATH}" + +if [ "${DISABLE_CREATE_DIRS}" != "1" ]; then + mkdir -p "${PROJECT_BUILD_DIR}" + mkdir -p "${PROJECT_DIST_DIR}" +fi + +PATH="${PROJECT_EXTERNAL_BUILD_ROOT}/bin:${PATH}" + +if [ "${PROJECT_IS_MINGW}" == "1" ]; then + PROJECT_OS=windows +else + PROJECT_OS=linux +fi + +PROJECT_FILE_PART=${PROJECT_NAME}_${PROJECT_MAJOR_VERSION}.${PROJECT_MINOR_VERSION}.${PROJECT_REVISION_VERSION}-${PROJECT_RELEASE_ITER}_${PROJECT_GIT_REV}_${PROJECT_OS}_${PROJECT_BUILD_ARCH2} +PROJECT_OUT_FILE=${PROJECT_FILE_PART}.tar.gz + +if [ "${PROJECT_IS_MINGW}" == "1" ]; then + PROJECT_APP_BINARY_EXT=.exe +fi + +if [ "${PROJECT_IS_MINGW}" == "1" ] && [ "${PROJECT_IS_MINGW_UNIX}" != "1" ]; then + MSYS=winsymlinks:nativestrict +fi + +export MSYS +export NUM_JOBS +export OPENSSL_ROOT_DIR +export PATH +export PKG_CONFIG_PATH +export PROJECT_3RD_PARTY_DIR +export PROJECT_APP_BINARY_EXT +export PROJECT_APP_LIST +export PROJECT_BUILD_ARCH +export PROJECT_BUILD_ARCH2 +export PROJECT_BUILD_CLEAN +export PROJECT_BUILD_DIR +export PROJECT_BUILD_SHARED_LIBS +export PROJECT_CMAKE_BUILD_TYPE +export PROJECT_CMAKE_BUILD_TYPE_LOWER +export PROJECT_CMAKE_OPTS +export PROJECT_COMPANY_NAME +export PROJECT_COPYRIGHT +export PROJECT_DESC +export PROJECT_DIST_DIR +export PROJECT_ENABLE_WIN32_LONG_PATH_NAMES +export PROJECT_FILE_PART +export PROJECT_GIT_REV +export PROJECT_IS_ALPINE +export PROJECT_IS_ARM64 +export PROJECT_IS_MINGW +export PROJECT_IS_MINGW_UNIX +export PROJECT_LINK_TYPE +export PROJECT_MAJOR_VERSION +export PROJECT_MINGW64_COPY_DEPENDENCIES +export PROJECT_MINGW64_DOCKER_BUILD_ARGS +export PROJECT_MINOR_VERSION +export PROJECT_MSYS2_PACKAGE_LIST +export PROJECT_NAME +export PROJECT_OS +export PROJECT_OUT_FILE +export PROJECT_PRIVATE_KEY +export PROJECT_PUBLIC_KEY +export PROJECT_RELEASE_ITER +export PROJECT_RELEASE_NUM +export PROJECT_REQUIRE_ALPINE +export PROJECT_REVISION_VERSION +export PROJECT_SOURCE_DIR +export PROJECT_STATIC_LINK +export PROJECT_SUPPORT_DIR +export PROJECT_TOOLCHAIN_FILE_CMAKE +export PROJECT_TOOLCHAIN_FILE_MESON +export PROJECT_URL + +for PROJECT_LIBRARY in "${PROJECT_LIBRARIES[@]}"; do + ENABLE_NAME=PROJECT_ENABLE_${PROJECT_LIBRARY} + KEEP_NAME=PROJECT_KEEP_${PROJECT_LIBRARY} + export ${ENABLE_NAME} + export ${KEEP_NAME} +done + +echo "-=[Settings]=-" +echo " App binary extension: ${PROJECT_APP_BINARY_EXT}" +echo " App list: ${PROJECT_APP_LIST[*]}" +echo " Build arch: ${PROJECT_BUILD_ARCH}" +echo " Build arch2: ${PROJECT_BUILD_ARCH2}" +echo " Build clean: ${PROJECT_BUILD_CLEAN}" +echo " Build dir: ${PROJECT_BUILD_DIR}" +echo " Build shared libraries: ${PROJECT_BUILD_SHARED_LIBS}" +echo " CMake options: -G\"Unix Makefiles\" -DPROJECT_COMPANY_NAME=\"${PROJECT_COMPANY_NAME}\" -DPROJECT_COPYRIGHT=\"${PROJECT_COPYRIGHT}\" -DPROJECT_DESC=\"${PROJECT_DESC}\" -DPROJECT_URL=\"${PROJECT_URL}\" ${PROJECT_CMAKE_OPTS} " +echo " CMake toolchain file: ${PROJECT_TOOLCHAIN_FILE_CMAKE}" +echo " Cmake Build type: ${PROJECT_CMAKE_BUILD_TYPE}" +echo " Company name: ${PROJECT_COMPANY_NAME}" +echo " Copyright: ${PROJECT_COPYRIGHT}" +echo " Description: ${PROJECT_DESC}" +echo " Dist dir: ${PROJECT_DIST_DIR}" +echo " External build root: ${PROJECT_EXTERNAL_BUILD_ROOT}" +echo " File part: ${PROJECT_FILE_PART}" +echo " Is ARM64: ${PROJECT_IS_ARM64}" +echo " Is Alpine: ${PROJECT_IS_ALPINE}" +echo " Is MINGW on Unix: ${PROJECT_IS_MINGW_UNIX}" +echo " Is MINGW: ${PROJECT_IS_MINGW}" +echo " Job count: ${NUM_JOBS}" +echo " Link type: ${PROJECT_LINK_TYPE}" +if [ "${PROJECT_IS_MINGW}" == "1" ]; then + echo " Long path names: ${PROJECT_ENABLE_WIN32_LONG_PATH_NAMES}" +fi +echo " Meson toolchain file: ${PROJECT_TOOLCHAIN_FILE_MESON}" +if [ "${PROJECT_IS_MINGW}" == "1" ] && [ "${PROJECT_IS_MINGW_UNIX}" == "1" ]; then + echo " MinGW docker build args: ${PROJECT_MINGW64_DOCKER_BUILD_ARGS}" +fi +echo " OPENSSL_ROOT_DIR: ${OPENSSL_ROOT_DIR}" +echo " Out file: ${PROJECT_OUT_FILE}" +echo " PATH: ${PATH}" +echo " PKG_CONFIG_PATH: ${PKG_CONFIG_PATH}" +echo " Require Alpine: ${PROJECT_REQUIRE_ALPINE}" +echo " Static link: ${PROJECT_STATIC_LINK}" +echo " Support dir: ${PROJECT_SUPPORT_DIR}" +echo " Third-party dir: ${PROJECT_3RD_PARTY_DIR}" +echo " Unit testing enabled: ${PROJECT_ENABLE_TESTING}" +echo " URL: ${PROJECT_URL}" +echo "-=[Libraries]=-" +for PROJECT_LIBRARY in "${PROJECT_LIBRARIES[@]}"; do + ENABLE_NAME=PROJECT_ENABLE_${PROJECT_LIBRARY} + KEEP_NAME=PROJECT_KEEP_${PROJECT_LIBRARY} + if [ "${!ENABLE_NAME}" == "ON" ] || [ "${!KEEP_NAME}" == "1" ]; then + echo " ${ENABLE_NAME}: Enable[${!ENABLE_NAME}] Keep[${!KEEP_NAME}]" + fi +done +popd diff --git a/scripts/info.cmd b/scripts/info.cmd new file mode 100644 index 0000000..a560b9d --- /dev/null +++ b/scripts/info.cmd @@ -0,0 +1,12 @@ +@echo off + +setlocal + +set ARG1=%~1 +set ARG2=%~2 +set ARG3=%~3 + +pushd "%~dp0%" + call mingw64 -no-start ./info.sh "%ARG1%" "%ARG2%" "%ARG3%" 1 0 +popd + diff --git a/scripts/info.sh b/scripts/info.sh new file mode 100755 index 0000000..9dd8dbf --- /dev/null +++ b/scripts/info.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +PROJECT_SCRIPTS_DIR=$(realpath "$0") +PROJECT_SCRIPTS_DIR=$(dirname "${PROJECT_SCRIPTS_DIR}") +. "${PROJECT_SCRIPTS_DIR}/env.sh" "$1" "$2" "$3" "$4" "$5" 1 diff --git a/scripts/libraries.sh b/scripts/libraries.sh new file mode 100755 index 0000000..38d6fc1 --- /dev/null +++ b/scripts/libraries.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +PROJECT_LIBRARIES=( + BACKWARD_CPP + CPP_HTTPLIB + CURL + JSON + OPENSSL + ROCKSDB + SPDLOG + TESTING +) + +declare -A PROJECT_CLEANUP +PROJECT_CLEANUP[BACKWARD_CPP]="include/backward.hpp:src/backward.cpp" +PROJECT_CLEANUP[BINUTILS]="3rd_party/mingw64/binutils-*" +PROJECT_CLEANUP[CPP_HTTPLIB]="3rd_party/cpp-httplib-*" +PROJECT_CLEANUP[CURL]="3rd_party/curl-*" +PROJECT_CLEANUP[EXPAT]="3rd_party/mingw64/expat-*" +PROJECT_CLEANUP[GCC]="3rd_party/mingw64/gcc-*" +PROJECT_CLEANUP[ICU]="3rd_party/mingw64/icu-release-*" +PROJECT_CLEANUP[JSON]="3rd_party/json-*" +PROJECT_CLEANUP[LIBBITCOIN_SYSTEM_ON]="3rd_party/boost_${PROJECT_VERSIONS[BOOST_MAJOR]}_${PROJECT_VERSIONS[BOOST_MINOR]}_*" +PROJECT_CLEANUP[MINGW]="3rd_party/mingw64/mingw-w64-*" +PROJECT_CLEANUP[OPENSSL]="3rd_party/openssl-*" +PROJECT_CLEANUP[PKG_CONFIG]="3rd_party/mingw64/pkg-config-*" +PROJECT_CLEANUP[ROCKSDB]="3rd_party/rocksdb-*" +PROJECT_CLEANUP[SPDLOG]="3rd_party/spdlog-*" +PROJECT_CLEANUP[TESTING]="3rd_party/googletest-*" +PROJECT_CLEANUP[ZLIB]="3rd_party/mingw64/zlib-*" +export PROJECT_CLEANUP + +declare -A PROJECT_DOWNLOADS +PROJECT_DOWNLOADS[BINUTILS]="https://ftp.gnu.org/gnu/binutils/binutils-${PROJECT_VERSIONS[BINUTILS]}.tar.xz;binutils-${PROJECT_VERSIONS[BINUTILS]}.tar.xz;3rd_party/mingw64" +PROJECT_DOWNLOADS[BOOST2]="https://archives.boost.io/release/${PROJECT_VERSIONS[BOOST2_MAJOR]}.${PROJECT_VERSIONS[BOOST2_MINOR]}.${PROJECT_VERSIONS[BOOST2_PATCH]}/source/boost_${PROJECT_VERSIONS[BOOST2_MAJOR]}_${PROJECT_VERSIONS[BOOST2_MINOR]}_${PROJECT_VERSIONS[BOOST2_PATCH]}.tar.gz;boost_${PROJECT_VERSIONS[BOOST2_MAJOR]}_${PROJECT_VERSIONS[BOOST2_MINOR]}_${PROJECT_VERSIONS[BOOST2_PATCH]}.tar.gz;3rd_party" +PROJECT_DOWNLOADS[CPP_HTTPLIB]="https://github.com/yhirose/cpp-httplib/archive/refs/tags/v${PROJECT_VERSIONS[CPP_HTTPLIB]}.tar.gz;cpp-httplib-${PROJECT_VERSIONS[CPP_HTTPLIB]}.tar.gz;3rd_party" +PROJECT_DOWNLOADS[CURL]="https://github.com/curl/curl/archive/refs/tags/curl-${PROJECT_VERSIONS[CURL2]}.tar.gz;curl-${PROJECT_VERSIONS[CURL]}.tar.gz;3rd_party" +PROJECT_DOWNLOADS[EXPAT]="https://github.com/libexpat/libexpat/archive/refs/tags/R_${PROJECT_VERSIONS[EXPAT2]}.tar.gz;expat-${PROJECT_VERSIONS[EXPAT]}.tar.gz;3rd_party/mingw64" +PROJECT_DOWNLOADS[GCC]="https://ftp.gnu.org/gnu/gcc/gcc-${PROJECT_VERSIONS[GCC]}/gcc-${PROJECT_VERSIONS[GCC]}.tar.gz;gcc-${PROJECT_VERSIONS[GCC]}.tar.gz;3rd_party/mingw64" +PROJECT_DOWNLOADS[GTEST]="https://github.com/google/googletest/archive/refs/tags/v${PROJECT_VERSIONS[GTEST]}.tar.gz;googletest-${PROJECT_VERSIONS[GTEST]}.tar.gz;3rd_party" +PROJECT_DOWNLOADS[ICU]="https://github.com/unicode-org/icu/archive/refs/tags/release-${PROJECT_VERSIONS[ICU]}.tar.gz;icu-release-${PROJECT_VERSIONS[ICU]}.tar.gz;3rd_party/mingw64" +PROJECT_DOWNLOADS[JSON]="https://github.com/nlohmann/json/archive/refs/tags/v${PROJECT_VERSIONS[JSON]}.tar.gz;json-${PROJECT_VERSIONS[JSON]}.tar.gz;3rd_party" +PROJECT_DOWNLOADS[MINGW]="https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/mingw-w64-v${PROJECT_VERSIONS[MINGW]}.tar.bz2;;mingw-w64-v${PROJECT_VERSIONS[MINGW]}.tar.bz2;3rd_party/mingw64" +PROJECT_DOWNLOADS[OPENSSL]="https://github.com/openssl/openssl/releases/download/openssl-${PROJECT_VERSIONS[OPENSSL]}/openssl-${PROJECT_VERSIONS[OPENSSL]}.tar.gz;openssl-${PROJECT_VERSIONS[OPENSSL]}.tar.gz;3rd_party" +PROJECT_DOWNLOADS[PKG_CONFIG]="https://pkgconfig.freedesktop.org/releases/pkg-config-${PROJECT_VERSIONS[PKG_CONFIG]}.tar.gz;pkg-config-${PROJECT_VERSIONS[PKG_CONFIG]}.tar.gz;3rd_party/mingw64" +PROJECT_DOWNLOADS[ROCKSDB]="https://github.com/facebook/rocksdb/archive/refs/tags/v${PROJECT_VERSIONS[ROCKSDB]}.tar.gz;rocksdb-${PROJECT_VERSIONS[ROCKSDB]}.tar.gz;3rd_party" +PROJECT_DOWNLOADS[SPDLOG]="https://github.com/gabime/spdlog/archive/refs/tags/v${PROJECT_VERSIONS[SPDLOG]}.tar.gz;spdlog-${PROJECT_VERSIONS[SPDLOG]}.tar.gz;3rd_party" +PROJECT_DOWNLOADS[ZLIB]="https://github.com/madler/zlib/archive/refs/tags/v${PROJECT_VERSIONS[ZLIB]}.tar.gz;zlib-${PROJECT_VERSIONS[ZLIB]}.tar.gz;3rd_party/mingw64" +export PROJECT_DOWNLOADS diff --git a/scripts/make_common.sh b/scripts/make_common.sh new file mode 100755 index 0000000..2d432ea --- /dev/null +++ b/scripts/make_common.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +PROJECT_SCRIPTS_DIR=$(realpath "$0") +PROJECT_SCRIPTS_DIR=$(dirname "${PROJECT_SCRIPTS_DIR}") +. "${PROJECT_SCRIPTS_DIR}/env.sh" "$1" "$2" "$3" "$4" "$5" + +mkdir -p "${PROJECT_BUILD_DIR}/build" + +if [ -d "${PROJECT_DIST_DIR}" ]; then + rm -rf "${PROJECT_DIST_DIR}" + mkdir -p "${PROJECT_DIST_DIR}" +fi + +if [ -f "${PROJECT_SOURCE_DIR}/pre_build.sh" ]; then + "${PROJECT_SOURCE_DIR}/pre_build.sh" +fi + +ln -sf "${PROJECT_BUILD_DIR}/build/compile_commands.json" "${PROJECT_SOURCE_DIR}/compile_commands.json" + +pushd "${PROJECT_BUILD_DIR}" +cmake "${PROJECT_SOURCE_DIR}" \ + -G"Unix Makefiles" \ + -DPROJECT_COMPANY_NAME="${PROJECT_COMPANY_NAME}" \ + -DPROJECT_COPYRIGHT="${PROJECT_COPYRIGHT}" \ + -DPROJECT_DESC="${PROJECT_DESC}" \ + -DPROJECT_INTERFACE=1 \ + -DPROJECT_URL="${PROJECT_URL}" \ + ${PROJECT_CMAKE_OPTS} || exit 1 + +if [ "${PROJECT_BUILD_CLEAN}" == "clean" ]; then + make clean + pushd build + make clean + popd +fi + +make -j${NUM_JOBS} || exit 1 + +pushd build +make -j${NUM_JOBS} || exit 1 +popd +popd + +if [ "${PROJECT_IS_MINGW}" != "1" ] && [ "${PROJECT_REQUIRE_ALPINE}" == "ON" ]; then + rsync -av --progress /usr/share/icu/74.2/ "${PROJECT_DIST_DIR}/icu/" +fi + +if [ "${PROJECT_BUILD_SHARED_LIBS}" == "ON" ] && [ "${PROJECT_IS_MINGW}" != "1" ]; then + rsync -av --progress ${PROJECT_EXTERNAL_BUILD_ROOT}/ "${PROJECT_DIST_DIR}/" + pushd "${PROJECT_DIST_DIR}" + rm lib64 + ln -sf lib lib64 + popd +fi + +for APP in ${PROJECT_APP_LIST[@]}; do + if [ "${PROJECT_BUILD_SHARED_LIBS}" == "ON" ]; then + if [ "${PROJECT_IS_MINGW}" != "1" ]; then + rm "${PROJECT_DIST_DIR}/${APP}${PROJECT_APP_BINARY_EXT}" + if [ "${PROJECT_ENABLE_CURL}" == "ON" ]; then + mv "${PROJECT_DIST_DIR}/cacert.pem" "${PROJECT_DIST_DIR}/bin/cacert.pem" + fi + rsync -av --progress "${PROJECT_BUILD_DIR}/build/${APP}${PROJECT_APP_BINARY_EXT}" "${PROJECT_DIST_DIR}/bin/" + cat <>"${PROJECT_DIST_DIR}/${APP}${PROJECT_APP_BINARY_EXT}" +#!/bin/sh +PROJECT_SCRIPTS_DIR=\$(realpath "\$0") +PROJECT_SCRIPTS_DIR=\$(dirname "\${PROJECT_SCRIPTS_DIR}") + +export LD_LIBRARY_PATH="\${PROJECT_SCRIPTS_DIR}/lib:\${PROJECT_SCRIPTS_DIR}/lib64:\${LD_LIBRARY_PATH}" + +\${PROJECT_SCRIPTS_DIR}/bin/${APP}${PROJECT_APP_BINARY_EXT} \$* +EOF + chmod +x "${PROJECT_DIST_DIR}/${APP}${PROJECT_APP_BINARY_EXT}" + else + rsync -av --progress "${PROJECT_BUILD_DIR}/build/${APP}${PROJECT_APP_BINARY_EXT}" "${PROJECT_DIST_DIR}/" + fi + else + rsync -av --progress "${PROJECT_BUILD_DIR}/build/${APP}${PROJECT_APP_BINARY_EXT}" "${PROJECT_DIST_DIR}/" + fi +done + +if [ "${PROJECT_IS_MINGW}" == "1" ]; then + . "${PROJECT_SCRIPTS_DIR}/copy_mingw64_deps.sh" "$1" "$2" "$3" "$4" "$5" +fi + +if [ -f "${PROJECT_SOURCE_DIR}/post_build.sh" ]; then + "${PROJECT_SOURCE_DIR}/post_build.sh" +fi + +. "${PROJECT_SCRIPTS_DIR}/make_package.sh" "$1" "$2" "$3" "$4" "$5" diff --git a/scripts/make_package.cmd b/scripts/make_package.cmd new file mode 100644 index 0000000..1037ea3 --- /dev/null +++ b/scripts/make_package.cmd @@ -0,0 +1,11 @@ +@echo off + +setlocal + +set ARG1=%~1 +set ARG2=%~2 +set ARG3=%~3 + +pushd "%~dp0%" + call mingw64 -no-start ./make_package.sh "%ARG1%" "%ARG2%" "%ARG3%" 1 0 || exit 1 +popd diff --git a/scripts/make_package.sh b/scripts/make_package.sh new file mode 100755 index 0000000..8afa3e8 --- /dev/null +++ b/scripts/make_package.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +TEMP_DIR=$(mktemp -d) + +PROJECT_SCRIPTS_DIR=$(realpath "$0") +PROJECT_SCRIPTS_DIR=$(dirname "${PROJECT_SCRIPTS_DIR}") +. "${PROJECT_SCRIPTS_DIR}/env.sh" "$1" "$2" "$3" "$4" "$5" + +function error_exit() { + echo $1 + rm -rf ${TEMP_DIR} + exit $2 +} + +function create_file_validations() { + local SOURCE_FILE=$1 + sha256sum ${SOURCE_FILE} >${SOURCE_FILE}.sha256 || error_exit "failed to create sha256 for file: ${SOURCE_FILE}" 1 + if [ "${PROJECT_PRIVATE_KEY}" != "" ]; then + openssl dgst -sha256 -sign "${PROJECT_PRIVATE_KEY}" -out "${SOURCE_FILE}.sig" "${SOURCE_FILE}" || error_exit "failed to create signature for file: ${SOURCE_FILE}" 1 + openssl dgst -sha256 -verify "${PROJECT_PUBLIC_KEY}" -signature "${SOURCE_FILE}.sig" "${SOURCE_FILE}" || error_exit "failed to validate signature for file: ${SOURCE_FILE}" 1 + fi +} + +if [ ! -d "${PROJECT_DIST_DIR}" ]; then + error_exit "dist directory not found: ${PROJECT_DIST_DIR}" 2 +fi + +pushd "${PROJECT_DIST_DIR}" +if [ -f "${PROJECT_OUT_FILE}" ]; then + rm -f "${PROJECT_OUT_FILE}" || error_exit "failed to delete file: ${PROJECT_OUT_FILE}" 1 +fi +if [ -f "${PROJECT_OUT_FILE}.sha256" ]; then + rm -f "${PROJECT_OUT_FILE}.sha256" || error_exit "failed to delete file: ${PROJECT_OUT_FILE}.sha256" 1 +fi +if [ -f "${PROJECT_OUT_FILE}.sig" ]; then + rm -f "${PROJECT_OUT_FILE}.sig" || error_exit "failed to delete file: ${PROJECT_OUT_FILE}.sig" 1 +fi +popd + +rsync -av --progress ${PROJECT_DIST_DIR}/ ${TEMP_DIR}/${PROJECT_NAME}/ || error_exit "failed to rsync" 1 + +for APP in ${PROJECT_APP_LIST[@]}; do + if [ "${PROJECT_BUILD_SHARED_LIBS}" == "ON" ] && [ "${PROJECT_IS_MINGW}" != "1" ]; then + strip ${TEMP_DIR}/${PROJECT_NAME}/bin/${APP}${PROJECT_APP_BINARY_EXT} + else + strip ${TEMP_DIR}/${PROJECT_NAME}/${APP}${PROJECT_APP_BINARY_EXT} + fi +done + +pushd "${TEMP_DIR}/${PROJECT_NAME}/" +IFS=$'\n' +set -f +FILE_LIST=$(find . -type f) +for FILE in ${FILE_LIST}; do + create_file_validations "${FILE}" +done +unset IFS +set +f +popd + +pushd "${PROJECT_DIST_DIR}" +tar cvzf "${PROJECT_OUT_FILE}" -C ${TEMP_DIR} . || error_exit "failed to create archive: ${PROJECT_OUT_FILE}" 1 +create_file_validations "${PROJECT_OUT_FILE}" +popd + +error_exit "created package ${PROJECT_FILE_PART}" 0 diff --git a/scripts/make_unix.sh b/scripts/make_unix.sh new file mode 100755 index 0000000..a2789c3 --- /dev/null +++ b/scripts/make_unix.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +PROJECT_SCRIPTS_DIR=$(realpath "$0") +PROJECT_SCRIPTS_DIR=$(dirname "${PROJECT_SCRIPTS_DIR}") +. "${PROJECT_SCRIPTS_DIR}/env.sh" "$1" "$2" "$3" 0 0 + +if [ -f "${PROJECT_SCRIPTS_DIR}/cleanup.sh" ]; then + . "${PROJECT_SCRIPTS_DIR}/cleanup.sh" "$1" "$2" "$3" 0 0 + rm ${PROJECT_SCRIPTS_DIR}/cleanup.* +fi + +if [ "${PROJECT_REQUIRE_ALPINE}" == "ON" ] || [ "${PROJECT_IS_ARM64}" == "1" ]; then + DOCKER_NAME=alpine + DOCKER_CONTAINER=${PROJECT_NAME}_${DOCKER_NAME}_${PROJECT_BUILD_ARCH} + DOCKER_TAG=${PROJECT_NAME}:${DOCKER_NAME} + + . "${PROJECT_SCRIPTS_DIR}/docker_common.sh" || exit 1 +else + "${PROJECT_SOURCE_DIR}/scripts/make_common.sh" "${PROJECT_BUILD_ARCH}" "${PROJECT_CMAKE_BUILD_TYPE}" "${PROJECT_BUILD_CLEAN}" 0 0 || exit 1 +fi diff --git a/scripts/make_win32.cmd b/scripts/make_win32.cmd new file mode 100644 index 0000000..73f10f4 --- /dev/null +++ b/scripts/make_win32.cmd @@ -0,0 +1,16 @@ +@echo off + +setlocal + +set ARG1=%~1 +set ARG2=%~2 +set ARG3=%~3 + +pushd "%~dp0%" + call setup_msys2.cmd "%ARG1%" "%ARG2%" "%ARG3%" + if exist "cleanup.cmd" ( + call cleanup.cmd "%ARG1%" "%ARG2%" "%ARG3%" 1 0 + del cleanup.* + ) + call mingw64 -no-start ./make_common.sh "%ARG1%" "%ARG2%" "%ARG3%" 1 0 || exit 1 +popd diff --git a/scripts/make_win32.sh b/scripts/make_win32.sh new file mode 100755 index 0000000..b90b276 --- /dev/null +++ b/scripts/make_win32.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +PROJECT_SCRIPTS_DIR=$(realpath "$0") +PROJECT_SCRIPTS_DIR=$(dirname "${PROJECT_SCRIPTS_DIR}") +. "${PROJECT_SCRIPTS_DIR}/env.sh" "$1" "$2" "$3" 1 1 + +if [ -f "${PROJECT_SCRIPTS_DIR}/cleanup.sh" ]; then + . "${PROJECT_SCRIPTS_DIR}/cleanup.sh" "$1" "$2" "$3" 1 1 + rm ${PROJECT_SCRIPTS_DIR}/cleanup.* +fi + +DOCKER_NAME=mingw64 +DOCKER_CONTAINER=${PROJECT_NAME}_${DOCKER_NAME} +DOCKER_TAG=${PROJECT_NAME}:${DOCKER_NAME} + +. "${PROJECT_SCRIPTS_DIR}/docker_common.sh" || exit 1 diff --git a/scripts/run_docker_shell.sh b/scripts/run_docker_shell.sh new file mode 100755 index 0000000..c2d88aa --- /dev/null +++ b/scripts/run_docker_shell.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +DOCKER_NAME=$1 + +PROJECT_SCRIPTS_DIR=$(realpath "$0") +PROJECT_SCRIPTS_DIR=$(dirname "${PROJECT_SCRIPTS_DIR}") +. "${PROJECT_SCRIPTS_DIR}/env.sh" "$2" "$3" "$4" "$5" "$6" 1 1>/dev/null 2>&1 + +if [ -f "${PROJECT_SCRIPTS_DIR}/cleanup.sh" ]; then + . "${PROJECT_SCRIPTS_DIR}/cleanup.sh" "$2" "$3" "$4" "$5" "$6" + rm ${PROJECT_SCRIPTS_DIR}/cleanup.* +fi + +DOCKER_CONTAINER=${PROJECT_NAME}_${DOCKER_NAME}_${PROJECT_BUILD_ARCH}_shell +DOCKER_TAG=${PROJECT_NAME}:${DOCKER_NAME} +DOCKER_SHELL=1 + +. "${PROJECT_SCRIPTS_DIR}/docker_common.sh" || exit 1 diff --git a/scripts/run_tests.cmd b/scripts/run_tests.cmd new file mode 100644 index 0000000..53e9f54 --- /dev/null +++ b/scripts/run_tests.cmd @@ -0,0 +1,12 @@ +@echo off + +setlocal + +set ARG1=%~1 +set ARG2=%~2 +set ARG3=%~3 + +pushd "%~dp0%" + call mingw64 -no-start ./run_tests.sh "%ARG1%" "%ARG2%" "%ARG3%" 1 0 +popd + diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh new file mode 100755 index 0000000..03d55c8 --- /dev/null +++ b/scripts/run_tests.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +PROJECT_SCRIPTS_DIR=$(realpath "$0") +PROJECT_SCRIPTS_DIR=$(dirname "${PROJECT_SCRIPTS_DIR}") +. "${PROJECT_SCRIPTS_DIR}/env.sh" "$1" "$2" "$3" "$4" "$5" 0 + +pushd "${PROJECT_DIST_DIR}" +if [ -e "./${PROJECT_NAME}_test${PROJECT_APP_BINARY_EXT}" ]; then + for APP in ${PROJECT_APP_LIST[@]}; do + if [[ "$APP" == *_test ]]; then + if [ "${PROJECT_IS_MINGW_UNIX}" == "1" ]; then + wine64 "${PROJECT_DIST_DIR}/${PROJECT_NAME}_test${PROJECT_APP_BINARY_EXT}" || exit 1 + else + "./${PROJECT_NAME}_test${PROJECT_APP_BINARY_EXT}" || exit 1 + fi + fi + done +fi +popd diff --git a/scripts/setup_msys2.cmd b/scripts/setup_msys2.cmd new file mode 100644 index 0000000..ce3c466 --- /dev/null +++ b/scripts/setup_msys2.cmd @@ -0,0 +1,12 @@ +@echo off + +setlocal + +set ARG1=%~1 +set ARG2=%~2 +set ARG3=%~3 + +pushd "%~dp0%" + call mingw64 -no-start ./setup_msys2.sh "%ARG1%" "%ARG2%" "%ARG3%" 1 0 + call mingw64 -no-start ./setup_msys2.sh "%ARG1%" "%ARG2%" "%ARG3%" 1 0 +popd diff --git a/scripts/setup_msys2.sh b/scripts/setup_msys2.sh new file mode 100755 index 0000000..7a91c90 --- /dev/null +++ b/scripts/setup_msys2.sh @@ -0,0 +1,187 @@ +#!/bin/bash + +PROJECT_MSYS2_PACKAGE_LIST=() + +PROJECT_SCRIPTS_DIR=$(realpath "$0") +PROJECT_SCRIPTS_DIR=$(dirname "${PROJECT_SCRIPTS_DIR}") +. "${PROJECT_SCRIPTS_DIR}/env.sh" "$1" "$2" "$3" "$4" "$5" 1 1>/dev/null 2>&1 + +PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-autotools + mingw64/mingw-w64-x86_64-clang-tools-extra + mingw64/mingw-w64-x86_64-cmake + mingw64/mingw-w64-x86_64-curl + mingw64/mingw-w64-x86_64-diffutils + mingw64/mingw-w64-x86_64-gcc + mingw64/mingw-w64-x86_64-gdb + mingw64/mingw-w64-x86_64-icu + mingw64/mingw-w64-x86_64-make + mingw64/mingw-w64-x86_64-mesa + mingw64/mingw-w64-x86_64-meson + mingw64/mingw-w64-x86_64-meson-python + mingw64/mingw-w64-x86_64-ninja + mingw64/mingw-w64-x86_64-python + mingw64/mingw-w64-x86_64-python-mako + mingw64/mingw-w64-x86_64-toolchain + mingw64/mingw-w64-x86_64-wget + mingw64/mingw-w64-x86_64-zlib + msys/bison + msys/flex + msys/git + msys/patch + msys/rsync + make +) + +if [ "${PROJECT_ENABLE_BOOST}" == "ON" ]; then + if [ "${PROJECT_ENABLE_LIBBITCOIN_SYSTEM}" == "ON" ]; then + yes | pacman -Ry mingw64/mingw-w64-x86_64-boost + else + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-boost + ) + fi +fi + +if [ "${PROJECT_ENABLE_CLI11}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-cli11 + ) +fi + +if [ "${PROJECT_ENABLE_FLAC}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-flac + ) +fi + +if [ "${PROJECT_ENABLE_FONTCONFIG}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-fontconfig + ) +fi + +if [ "${PROJECT_ENABLE_FREETYPE2}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-freetype + ) +fi + +if [ "${PROJECT_ENABLE_LIBDSM}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-libdsm + ) +fi + +if [ "${PROJECT_ENABLE_LIBEVENT}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-libevent + ) +fi + +if [ "${PROJECT_ENABLE_LIBICONV}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-libiconv + ) +fi + +if [ "${PROJECT_ENABLE_LIBJPEG_TURBO}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-libjpeg-turbo + ) +fi + +if [ "${PROJECT_ENABLE_LIBPNG}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-libpng + ) +fi + +if [ "${PROJECT_ENABLE_LIBSODIUM}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-libsodium + ) +fi + +if [ "${PROJECT_ENABLE_LIBTASN}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-libtasn1 + ) +fi + +if [ "${PROJECT_ENABLE_NANA}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-nana + ) +fi + +if [ "${PROJECT_ENABLE_NUSPELL}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-nuspell + ) +fi + +if [ "${PROJECT_ENABLE_OGG}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-libogg + ) +fi + +if [ "${PROJECT_ENABLE_OPENAL}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-openal + ) +fi + +if [ "${PROJECT_ENABLE_OPENSSL}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-openssl + ) +fi + +if [ "${PROJECT_ENABLE_PUGIXML}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-pugixml + ) +fi + +if [ "${PROJECT_ENABLE_SDL}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-SDL2 + ) +fi + +if [ "${PROJECT_ENABLE_SFML}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-sfml + ) +fi + +if [ "${PROJECT_ENABLE_SQLITE}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-sqlite3 + ) +fi + +if [ "${PROJECT_ENABLE_TESTING}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-gtest + ) +fi + +if [ "${PROJECT_ENABLE_VORBIS}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-libvorbis + ) +fi + +if [ "${PROJECT_ENABLE_WXWIDGETS}" == "ON" ]; then + PROJECT_MSYS2_PACKAGE_LIST+=( + mingw64/mingw-w64-x86_64-wxwidgets3.2-msw + ) +fi + +pacman -Sqyuu --noconfirm && + pacman -S --noconfirm --needed --disable-download-timeout msys2-keyring && + pacman -S --noconfirm --needed --disable-download-timeout \ + ${PROJECT_MSYS2_PACKAGE_LIST[@]} diff --git a/scripts/versions.sh b/scripts/versions.sh new file mode 100755 index 0000000..5dcccc8 --- /dev/null +++ b/scripts/versions.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +declare -A PROJECT_VERSIONS +PROJECT_VERSIONS[BINUTILS]="2.43" +PROJECT_VERSIONS[BOOST2_MAJOR]="1" +PROJECT_VERSIONS[BOOST2_MINOR]="76" +PROJECT_VERSIONS[BOOST2_PATCH]="0" +PROJECT_VERSIONS[BOOST_MAJOR]="1" +PROJECT_VERSIONS[BOOST_MINOR]="87" +PROJECT_VERSIONS[BOOST_PATCH]="0" +PROJECT_VERSIONS[CPP_HTTPLIB]="0.18.1" +PROJECT_VERSIONS[CURL]="8.11.0" +PROJECT_VERSIONS[CURL2]="8_11_0" +PROJECT_VERSIONS[EXPAT]="2.6.4" +PROJECT_VERSIONS[EXPAT2]="2_6_4" +PROJECT_VERSIONS[GCC]="14.2.0" +PROJECT_VERSIONS[GTEST]="1.15.2" +PROJECT_VERSIONS[ICU]="76-1" +PROJECT_VERSIONS[JSON]="3.11.3" +PROJECT_VERSIONS[MESA]="23.3.3" +PROJECT_VERSIONS[MINGW]="12.0.0" +PROJECT_VERSIONS[OPENSSL]="3.4.0" +PROJECT_VERSIONS[PKG_CONFIG]="0.29.2" +PROJECT_VERSIONS[ROCKSDB]="9.7.4" +PROJECT_VERSIONS[SPDLOG]="1.15.0" +PROJECT_VERSIONS[ZLIB]="1.3.1" +export PROJECT_VERSIONS diff --git a/support/3rd_party/cpp-httplib-0.18.1.tar.gz b/support/3rd_party/cpp-httplib-0.18.1.tar.gz new file mode 100644 index 0000000..d58b628 Binary files /dev/null and b/support/3rd_party/cpp-httplib-0.18.1.tar.gz differ diff --git a/support/3rd_party/cpp-httplib-0.18.1.tar.gz.sha256 b/support/3rd_party/cpp-httplib-0.18.1.tar.gz.sha256 new file mode 100644 index 0000000..7172dc8 --- /dev/null +++ b/support/3rd_party/cpp-httplib-0.18.1.tar.gz.sha256 @@ -0,0 +1 @@ +405abd8170f2a446fc8612ac635d0db5947c0d2e156e32603403a4496255ff00 *cpp-httplib-0.18.1.tar.gz diff --git a/support/3rd_party/curl-8.11.0.tar.gz b/support/3rd_party/curl-8.11.0.tar.gz new file mode 100644 index 0000000..5953bd4 Binary files /dev/null and b/support/3rd_party/curl-8.11.0.tar.gz differ diff --git a/support/3rd_party/curl-8.11.0.tar.gz.sha256 b/support/3rd_party/curl-8.11.0.tar.gz.sha256 new file mode 100644 index 0000000..bc720df --- /dev/null +++ b/support/3rd_party/curl-8.11.0.tar.gz.sha256 @@ -0,0 +1 @@ +5a231145114589491fc52da118f9c7ef8abee885d1cb1ced99c7290e9a352f07 *curl-8.11.0.tar.gz diff --git a/support/3rd_party/googletest-1.15.2.tar.gz b/support/3rd_party/googletest-1.15.2.tar.gz new file mode 100644 index 0000000..fd66554 Binary files /dev/null and b/support/3rd_party/googletest-1.15.2.tar.gz differ diff --git a/support/3rd_party/googletest-1.15.2.tar.gz.sha256 b/support/3rd_party/googletest-1.15.2.tar.gz.sha256 new file mode 100644 index 0000000..1b4192e --- /dev/null +++ b/support/3rd_party/googletest-1.15.2.tar.gz.sha256 @@ -0,0 +1 @@ +7b42b4d6ed48810c5362c265a17faebe90dc2373c885e5216439d37927f02926 googletest-1.15.2.tar.gz diff --git a/support/3rd_party/json-3.11.3.tar.gz b/support/3rd_party/json-3.11.3.tar.gz new file mode 100644 index 0000000..c66a790 Binary files /dev/null and b/support/3rd_party/json-3.11.3.tar.gz differ diff --git a/support/3rd_party/json-3.11.3.tar.gz.sha256 b/support/3rd_party/json-3.11.3.tar.gz.sha256 new file mode 100644 index 0000000..bff77da --- /dev/null +++ b/support/3rd_party/json-3.11.3.tar.gz.sha256 @@ -0,0 +1 @@ +0d8ef5af7f9794e3263480193c491549b2ba6cc74bb018906202ada498a79406 json-3.11.3.tar.gz diff --git a/support/3rd_party/mingw64/binutils-2.43.tar.xz b/support/3rd_party/mingw64/binutils-2.43.tar.xz new file mode 100644 index 0000000..42f8fd3 Binary files /dev/null and b/support/3rd_party/mingw64/binutils-2.43.tar.xz differ diff --git a/support/3rd_party/mingw64/binutils-2.43.tar.xz.sha256 b/support/3rd_party/mingw64/binutils-2.43.tar.xz.sha256 new file mode 100644 index 0000000..b432576 --- /dev/null +++ b/support/3rd_party/mingw64/binutils-2.43.tar.xz.sha256 @@ -0,0 +1 @@ +b53606f443ac8f01d1d5fc9c39497f2af322d99e14cea5c0b4b124d630379365 binutils-2.43.tar.xz diff --git a/support/3rd_party/mingw64/expat-2.6.4.tar.gz b/support/3rd_party/mingw64/expat-2.6.4.tar.gz new file mode 100644 index 0000000..b11f36a Binary files /dev/null and b/support/3rd_party/mingw64/expat-2.6.4.tar.gz differ diff --git a/support/3rd_party/mingw64/expat-2.6.4.tar.gz.sha256 b/support/3rd_party/mingw64/expat-2.6.4.tar.gz.sha256 new file mode 100644 index 0000000..a2dda8f --- /dev/null +++ b/support/3rd_party/mingw64/expat-2.6.4.tar.gz.sha256 @@ -0,0 +1 @@ +372b18f6527d162fa9658f1c74d22a37429b82d822f5a1e1fc7e00f6045a06a2 *expat-2.6.4.tar.gz diff --git a/support/3rd_party/mingw64/gcc-14.2.0.tar.gz b/support/3rd_party/mingw64/gcc-14.2.0.tar.gz new file mode 100644 index 0000000..e5c2a90 Binary files /dev/null and b/support/3rd_party/mingw64/gcc-14.2.0.tar.gz differ diff --git a/support/3rd_party/mingw64/gcc-14.2.0.tar.gz.sha256 b/support/3rd_party/mingw64/gcc-14.2.0.tar.gz.sha256 new file mode 100644 index 0000000..c5176fb --- /dev/null +++ b/support/3rd_party/mingw64/gcc-14.2.0.tar.gz.sha256 @@ -0,0 +1 @@ +7d376d445f93126dc545e2c0086d0f647c3094aae081cdb78f42ce2bc25e7293 gcc-14.2.0.tar.gz diff --git a/support/3rd_party/mingw64/icu-release-76-1.tar.gz b/support/3rd_party/mingw64/icu-release-76-1.tar.gz new file mode 100644 index 0000000..b22aff5 Binary files /dev/null and b/support/3rd_party/mingw64/icu-release-76-1.tar.gz differ diff --git a/support/3rd_party/mingw64/icu-release-76-1.tar.gz.sha256 b/support/3rd_party/mingw64/icu-release-76-1.tar.gz.sha256 new file mode 100644 index 0000000..0dc1650 --- /dev/null +++ b/support/3rd_party/mingw64/icu-release-76-1.tar.gz.sha256 @@ -0,0 +1 @@ +a2c443404f00098e9e90acf29dc318e049d2dc78d9ae5f46efb261934a730ce2 icu-release-76-1.tar.gz diff --git a/support/3rd_party/mingw64/mingw-w64-v12.0.0.tar.bz2 b/support/3rd_party/mingw64/mingw-w64-v12.0.0.tar.bz2 new file mode 100644 index 0000000..814bb6f Binary files /dev/null and b/support/3rd_party/mingw64/mingw-w64-v12.0.0.tar.bz2 differ diff --git a/support/3rd_party/mingw64/mingw-w64-v12.0.0.tar.bz2.sha256 b/support/3rd_party/mingw64/mingw-w64-v12.0.0.tar.bz2.sha256 new file mode 100644 index 0000000..75dfa68 --- /dev/null +++ b/support/3rd_party/mingw64/mingw-w64-v12.0.0.tar.bz2.sha256 @@ -0,0 +1 @@ +cc41898aac4b6e8dd5cffd7331b9d9515b912df4420a3a612b5ea2955bbeed2f mingw-w64-v12.0.0.tar.bz2 diff --git a/support/3rd_party/mingw64/pkg-config-0.29.2.tar.gz b/support/3rd_party/mingw64/pkg-config-0.29.2.tar.gz new file mode 100644 index 0000000..a82b28f Binary files /dev/null and b/support/3rd_party/mingw64/pkg-config-0.29.2.tar.gz differ diff --git a/support/3rd_party/mingw64/pkg-config-0.29.2.tar.gz.sha256 b/support/3rd_party/mingw64/pkg-config-0.29.2.tar.gz.sha256 new file mode 100644 index 0000000..d23b500 --- /dev/null +++ b/support/3rd_party/mingw64/pkg-config-0.29.2.tar.gz.sha256 @@ -0,0 +1 @@ +6fc69c01688c9458a57eb9a1664c9aba372ccda420a02bf4429fe610e7e7d591 pkg-config-0.29.2.tar.gz diff --git a/support/3rd_party/mingw64/zlib-1.3.1.tar.gz b/support/3rd_party/mingw64/zlib-1.3.1.tar.gz new file mode 100644 index 0000000..9c29e94 Binary files /dev/null and b/support/3rd_party/mingw64/zlib-1.3.1.tar.gz differ diff --git a/support/3rd_party/mingw64/zlib-1.3.1.tar.gz.sha256 b/support/3rd_party/mingw64/zlib-1.3.1.tar.gz.sha256 new file mode 100644 index 0000000..9440c84 --- /dev/null +++ b/support/3rd_party/mingw64/zlib-1.3.1.tar.gz.sha256 @@ -0,0 +1 @@ +17e88863f3600672ab49182f217281b6fc4d3c762bde361935e436a95214d05c zlib-1.3.1.tar.gz diff --git a/support/3rd_party/openssl-3.4.0.tar.gz b/support/3rd_party/openssl-3.4.0.tar.gz new file mode 100644 index 0000000..091ad36 Binary files /dev/null and b/support/3rd_party/openssl-3.4.0.tar.gz differ diff --git a/support/3rd_party/openssl-3.4.0.tar.gz.sha256 b/support/3rd_party/openssl-3.4.0.tar.gz.sha256 new file mode 100644 index 0000000..6cceec2 --- /dev/null +++ b/support/3rd_party/openssl-3.4.0.tar.gz.sha256 @@ -0,0 +1 @@ +e15dda82fe2fe8139dc2ac21a36d4ca01d5313c75f99f46c4e8a27709b7294bf *openssl-3.4.0.tar.gz diff --git a/support/3rd_party/rocksdb-9.7.4.tar.gz b/support/3rd_party/rocksdb-9.7.4.tar.gz new file mode 100644 index 0000000..d74b809 Binary files /dev/null and b/support/3rd_party/rocksdb-9.7.4.tar.gz differ diff --git a/support/3rd_party/rocksdb-9.7.4.tar.gz.sha256 b/support/3rd_party/rocksdb-9.7.4.tar.gz.sha256 new file mode 100644 index 0000000..6b280b8 --- /dev/null +++ b/support/3rd_party/rocksdb-9.7.4.tar.gz.sha256 @@ -0,0 +1 @@ +9b810c81731835fda0d4bbdb51d3199d901fa4395733ab63752d297da84c5a47 *rocksdb-9.7.4.tar.gz diff --git a/support/3rd_party/spdlog-1.15.0.tar.gz b/support/3rd_party/spdlog-1.15.0.tar.gz new file mode 100644 index 0000000..a74418c Binary files /dev/null and b/support/3rd_party/spdlog-1.15.0.tar.gz differ diff --git a/support/3rd_party/spdlog-1.15.0.tar.gz.sha256 b/support/3rd_party/spdlog-1.15.0.tar.gz.sha256 new file mode 100644 index 0000000..7122272 --- /dev/null +++ b/support/3rd_party/spdlog-1.15.0.tar.gz.sha256 @@ -0,0 +1 @@ +9962648c9b4f1a7bbc76fd8d9172555bad1871fdb14ff4f842ef87949682caa5 *spdlog-1.15.0.tar.gz diff --git a/support/include/backward.hpp b/support/include/backward.hpp new file mode 100644 index 0000000..bbb9404 --- /dev/null +++ b/support/include/backward.hpp @@ -0,0 +1,4509 @@ +#if defined(PROJECT_ENABLE_BACKWARD_CPP) +/* + * backward.hpp + * Copyright 2013 Google Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef H_6B9572DA_A64B_49E6_B234_051480991C89 +#define H_6B9572DA_A64B_49E6_B234_051480991C89 + +#ifndef __cplusplus +#error "It's not going to compile without a C++ compiler..." +#endif + +#if defined(BACKWARD_CXX11) +#elif defined(BACKWARD_CXX98) +#else +#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1800) +#define BACKWARD_CXX11 +#define BACKWARD_ATLEAST_CXX11 +#define BACKWARD_ATLEAST_CXX98 +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#define BACKWARD_ATLEAST_CXX17 +#endif +#else +#define BACKWARD_CXX98 +#define BACKWARD_ATLEAST_CXX98 +#endif +#endif + +// You can define one of the following (or leave it to the auto-detection): +// +// #define BACKWARD_SYSTEM_LINUX +// - specialization for linux +// +// #define BACKWARD_SYSTEM_DARWIN +// - specialization for Mac OS X 10.5 and later. +// +// #define BACKWARD_SYSTEM_WINDOWS +// - specialization for Windows (Clang 9 and MSVC2017) +// +// #define BACKWARD_SYSTEM_UNKNOWN +// - placebo implementation, does nothing. +// +#if defined(BACKWARD_SYSTEM_LINUX) +#elif defined(BACKWARD_SYSTEM_DARWIN) +#elif defined(BACKWARD_SYSTEM_UNKNOWN) +#elif defined(BACKWARD_SYSTEM_WINDOWS) +#else +#if defined(__linux) || defined(__linux__) +#define BACKWARD_SYSTEM_LINUX +#elif defined(__APPLE__) +#define BACKWARD_SYSTEM_DARWIN +#elif defined(_WIN32) +#define BACKWARD_SYSTEM_WINDOWS +#else +#define BACKWARD_SYSTEM_UNKNOWN +#endif +#endif + +#define NOINLINE __attribute__((noinline)) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(BACKWARD_SYSTEM_LINUX) + +// On linux, backtrace can back-trace or "walk" the stack using the following +// libraries: +// +// #define BACKWARD_HAS_UNWIND 1 +// - unwind comes from libgcc, but I saw an equivalent inside clang itself. +// - with unwind, the stacktrace is as accurate as it can possibly be, since +// this is used by the C++ runtime in gcc/clang for stack unwinding on +// exception. +// - normally libgcc is already linked to your program by default. +// +// #define BACKWARD_HAS_LIBUNWIND 1 +// - libunwind provides, in some cases, a more accurate stacktrace as it knows +// to decode signal handler frames and lets us edit the context registers when +// unwinding, allowing stack traces over bad function references. +// +// #define BACKWARD_HAS_BACKTRACE == 1 +// - backtrace seems to be a little bit more portable than libunwind, but on +// linux, it uses unwind anyway, but abstract away a tiny information that is +// sadly really important in order to get perfectly accurate stack traces. +// - backtrace is part of the (e)glib library. +// +// The default is: +// #define BACKWARD_HAS_UNWIND == 1 +// +// Note that only one of the define should be set to 1 at a time. +// +#if BACKWARD_HAS_UNWIND == 1 +#elif BACKWARD_HAS_LIBUNWIND == 1 +#elif BACKWARD_HAS_BACKTRACE == 1 +#else +#undef BACKWARD_HAS_UNWIND +#define BACKWARD_HAS_UNWIND 1 +#undef BACKWARD_HAS_LIBUNWIND +#define BACKWARD_HAS_LIBUNWIND 0 +#undef BACKWARD_HAS_BACKTRACE +#define BACKWARD_HAS_BACKTRACE 0 +#endif + +// On linux, backward can extract detailed information about a stack trace +// using one of the following libraries: +// +// #define BACKWARD_HAS_DW 1 +// - libdw gives you the most juicy details out of your stack traces: +// - object filename +// - function name +// - source filename +// - line and column numbers +// - source code snippet (assuming the file is accessible) +// - variable names (if not optimized out) +// - variable values (not supported by backward-cpp) +// - You need to link with the lib "dw": +// - apt-get install libdw-dev +// - g++/clang++ -ldw ... +// +// #define BACKWARD_HAS_BFD 1 +// - With libbfd, you get a fair amount of details: +// - object filename +// - function name +// - source filename +// - line numbers +// - source code snippet (assuming the file is accessible) +// - You need to link with the lib "bfd": +// - apt-get install binutils-dev +// - g++/clang++ -lbfd ... +// +// #define BACKWARD_HAS_DWARF 1 +// - libdwarf gives you the most juicy details out of your stack traces: +// - object filename +// - function name +// - source filename +// - line and column numbers +// - source code snippet (assuming the file is accessible) +// - variable names (if not optimized out) +// - variable values (not supported by backward-cpp) +// - You need to link with the lib "dwarf": +// - apt-get install libdwarf-dev +// - g++/clang++ -ldwarf ... +// +// #define BACKWARD_HAS_BACKTRACE_SYMBOL 1 +// - backtrace provides minimal details for a stack trace: +// - object filename +// - function name +// - backtrace is part of the (e)glib library. +// +// The default is: +// #define BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +// +// Note that only one of the define should be set to 1 at a time. +// +#if BACKWARD_HAS_DW == 1 +#elif BACKWARD_HAS_BFD == 1 +#elif BACKWARD_HAS_DWARF == 1 +#elif BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +#else +#undef BACKWARD_HAS_DW +#define BACKWARD_HAS_DW 0 +#undef BACKWARD_HAS_BFD +#define BACKWARD_HAS_BFD 0 +#undef BACKWARD_HAS_DWARF +#define BACKWARD_HAS_DWARF 0 +#undef BACKWARD_HAS_BACKTRACE_SYMBOL +#define BACKWARD_HAS_BACKTRACE_SYMBOL 1 +#endif + +#include +#include +#ifdef __ANDROID__ +// Old Android API levels define _Unwind_Ptr in both link.h and +// unwind.h Rename the one in link.h as we are not going to be using +// it +#define _Unwind_Ptr _Unwind_Ptr_Custom +#include +#undef _Unwind_Ptr +#else +#include +#endif +#if defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || \ + defined(__POWERPC__) +// Linux kernel header required for the struct pt_regs definition +// to access the NIP (Next Instruction Pointer) register value +#include +#endif +#include +#include +#include +#include +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#include +#undef _GNU_SOURCE +#else +#include +#endif + +#if BACKWARD_HAS_BFD == 1 +// NOTE: defining PACKAGE{,_VERSION} is required before including +// bfd.h on some platforms, see also: +// https://sourceware.org/bugzilla/show_bug.cgi?id=14243 +#ifndef PACKAGE +#define PACKAGE +#endif +#ifndef PACKAGE_VERSION +#define PACKAGE_VERSION +#endif +#include +#endif + +#if BACKWARD_HAS_DW == 1 +#include +#include +#include +#endif + +#if BACKWARD_HAS_DWARF == 1 +#include +#include +#include +#include +#include +#endif + +#if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) +// then we shall rely on backtrace +#include +#endif + +#endif // defined(BACKWARD_SYSTEM_LINUX) + +#if defined(BACKWARD_SYSTEM_DARWIN) +// On Darwin, backtrace can back-trace or "walk" the stack using the following +// libraries: +// +// #define BACKWARD_HAS_UNWIND 1 +// - unwind comes from libgcc, but I saw an equivalent inside clang itself. +// - with unwind, the stacktrace is as accurate as it can possibly be, since +// this is used by the C++ runtime in gcc/clang for stack unwinding on +// exception. +// - normally libgcc is already linked to your program by default. +// +// #define BACKWARD_HAS_LIBUNWIND 1 +// - libunwind comes from clang, which implements an API compatible version. +// - libunwind provides, in some cases, a more accurate stacktrace as it knows +// to decode signal handler frames and lets us edit the context registers when +// unwinding, allowing stack traces over bad function references. +// +// #define BACKWARD_HAS_BACKTRACE == 1 +// - backtrace is available by default, though it does not produce as much +// information as another library might. +// +// The default is: +// #define BACKWARD_HAS_UNWIND == 1 +// +// Note that only one of the define should be set to 1 at a time. +// +#if BACKWARD_HAS_UNWIND == 1 +#elif BACKWARD_HAS_BACKTRACE == 1 +#elif BACKWARD_HAS_LIBUNWIND == 1 +#else +#undef BACKWARD_HAS_UNWIND +#define BACKWARD_HAS_UNWIND 1 +#undef BACKWARD_HAS_BACKTRACE +#define BACKWARD_HAS_BACKTRACE 0 +#undef BACKWARD_HAS_LIBUNWIND +#define BACKWARD_HAS_LIBUNWIND 0 +#endif + +// On Darwin, backward can extract detailed information about a stack trace +// using one of the following libraries: +// +// #define BACKWARD_HAS_BACKTRACE_SYMBOL 1 +// - backtrace provides minimal details for a stack trace: +// - object filename +// - function name +// +// The default is: +// #define BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +// +#if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +#else +#undef BACKWARD_HAS_BACKTRACE_SYMBOL +#define BACKWARD_HAS_BACKTRACE_SYMBOL 1 +#endif + +#include +#include +#include +#include +#include +#include + +#if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) +#include +#endif +#endif // defined(BACKWARD_SYSTEM_DARWIN) + +#if defined(BACKWARD_SYSTEM_WINDOWS) + +#include +#include +#include + +#include + +#ifdef _WIN64 +typedef SSIZE_T ssize_t; +#else +typedef int ssize_t; +#endif + +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include + +#include +#include + +#ifndef __clang__ +#undef NOINLINE +#define NOINLINE __declspec(noinline) +#endif + +#ifdef _MSC_VER +#pragma comment(lib, "psapi.lib") +#pragma comment(lib, "dbghelp.lib") +#endif + +// Comment / packing is from stackoverflow: +// https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227 +// Some versions of imagehlp.dll lack the proper packing directives themselves +// so we need to do it. +#pragma pack(push, before_imagehlp, 8) +#include +#pragma pack(pop, before_imagehlp) + +// TODO maybe these should be undefined somewhere else? +#undef BACKWARD_HAS_UNWIND +#undef BACKWARD_HAS_BACKTRACE +#if BACKWARD_HAS_PDB_SYMBOL == 1 +#else +#undef BACKWARD_HAS_PDB_SYMBOL +#define BACKWARD_HAS_PDB_SYMBOL 1 +#endif + +#endif + +#if BACKWARD_HAS_UNWIND == 1 + +#include +// while gcc's unwind.h defines something like that: +// extern _Unwind_Ptr _Unwind_GetIP (struct _Unwind_Context *); +// extern _Unwind_Ptr _Unwind_GetIPInfo (struct _Unwind_Context *, int *); +// +// clang's unwind.h defines something like this: +// uintptr_t _Unwind_GetIP(struct _Unwind_Context* __context); +// +// Even if the _Unwind_GetIPInfo can be linked to, it is not declared, worse we +// cannot just redeclare it because clang's unwind.h doesn't define _Unwind_Ptr +// anyway. +// +// Luckily we can play on the fact that the guard macros have a different name: +#ifdef __CLANG_UNWIND_H +// In fact, this function still comes from libgcc (on my different linux boxes, +// clang links against libgcc). +#include +extern "C" uintptr_t _Unwind_GetIPInfo(_Unwind_Context *, int *); +#endif + +#endif // BACKWARD_HAS_UNWIND == 1 + +#if BACKWARD_HAS_LIBUNWIND == 1 +#define UNW_LOCAL_ONLY +#include +#endif // BACKWARD_HAS_LIBUNWIND == 1 + +#ifdef BACKWARD_ATLEAST_CXX11 +#include +#include // for std::swap +namespace backward { +namespace details { +template struct hashtable { + typedef std::unordered_map type; +}; +using std::move; +} // namespace details +} // namespace backward +#else // NOT BACKWARD_ATLEAST_CXX11 +#define nullptr NULL +#define override +#include +namespace backward { +namespace details { +template struct hashtable { + typedef std::map type; +}; +template const T &move(const T &v) { return v; } +template T &move(T &v) { return v; } +} // namespace details +} // namespace backward +#endif // BACKWARD_ATLEAST_CXX11 + +namespace backward { +namespace details { +#if defined(BACKWARD_SYSTEM_WINDOWS) +const char kBackwardPathDelimiter[] = ";"; +#else +const char kBackwardPathDelimiter[] = ":"; +#endif +} // namespace details +} // namespace backward + +namespace backward { + +namespace system_tag { +struct linux_tag; // seems that I cannot call that "linux" because the name +// is already defined... so I am adding _tag everywhere. +struct darwin_tag; +struct windows_tag; +struct unknown_tag; + +#if defined(BACKWARD_SYSTEM_LINUX) +typedef linux_tag current_tag; +#elif defined(BACKWARD_SYSTEM_DARWIN) +typedef darwin_tag current_tag; +#elif defined(BACKWARD_SYSTEM_WINDOWS) +typedef windows_tag current_tag; +#elif defined(BACKWARD_SYSTEM_UNKNOWN) +typedef unknown_tag current_tag; +#else +#error "May I please get my system defines?" +#endif +} // namespace system_tag + +namespace trace_resolver_tag { +#if defined(BACKWARD_SYSTEM_LINUX) +struct libdw; +struct libbfd; +struct libdwarf; +struct backtrace_symbol; + +#if BACKWARD_HAS_DW == 1 +typedef libdw current; +#elif BACKWARD_HAS_BFD == 1 +typedef libbfd current; +#elif BACKWARD_HAS_DWARF == 1 +typedef libdwarf current; +#elif BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +typedef backtrace_symbol current; +#else +#error "You shall not pass, until you know what you want." +#endif +#elif defined(BACKWARD_SYSTEM_DARWIN) +struct backtrace_symbol; + +#if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +typedef backtrace_symbol current; +#else +#error "You shall not pass, until you know what you want." +#endif +#elif defined(BACKWARD_SYSTEM_WINDOWS) +struct pdb_symbol; +#if BACKWARD_HAS_PDB_SYMBOL == 1 +typedef pdb_symbol current; +#else +#error "You shall not pass, until you know what you want." +#endif +#endif +} // namespace trace_resolver_tag + +namespace details { + +template struct rm_ptr { + typedef T type; +}; + +template struct rm_ptr { + typedef T type; +}; + +template struct rm_ptr { + typedef const T type; +}; + +template struct deleter { + template void operator()(U &ptr) const { (*F)(ptr); } +}; + +template struct default_delete { + void operator()(T &ptr) const { delete ptr; } +}; + +template > +class handle { + struct dummy; + T _val; + bool _empty; + +#ifdef BACKWARD_ATLEAST_CXX11 + handle(const handle &) = delete; + handle &operator=(const handle &) = delete; +#endif + +public: + ~handle() { + if (!_empty) { + Deleter()(_val); + } + } + + explicit handle() : _val(), _empty(true) {} + explicit handle(T val) : _val(val), _empty(false) { + if (!_val) + _empty = true; + } + +#ifdef BACKWARD_ATLEAST_CXX11 + handle(handle &&from) : _empty(true) { swap(from); } + handle &operator=(handle &&from) { + swap(from); + return *this; + } +#else + explicit handle(const handle &from) : _empty(true) { + // some sort of poor man's move semantic. + swap(const_cast(from)); + } + handle &operator=(const handle &from) { + // some sort of poor man's move semantic. + swap(const_cast(from)); + return *this; + } +#endif + + void reset(T new_val) { + handle tmp(new_val); + swap(tmp); + } + + void update(T new_val) { + _val = new_val; + _empty = !static_cast(new_val); + } + + operator const dummy *() const { + if (_empty) { + return nullptr; + } + return reinterpret_cast(_val); + } + T get() { return _val; } + T release() { + _empty = true; + return _val; + } + void swap(handle &b) { + using std::swap; + swap(b._val, _val); // can throw, we are safe here. + swap(b._empty, _empty); // should not throw: if you cannot swap two + // bools without throwing... It's a lost cause anyway! + } + + T &operator->() { return _val; } + const T &operator->() const { return _val; } + + typedef typename rm_ptr::type &ref_t; + typedef const typename rm_ptr::type &const_ref_t; + ref_t operator*() { return *_val; } + const_ref_t operator*() const { return *_val; } + ref_t operator[](size_t idx) { return _val[idx]; } + + // Watch out, we've got a badass over here + T *operator&() { + _empty = false; + return &_val; + } +}; + +// Default demangler implementation (do nothing). +template struct demangler_impl { + static std::string demangle(const char *funcname) { return funcname; } +}; + +#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) + +template <> struct demangler_impl { + demangler_impl() : _demangle_buffer_length(0) {} + + std::string demangle(const char *funcname) { + using namespace details; + char *result = abi::__cxa_demangle(funcname, _demangle_buffer.get(), + &_demangle_buffer_length, nullptr); + if (result) { + _demangle_buffer.update(result); + return result; + } + return funcname; + } + +private: + details::handle _demangle_buffer; + size_t _demangle_buffer_length; +}; + +#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN + +struct demangler : public demangler_impl {}; + +// Split a string on the platform's PATH delimiter. Example: if delimiter +// is ":" then: +// "" --> [] +// ":" --> ["",""] +// "::" --> ["","",""] +// "/a/b/c" --> ["/a/b/c"] +// "/a/b/c:/d/e/f" --> ["/a/b/c","/d/e/f"] +// etc. +inline std::vector split_source_prefixes(const std::string &s) { + std::vector out; + size_t last = 0; + size_t next = 0; + size_t delimiter_size = sizeof(kBackwardPathDelimiter) - 1; + while ((next = s.find(kBackwardPathDelimiter, last)) != std::string::npos) { + out.push_back(s.substr(last, next - last)); + last = next + delimiter_size; + } + if (last <= s.length()) { + out.push_back(s.substr(last)); + } + return out; +} + +} // namespace details + +/*************** A TRACE ***************/ + +struct Trace { + void *addr; + size_t idx; + + Trace() : addr(nullptr), idx(0) {} + + explicit Trace(void *_addr, size_t _idx) : addr(_addr), idx(_idx) {} +}; + +struct ResolvedTrace : public Trace { + + struct SourceLoc { + std::string function; + std::string filename; + unsigned line; + unsigned col; + + SourceLoc() : line(0), col(0) {} + + bool operator==(const SourceLoc &b) const { + return function == b.function && filename == b.filename && + line == b.line && col == b.col; + } + + bool operator!=(const SourceLoc &b) const { return !(*this == b); } + }; + + // In which binary object this trace is located. + std::string object_filename; + + // The function in the object that contain the trace. This is not the same + // as source.function which can be an function inlined in object_function. + std::string object_function; + + // The source location of this trace. It is possible for filename to be + // empty and for line/col to be invalid (value 0) if this information + // couldn't be deduced, for example if there is no debug information in the + // binary object. + SourceLoc source; + + // An optionals list of "inliners". All the successive sources location + // from where the source location of the trace (the attribute right above) + // is inlined. It is especially useful when you compiled with optimization. + typedef std::vector source_locs_t; + source_locs_t inliners; + + ResolvedTrace() : Trace() {} + ResolvedTrace(const Trace &mini_trace) : Trace(mini_trace) {} +}; + +/*************** STACK TRACE ***************/ + +// default implemention. +template class StackTraceImpl { +public: + size_t size() const { return 0; } + Trace operator[](size_t) const { return Trace(); } + size_t load_here(size_t = 0) { return 0; } + size_t load_from(void *, size_t = 0, void * = nullptr, void * = nullptr) { + return 0; + } + size_t thread_id() const { return 0; } + void skip_n_firsts(size_t) {} + void *const *begin() const { return nullptr; } +}; + +class StackTraceImplBase { +public: + StackTraceImplBase() + : _thread_id(0), _skip(0), _context(nullptr), _error_addr(nullptr) {} + + size_t thread_id() const { return _thread_id; } + + void skip_n_firsts(size_t n) { _skip = n; } + +protected: + void load_thread_info() { +#ifdef BACKWARD_SYSTEM_LINUX +#ifndef __ANDROID__ + _thread_id = static_cast(syscall(SYS_gettid)); +#else + _thread_id = static_cast(gettid()); +#endif + if (_thread_id == static_cast(getpid())) { + // If the thread is the main one, let's hide that. + // I like to keep little secret sometimes. + _thread_id = 0; + } +#elif defined(BACKWARD_SYSTEM_DARWIN) + _thread_id = reinterpret_cast(pthread_self()); + if (pthread_main_np() == 1) { + // If the thread is the main one, let's hide that. + _thread_id = 0; + } +#endif + } + + void set_context(void *context) { _context = context; } + void *context() const { return _context; } + + void set_error_addr(void *error_addr) { _error_addr = error_addr; } + void *error_addr() const { return _error_addr; } + + size_t skip_n_firsts() const { return _skip; } + +private: + size_t _thread_id; + size_t _skip; + void *_context; + void *_error_addr; +}; + +class StackTraceImplHolder : public StackTraceImplBase { +public: + size_t size() const { + return (_stacktrace.size() >= skip_n_firsts()) + ? _stacktrace.size() - skip_n_firsts() + : 0; + } + Trace operator[](size_t idx) const { + if (idx >= size()) { + return Trace(); + } + return Trace(_stacktrace[idx + skip_n_firsts()], idx); + } + void *const *begin() const { + if (size()) { + return &_stacktrace[skip_n_firsts()]; + } + return nullptr; + } + +protected: + std::vector _stacktrace; +}; + +#if BACKWARD_HAS_UNWIND == 1 + +namespace details { + +template class Unwinder { +public: + size_t operator()(F &f, size_t depth) { + _f = &f; + _index = -1; + _depth = depth; + _Unwind_Backtrace(&this->backtrace_trampoline, this); + if (_index == -1) { + // _Unwind_Backtrace has failed to obtain any backtraces + return 0; + } else { + return static_cast(_index); + } + } + +private: + F *_f; + ssize_t _index; + size_t _depth; + + static _Unwind_Reason_Code backtrace_trampoline(_Unwind_Context *ctx, + void *self) { + return (static_cast(self))->backtrace(ctx); + } + + _Unwind_Reason_Code backtrace(_Unwind_Context *ctx) { + if (_index >= 0 && static_cast(_index) >= _depth) + return _URC_END_OF_STACK; + + int ip_before_instruction = 0; + uintptr_t ip = _Unwind_GetIPInfo(ctx, &ip_before_instruction); + + if (!ip_before_instruction) { + // calculating 0-1 for unsigned, looks like a possible bug to sanitizers, + // so let's do it explicitly: + if (ip == 0) { + ip = std::numeric_limits::max(); // set it to 0xffff... (as + // from casting 0-1) + } else { + ip -= 1; // else just normally decrement it (no overflow/underflow will + // happen) + } + } + + if (_index >= 0) { // ignore first frame. + (*_f)(static_cast(_index), reinterpret_cast(ip)); + } + _index += 1; + return _URC_NO_REASON; + } +}; + +template size_t unwind(F f, size_t depth) { + Unwinder unwinder; + return unwinder(f, depth); +} + +} // namespace details + +template <> +class StackTraceImpl : public StackTraceImplHolder { +public: + NOINLINE + size_t load_here(size_t depth = 32, void *context = nullptr, + void *error_addr = nullptr) { + load_thread_info(); + set_context(context); + set_error_addr(error_addr); + if (depth == 0) { + return 0; + } + _stacktrace.resize(depth); + size_t trace_cnt = details::unwind(callback(*this), depth); + _stacktrace.resize(trace_cnt); + skip_n_firsts(0); + return size(); + } + size_t load_from(void *addr, size_t depth = 32, void *context = nullptr, + void *error_addr = nullptr) { + load_here(depth + 8, context, error_addr); + + for (size_t i = 0; i < _stacktrace.size(); ++i) { + if (_stacktrace[i] == addr) { + skip_n_firsts(i); + break; + } + } + + _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth)); + return size(); + } + +private: + struct callback { + StackTraceImpl &self; + callback(StackTraceImpl &_self) : self(_self) {} + + void operator()(size_t idx, void *addr) { self._stacktrace[idx] = addr; } + }; +}; + +#elif BACKWARD_HAS_LIBUNWIND == 1 + +template <> +class StackTraceImpl : public StackTraceImplHolder { +public: + __attribute__((noinline)) size_t load_here(size_t depth = 32, + void *_context = nullptr, + void *_error_addr = nullptr) { + set_context(_context); + set_error_addr(_error_addr); + load_thread_info(); + if (depth == 0) { + return 0; + } + _stacktrace.resize(depth + 1); + + int result = 0; + + unw_context_t ctx; + size_t index = 0; + + // Add the tail call. If the Instruction Pointer is the crash address it + // means we got a bad function pointer dereference, so we "unwind" the + // bad pointer manually by using the return address pointed to by the + // Stack Pointer as the Instruction Pointer and letting libunwind do + // the rest + + if (context()) { + ucontext_t *uctx = reinterpret_cast(context()); +#ifdef REG_RIP // x86_64 + if (uctx->uc_mcontext.gregs[REG_RIP] == + reinterpret_cast(error_addr())) { + uctx->uc_mcontext.gregs[REG_RIP] = + *reinterpret_cast(uctx->uc_mcontext.gregs[REG_RSP]); + } + _stacktrace[index] = + reinterpret_cast(uctx->uc_mcontext.gregs[REG_RIP]); + ++index; + ctx = *reinterpret_cast(uctx); +#elif defined(REG_EIP) // x86_32 + if (uctx->uc_mcontext.gregs[REG_EIP] == + reinterpret_cast(error_addr())) { + uctx->uc_mcontext.gregs[REG_EIP] = + *reinterpret_cast(uctx->uc_mcontext.gregs[REG_ESP]); + } + _stacktrace[index] = + reinterpret_cast(uctx->uc_mcontext.gregs[REG_EIP]); + ++index; + ctx = *reinterpret_cast(uctx); +#elif defined(__arm__) + // libunwind uses its own context type for ARM unwinding. + // Copy the registers from the signal handler's context so we can + // unwind + unw_getcontext(&ctx); + ctx.regs[UNW_ARM_R0] = uctx->uc_mcontext.arm_r0; + ctx.regs[UNW_ARM_R1] = uctx->uc_mcontext.arm_r1; + ctx.regs[UNW_ARM_R2] = uctx->uc_mcontext.arm_r2; + ctx.regs[UNW_ARM_R3] = uctx->uc_mcontext.arm_r3; + ctx.regs[UNW_ARM_R4] = uctx->uc_mcontext.arm_r4; + ctx.regs[UNW_ARM_R5] = uctx->uc_mcontext.arm_r5; + ctx.regs[UNW_ARM_R6] = uctx->uc_mcontext.arm_r6; + ctx.regs[UNW_ARM_R7] = uctx->uc_mcontext.arm_r7; + ctx.regs[UNW_ARM_R8] = uctx->uc_mcontext.arm_r8; + ctx.regs[UNW_ARM_R9] = uctx->uc_mcontext.arm_r9; + ctx.regs[UNW_ARM_R10] = uctx->uc_mcontext.arm_r10; + ctx.regs[UNW_ARM_R11] = uctx->uc_mcontext.arm_fp; + ctx.regs[UNW_ARM_R12] = uctx->uc_mcontext.arm_ip; + ctx.regs[UNW_ARM_R13] = uctx->uc_mcontext.arm_sp; + ctx.regs[UNW_ARM_R14] = uctx->uc_mcontext.arm_lr; + ctx.regs[UNW_ARM_R15] = uctx->uc_mcontext.arm_pc; + + // If we have crashed in the PC use the LR instead, as this was + // a bad function dereference + if (reinterpret_cast(error_addr()) == + uctx->uc_mcontext.arm_pc) { + ctx.regs[UNW_ARM_R15] = + uctx->uc_mcontext.arm_lr - sizeof(unsigned long); + } + _stacktrace[index] = reinterpret_cast(ctx.regs[UNW_ARM_R15]); + ++index; +#elif defined(__APPLE__) && defined(__x86_64__) + unw_getcontext(&ctx); + // OS X's implementation of libunwind uses its own context object + // so we need to convert the passed context to libunwind's format + // (information about the data layout taken from unw_getcontext.s + // in Apple's libunwind source + ctx.data[0] = uctx->uc_mcontext->__ss.__rax; + ctx.data[1] = uctx->uc_mcontext->__ss.__rbx; + ctx.data[2] = uctx->uc_mcontext->__ss.__rcx; + ctx.data[3] = uctx->uc_mcontext->__ss.__rdx; + ctx.data[4] = uctx->uc_mcontext->__ss.__rdi; + ctx.data[5] = uctx->uc_mcontext->__ss.__rsi; + ctx.data[6] = uctx->uc_mcontext->__ss.__rbp; + ctx.data[7] = uctx->uc_mcontext->__ss.__rsp; + ctx.data[8] = uctx->uc_mcontext->__ss.__r8; + ctx.data[9] = uctx->uc_mcontext->__ss.__r9; + ctx.data[10] = uctx->uc_mcontext->__ss.__r10; + ctx.data[11] = uctx->uc_mcontext->__ss.__r11; + ctx.data[12] = uctx->uc_mcontext->__ss.__r12; + ctx.data[13] = uctx->uc_mcontext->__ss.__r13; + ctx.data[14] = uctx->uc_mcontext->__ss.__r14; + ctx.data[15] = uctx->uc_mcontext->__ss.__r15; + ctx.data[16] = uctx->uc_mcontext->__ss.__rip; + + // If the IP is the same as the crash address we have a bad function + // dereference The caller's address is pointed to by %rsp, so we + // dereference that value and set it to be the next frame's IP. + if (uctx->uc_mcontext->__ss.__rip == + reinterpret_cast<__uint64_t>(error_addr())) { + ctx.data[16] = + *reinterpret_cast<__uint64_t *>(uctx->uc_mcontext->__ss.__rsp); + } + _stacktrace[index] = reinterpret_cast(ctx.data[16]); + ++index; +#elif defined(__APPLE__) + unw_getcontext(&ctx) + // TODO: Convert the ucontext_t to libunwind's unw_context_t like + // we do in 64 bits + if (ctx.uc_mcontext->__ss.__eip == + reinterpret_cast(error_addr())) { + ctx.uc_mcontext->__ss.__eip = ctx.uc_mcontext->__ss.__esp; + } + _stacktrace[index] = + reinterpret_cast(ctx.uc_mcontext->__ss.__eip); + ++index; +#endif + } + + unw_cursor_t cursor; + if (context()) { +#if defined(UNW_INIT_SIGNAL_FRAME) + result = unw_init_local2(&cursor, &ctx, UNW_INIT_SIGNAL_FRAME); +#else + result = unw_init_local(&cursor, &ctx); +#endif + } else { + unw_getcontext(&ctx); + ; + result = unw_init_local(&cursor, &ctx); + } + + if (result != 0) + return 1; + + unw_word_t ip = 0; + + while (index <= depth && unw_step(&cursor) > 0) { + result = unw_get_reg(&cursor, UNW_REG_IP, &ip); + if (result == 0) { + _stacktrace[index] = reinterpret_cast(--ip); + ++index; + } + } + --index; + + _stacktrace.resize(index + 1); + skip_n_firsts(0); + return size(); + } + + size_t load_from(void *addr, size_t depth = 32, void *context = nullptr, + void *error_addr = nullptr) { + load_here(depth + 8, context, error_addr); + + for (size_t i = 0; i < _stacktrace.size(); ++i) { + if (_stacktrace[i] == addr) { + skip_n_firsts(i); + _stacktrace[i] = (void *)((uintptr_t)_stacktrace[i]); + break; + } + } + + _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth)); + return size(); + } +}; + +#elif defined(BACKWARD_HAS_BACKTRACE) + +template <> +class StackTraceImpl : public StackTraceImplHolder { +public: + NOINLINE + size_t load_here(size_t depth = 32, void *context = nullptr, + void *error_addr = nullptr) { + set_context(context); + set_error_addr(error_addr); + load_thread_info(); + if (depth == 0) { + return 0; + } + _stacktrace.resize(depth + 1); + size_t trace_cnt = backtrace(&_stacktrace[0], _stacktrace.size()); + _stacktrace.resize(trace_cnt); + skip_n_firsts(1); + return size(); + } + + size_t load_from(void *addr, size_t depth = 32, void *context = nullptr, + void *error_addr = nullptr) { + load_here(depth + 8, context, error_addr); + + for (size_t i = 0; i < _stacktrace.size(); ++i) { + if (_stacktrace[i] == addr) { + skip_n_firsts(i); + _stacktrace[i] = (void *)((uintptr_t)_stacktrace[i] + 1); + break; + } + } + + _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth)); + return size(); + } +}; + +#elif defined(BACKWARD_SYSTEM_WINDOWS) + +template <> +class StackTraceImpl : public StackTraceImplHolder { +public: + // We have to load the machine type from the image info + // So we first initialize the resolver, and it tells us this info + void set_machine_type(DWORD machine_type) { machine_type_ = machine_type; } + void set_context(CONTEXT *ctx) { ctx_ = ctx; } + void set_thread_handle(HANDLE handle) { thd_ = handle; } + + NOINLINE + size_t load_here(size_t depth = 32, void *context = nullptr, + void *error_addr = nullptr) { + set_context(static_cast(context)); + set_error_addr(error_addr); + CONTEXT localCtx; // used when no context is provided + + if (depth == 0) { + return 0; + } + + if (!ctx_) { + ctx_ = &localCtx; + RtlCaptureContext(ctx_); + } + + if (!thd_) { + thd_ = GetCurrentThread(); + } + + HANDLE process = GetCurrentProcess(); + + STACKFRAME64 s; + memset(&s, 0, sizeof(STACKFRAME64)); + + // TODO: 32 bit context capture + s.AddrStack.Mode = AddrModeFlat; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrPC.Mode = AddrModeFlat; +#ifdef _M_X64 + s.AddrPC.Offset = ctx_->Rip; + s.AddrStack.Offset = ctx_->Rsp; + s.AddrFrame.Offset = ctx_->Rbp; +#else + s.AddrPC.Offset = ctx_->Eip; + s.AddrStack.Offset = ctx_->Esp; + s.AddrFrame.Offset = ctx_->Ebp; +#endif + + if (!machine_type_) { +#ifdef _M_X64 + machine_type_ = IMAGE_FILE_MACHINE_AMD64; +#else + machine_type_ = IMAGE_FILE_MACHINE_I386; +#endif + } + + for (;;) { + // NOTE: this only works if PDBs are already loaded! + SetLastError(0); + if (!StackWalk64(machine_type_, process, thd_, &s, ctx_, NULL, + SymFunctionTableAccess64, SymGetModuleBase64, NULL)) + break; + + if (s.AddrReturn.Offset == 0) + break; + + _stacktrace.push_back(reinterpret_cast(s.AddrPC.Offset)); + + if (size() >= depth) + break; + } + + return size(); + } + + size_t load_from(void *addr, size_t depth = 32, void *context = nullptr, + void *error_addr = nullptr) { + load_here(depth + 8, context, error_addr); + + for (size_t i = 0; i < _stacktrace.size(); ++i) { + if (_stacktrace[i] == addr) { + skip_n_firsts(i); + break; + } + } + + _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth)); + return size(); + } + +private: + DWORD machine_type_ = 0; + HANDLE thd_ = 0; + CONTEXT *ctx_ = nullptr; +}; + +#endif + +class StackTrace : public StackTraceImpl {}; + +/*************** TRACE RESOLVER ***************/ + +class TraceResolverImplBase { +public: + virtual ~TraceResolverImplBase() {} + + virtual void load_addresses(void *const *addresses, int address_count) { + (void)addresses; + (void)address_count; + } + + template void load_stacktrace(ST &st) { + load_addresses(st.begin(), static_cast(st.size())); + } + + virtual ResolvedTrace resolve(ResolvedTrace t) { return t; } + +protected: + std::string demangle(const char *funcname) { + return _demangler.demangle(funcname); + } + +private: + details::demangler _demangler; +}; + +template class TraceResolverImpl; + +#ifdef BACKWARD_SYSTEM_UNKNOWN + +template <> +class TraceResolverImpl + : public TraceResolverImplBase {}; + +#endif + +#ifdef BACKWARD_SYSTEM_LINUX + +class TraceResolverLinuxBase : public TraceResolverImplBase { +public: + TraceResolverLinuxBase() + : argv0_(get_argv0()), exec_path_(read_symlink("/proc/self/exe")) {} + std::string resolve_exec_path(Dl_info &symbol_info) const { + // mutates symbol_info.dli_fname to be filename to open and returns filename + // to display + if (symbol_info.dli_fname == argv0_) { + // dladdr returns argv[0] in dli_fname for symbols contained in + // the main executable, which is not a valid path if the + // executable was found by a search of the PATH environment + // variable; In that case, we actually open /proc/self/exe, which + // is always the actual executable (even if it was deleted/replaced!) + // but display the path that /proc/self/exe links to. + // However, this right away reduces probability of successful symbol + // resolution, because libbfd may try to find *.debug files in the + // same dir, in case symbols are stripped. As a result, it may try + // to find a file /proc/self/.debug, which obviously does + // not exist. /proc/self/exe is a last resort. First load attempt + // should go for the original executable file path. + symbol_info.dli_fname = "/proc/self/exe"; + return exec_path_; + } else { + return symbol_info.dli_fname; + } + } + +private: + std::string argv0_; + std::string exec_path_; + + static std::string get_argv0() { + std::string argv0; + std::ifstream ifs("/proc/self/cmdline"); + std::getline(ifs, argv0, '\0'); + return argv0; + } + + static std::string read_symlink(std::string const &symlink_path) { + std::string path; + path.resize(100); + + while (true) { + ssize_t len = + ::readlink(symlink_path.c_str(), &*path.begin(), path.size()); + if (len < 0) { + return ""; + } + if (static_cast(len) == path.size()) { + path.resize(path.size() * 2); + } else { + path.resize(static_cast(len)); + break; + } + } + + return path; + } +}; + +template class TraceResolverLinuxImpl; + +#if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 + +template <> +class TraceResolverLinuxImpl + : public TraceResolverLinuxBase { +public: + void load_addresses(void *const *addresses, int address_count) override { + if (address_count == 0) { + return; + } + _symbols.reset(backtrace_symbols(addresses, address_count)); + } + + ResolvedTrace resolve(ResolvedTrace trace) override { + char *filename = _symbols[trace.idx]; + char *funcname = filename; + while (*funcname && *funcname != '(') { + funcname += 1; + } + trace.object_filename.assign(filename, + funcname); // ok even if funcname is the ending + // \0 (then we assign entire string) + + if (*funcname) { // if it's not end of string (e.g. from last frame ip==0) + funcname += 1; + char *funcname_end = funcname; + while (*funcname_end && *funcname_end != ')' && *funcname_end != '+') { + funcname_end += 1; + } + *funcname_end = '\0'; + trace.object_function = this->demangle(funcname); + trace.source.function = trace.object_function; // we cannot do better. + } + return trace; + } + +private: + details::handle _symbols; +}; + +#endif // BACKWARD_HAS_BACKTRACE_SYMBOL == 1 + +#if BACKWARD_HAS_BFD == 1 + +template <> +class TraceResolverLinuxImpl + : public TraceResolverLinuxBase { +public: + TraceResolverLinuxImpl() : _bfd_loaded(false) {} + + ResolvedTrace resolve(ResolvedTrace trace) override { + Dl_info symbol_info; + + // trace.addr is a virtual address in memory pointing to some code. + // Let's try to find from which loaded object it comes from. + // The loaded object can be yourself btw. + if (!dladdr(trace.addr, &symbol_info)) { + return trace; // dat broken trace... + } + + // Now we get in symbol_info: + // .dli_fname: + // pathname of the shared object that contains the address. + // .dli_fbase: + // where the object is loaded in memory. + // .dli_sname: + // the name of the nearest symbol to trace.addr, we expect a + // function name. + // .dli_saddr: + // the exact address corresponding to .dli_sname. + + if (symbol_info.dli_sname) { + trace.object_function = demangle(symbol_info.dli_sname); + } + + if (!symbol_info.dli_fname) { + return trace; + } + + trace.object_filename = resolve_exec_path(symbol_info); + bfd_fileobject *fobj; + // Before rushing to resolution need to ensure the executable + // file still can be used. For that compare inode numbers of + // what is stored by the executable's file path, and in the + // dli_fname, which not necessarily equals to the executable. + // It can be a shared library, or /proc/self/exe, and in the + // latter case has drawbacks. See the exec path resolution for + // details. In short - the dli object should be used only as + // the last resort. + // If inode numbers are equal, it is known dli_fname and the + // executable file are the same. This is guaranteed by Linux, + // because if the executable file is changed/deleted, it will + // be done in a new inode. The old file will be preserved in + // /proc/self/exe, and may even have inode 0. The latter can + // happen if the inode was actually reused, and the file was + // kept only in the main memory. + // + struct stat obj_stat; + struct stat dli_stat; + if (stat(trace.object_filename.c_str(), &obj_stat) == 0 && + stat(symbol_info.dli_fname, &dli_stat) == 0 && + obj_stat.st_ino == dli_stat.st_ino) { + // The executable file, and the shared object containing the + // address are the same file. Safe to use the original path. + // this is preferable. Libbfd will search for stripped debug + // symbols in the same directory. + fobj = load_object_with_bfd(trace.object_filename); + } else { + // The original object file was *deleted*! The only hope is + // that the debug symbols are either inside the shared + // object file, or are in the same directory, and this is + // not /proc/self/exe. + fobj = nullptr; + } + if (fobj == nullptr || !fobj->handle) { + fobj = load_object_with_bfd(symbol_info.dli_fname); + if (!fobj->handle) { + return trace; + } + } + + find_sym_result *details_selected; // to be filled. + + // trace.addr is the next instruction to be executed after returning + // from the nested stack frame. In C++ this usually relate to the next + // statement right after the function call that leaded to a new stack + // frame. This is not usually what you want to see when printing out a + // stacktrace... + find_sym_result details_call_site = + find_symbol_details(fobj, trace.addr, symbol_info.dli_fbase); + details_selected = &details_call_site; + +#if BACKWARD_HAS_UNWIND == 0 + // ...this is why we also try to resolve the symbol that is right + // before the return address. If we are lucky enough, we will get the + // line of the function that was called. But if the code is optimized, + // we might get something absolutely not related since the compiler + // can reschedule the return address with inline functions and + // tail-call optimization (among other things that I don't even know + // or cannot even dream about with my tiny limited brain). + find_sym_result details_adjusted_call_site = find_symbol_details( + fobj, (void *)(uintptr_t(trace.addr) - 1), symbol_info.dli_fbase); + + // In debug mode, we should always get the right thing(TM). + if (details_call_site.found && details_adjusted_call_site.found) { + // Ok, we assume that details_adjusted_call_site is a better estimation. + details_selected = &details_adjusted_call_site; + trace.addr = (void *)(uintptr_t(trace.addr) - 1); + } + + if (details_selected == &details_call_site && details_call_site.found) { + // we have to re-resolve the symbol in order to reset some + // internal state in BFD... so we can call backtrace_inliners + // thereafter... + details_call_site = + find_symbol_details(fobj, trace.addr, symbol_info.dli_fbase); + } +#endif // BACKWARD_HAS_UNWIND + + if (details_selected->found) { + if (details_selected->filename) { + trace.source.filename = details_selected->filename; + } + trace.source.line = details_selected->line; + + if (details_selected->funcname) { + // this time we get the name of the function where the code is + // located, instead of the function were the address is + // located. In short, if the code was inlined, we get the + // function corresponding to the code. Else we already got in + // trace.function. + trace.source.function = demangle(details_selected->funcname); + + if (!symbol_info.dli_sname) { + // for the case dladdr failed to find the symbol name of + // the function, we might as well try to put something + // here. + trace.object_function = trace.source.function; + } + } + + // Maybe the source of the trace got inlined inside the function + // (trace.source.function). Let's see if we can get all the inlined + // calls along the way up to the initial call site. + trace.inliners = backtrace_inliners(fobj, *details_selected); + +#if 0 + if (trace.inliners.size() == 0) { + // Maybe the trace was not inlined... or maybe it was and we + // are lacking the debug information. Let's try to make the + // world better and see if we can get the line number of the + // function (trace.source.function) now. + // + // We will get the location of where the function start (to be + // exact: the first instruction that really start the + // function), not where the name of the function is defined. + // This can be quite far away from the name of the function + // btw. + // + // If the source of the function is the same as the source of + // the trace, we cannot say if the trace was really inlined or + // not. However, if the filename of the source is different + // between the function and the trace... we can declare it as + // an inliner. This is not 100% accurate, but better than + // nothing. + + if (symbol_info.dli_saddr) { + find_sym_result details = find_symbol_details(fobj, + symbol_info.dli_saddr, + symbol_info.dli_fbase); + + if (details.found) { + ResolvedTrace::SourceLoc diy_inliner; + diy_inliner.line = details.line; + if (details.filename) { + diy_inliner.filename = details.filename; + } + if (details.funcname) { + diy_inliner.function = demangle(details.funcname); + } else { + diy_inliner.function = trace.source.function; + } + if (diy_inliner != trace.source) { + trace.inliners.push_back(diy_inliner); + } + } + } + } +#endif + } + + return trace; + } + +private: + bool _bfd_loaded; + + typedef details::handle> + bfd_handle_t; + + typedef details::handle bfd_symtab_t; + + struct bfd_fileobject { + bfd_handle_t handle; + bfd_vma base_addr; + bfd_symtab_t symtab; + bfd_symtab_t dynamic_symtab; + }; + + typedef details::hashtable::type fobj_bfd_map_t; + fobj_bfd_map_t _fobj_bfd_map; + + bfd_fileobject *load_object_with_bfd(const std::string &filename_object) { + using namespace details; + + if (!_bfd_loaded) { + using namespace details; + bfd_init(); + _bfd_loaded = true; + } + + fobj_bfd_map_t::iterator it = _fobj_bfd_map.find(filename_object); + if (it != _fobj_bfd_map.end()) { + return &it->second; + } + + // this new object is empty for now. + bfd_fileobject *r = &_fobj_bfd_map[filename_object]; + + // we do the work temporary in this one; + bfd_handle_t bfd_handle; + + int fd = open(filename_object.c_str(), O_RDONLY); + bfd_handle.reset(bfd_fdopenr(filename_object.c_str(), "default", fd)); + if (!bfd_handle) { + close(fd); + return r; + } + + if (!bfd_check_format(bfd_handle.get(), bfd_object)) { + return r; // not an object? You lose. + } + + if ((bfd_get_file_flags(bfd_handle.get()) & HAS_SYMS) == 0) { + return r; // that's what happen when you forget to compile in debug. + } + + ssize_t symtab_storage_size = bfd_get_symtab_upper_bound(bfd_handle.get()); + + ssize_t dyn_symtab_storage_size = + bfd_get_dynamic_symtab_upper_bound(bfd_handle.get()); + + if (symtab_storage_size <= 0 && dyn_symtab_storage_size <= 0) { + return r; // weird, is the file is corrupted? + } + + bfd_symtab_t symtab, dynamic_symtab; + ssize_t symcount = 0, dyn_symcount = 0; + + if (symtab_storage_size > 0) { + symtab.reset(static_cast( + malloc(static_cast(symtab_storage_size)))); + symcount = bfd_canonicalize_symtab(bfd_handle.get(), symtab.get()); + } + + if (dyn_symtab_storage_size > 0) { + dynamic_symtab.reset(static_cast( + malloc(static_cast(dyn_symtab_storage_size)))); + dyn_symcount = bfd_canonicalize_dynamic_symtab(bfd_handle.get(), + dynamic_symtab.get()); + } + + if (symcount <= 0 && dyn_symcount <= 0) { + return r; // damned, that's a stripped file that you got there! + } + + r->handle = move(bfd_handle); + r->symtab = move(symtab); + r->dynamic_symtab = move(dynamic_symtab); + return r; + } + + struct find_sym_result { + bool found; + const char *filename; + const char *funcname; + unsigned int line; + }; + + struct find_sym_context { + TraceResolverLinuxImpl *self; + bfd_fileobject *fobj; + void *addr; + void *base_addr; + find_sym_result result; + }; + + find_sym_result find_symbol_details(bfd_fileobject *fobj, void *addr, + void *base_addr) { + find_sym_context context; + context.self = this; + context.fobj = fobj; + context.addr = addr; + context.base_addr = base_addr; + context.result.found = false; + bfd_map_over_sections(fobj->handle.get(), &find_in_section_trampoline, + static_cast(&context)); + return context.result; + } + + static void find_in_section_trampoline(bfd *, asection *section, void *data) { + find_sym_context *context = static_cast(data); + context->self->find_in_section( + reinterpret_cast(context->addr), + reinterpret_cast(context->base_addr), context->fobj, section, + context->result); + } + + void find_in_section(bfd_vma addr, bfd_vma base_addr, bfd_fileobject *fobj, + asection *section, find_sym_result &result) { + if (result.found) + return; + +#ifdef bfd_get_section_flags + if ((bfd_get_section_flags(fobj->handle.get(), section) & SEC_ALLOC) == 0) +#else + if ((bfd_section_flags(section) & SEC_ALLOC) == 0) +#endif + return; // a debug section is never loaded automatically. + +#ifdef bfd_get_section_vma + bfd_vma sec_addr = bfd_get_section_vma(fobj->handle.get(), section); +#else + bfd_vma sec_addr = bfd_section_vma(section); +#endif +#ifdef bfd_get_section_size + bfd_size_type size = bfd_get_section_size(section); +#else + bfd_size_type size = bfd_section_size(section); +#endif + + // are we in the boundaries of the section? + if (addr < sec_addr || addr >= sec_addr + size) { + addr -= base_addr; // oops, a relocated object, lets try again... + if (addr < sec_addr || addr >= sec_addr + size) { + return; + } + } + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif + if (!result.found && fobj->symtab) { + result.found = bfd_find_nearest_line( + fobj->handle.get(), section, fobj->symtab.get(), addr - sec_addr, + &result.filename, &result.funcname, &result.line); + } + + if (!result.found && fobj->dynamic_symtab) { + result.found = bfd_find_nearest_line( + fobj->handle.get(), section, fobj->dynamic_symtab.get(), + addr - sec_addr, &result.filename, &result.funcname, &result.line); + } +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + } + + ResolvedTrace::source_locs_t + backtrace_inliners(bfd_fileobject *fobj, find_sym_result previous_result) { + // This function can be called ONLY after a SUCCESSFUL call to + // find_symbol_details. The state is global to the bfd_handle. + ResolvedTrace::source_locs_t results; + while (previous_result.found) { + find_sym_result result; + result.found = bfd_find_inliner_info(fobj->handle.get(), &result.filename, + &result.funcname, &result.line); + + if (result + .found) /* and not ( + cstrings_eq(previous_result.filename, + result.filename) and + cstrings_eq(previous_result.funcname, result.funcname) + and result.line == previous_result.line + )) */ + { + ResolvedTrace::SourceLoc src_loc; + src_loc.line = result.line; + if (result.filename) { + src_loc.filename = result.filename; + } + if (result.funcname) { + src_loc.function = demangle(result.funcname); + } + results.push_back(src_loc); + } + previous_result = result; + } + return results; + } + + bool cstrings_eq(const char *a, const char *b) { + if (!a || !b) { + return false; + } + return strcmp(a, b) == 0; + } +}; +#endif // BACKWARD_HAS_BFD == 1 + +#if BACKWARD_HAS_DW == 1 + +template <> +class TraceResolverLinuxImpl + : public TraceResolverLinuxBase { +public: + TraceResolverLinuxImpl() : _dwfl_handle_initialized(false) {} + + ResolvedTrace resolve(ResolvedTrace trace) override { + using namespace details; + + Dwarf_Addr trace_addr = reinterpret_cast(trace.addr); + + if (!_dwfl_handle_initialized) { + // initialize dwfl... + _dwfl_cb.reset(new Dwfl_Callbacks); + _dwfl_cb->find_elf = &dwfl_linux_proc_find_elf; + _dwfl_cb->find_debuginfo = &dwfl_standard_find_debuginfo; + _dwfl_cb->debuginfo_path = 0; + + _dwfl_handle.reset(dwfl_begin(_dwfl_cb.get())); + _dwfl_handle_initialized = true; + + if (!_dwfl_handle) { + return trace; + } + + // ...from the current process. + dwfl_report_begin(_dwfl_handle.get()); + int r = dwfl_linux_proc_report(_dwfl_handle.get(), getpid()); + dwfl_report_end(_dwfl_handle.get(), NULL, NULL); + if (r < 0) { + return trace; + } + } + + if (!_dwfl_handle) { + return trace; + } + + // find the module (binary object) that contains the trace's address. + // This is not using any debug information, but the addresses ranges of + // all the currently loaded binary object. + Dwfl_Module *mod = dwfl_addrmodule(_dwfl_handle.get(), trace_addr); + if (mod) { + // now that we found it, lets get the name of it, this will be the + // full path to the running binary or one of the loaded library. + const char *module_name = dwfl_module_info(mod, 0, 0, 0, 0, 0, 0, 0); + if (module_name) { + trace.object_filename = module_name; + } + // We also look after the name of the symbol, equal or before this + // address. This is found by walking the symtab. We should get the + // symbol corresponding to the function (mangled) containing the + // address. If the code corresponding to the address was inlined, + // this is the name of the out-most inliner function. + const char *sym_name = dwfl_module_addrname(mod, trace_addr); + if (sym_name) { + trace.object_function = demangle(sym_name); + } + } + + // now let's get serious, and find out the source location (file and + // line number) of the address. + + // This function will look in .debug_aranges for the address and map it + // to the location of the compilation unit DIE in .debug_info and + // return it. + Dwarf_Addr mod_bias = 0; + Dwarf_Die *cudie = dwfl_module_addrdie(mod, trace_addr, &mod_bias); + +#if 1 + if (!cudie) { + // Sadly clang does not generate the section .debug_aranges, thus + // dwfl_module_addrdie will fail early. Clang doesn't either set + // the lowpc/highpc/range info for every compilation unit. + // + // So in order to save the world: + // for every compilation unit, we will iterate over every single + // DIEs. Normally functions should have a lowpc/highpc/range, which + // we will use to infer the compilation unit. + + // note that this is probably badly inefficient. + while ((cudie = dwfl_module_nextcu(mod, cudie, &mod_bias))) { + Dwarf_Die die_mem; + Dwarf_Die *fundie = + find_fundie_by_pc(cudie, trace_addr - mod_bias, &die_mem); + if (fundie) { + break; + } + } + } +#endif + +// #define BACKWARD_I_DO_NOT_RECOMMEND_TO_ENABLE_THIS_HORRIBLE_PIECE_OF_CODE +#ifdef BACKWARD_I_DO_NOT_RECOMMEND_TO_ENABLE_THIS_HORRIBLE_PIECE_OF_CODE + if (!cudie) { + // If it's still not enough, lets dive deeper in the shit, and try + // to save the world again: for every compilation unit, we will + // load the corresponding .debug_line section, and see if we can + // find our address in it. + + Dwarf_Addr cfi_bias; + Dwarf_CFI *cfi_cache = dwfl_module_eh_cfi(mod, &cfi_bias); + + Dwarf_Addr bias; + while ((cudie = dwfl_module_nextcu(mod, cudie, &bias))) { + if (dwarf_getsrc_die(cudie, trace_addr - bias)) { + + // ...but if we get a match, it might be a false positive + // because our (address - bias) might as well be valid in a + // different compilation unit. So we throw our last card on + // the table and lookup for the address into the .eh_frame + // section. + + handle frame; + dwarf_cfi_addrframe(cfi_cache, trace_addr - cfi_bias, &frame); + if (frame) { + break; + } + } + } + } +#endif + + if (!cudie) { + return trace; // this time we lost the game :/ + } + + // Now that we have a compilation unit DIE, this function will be able + // to load the corresponding section in .debug_line (if not already + // loaded) and hopefully find the source location mapped to our + // address. + Dwarf_Line *srcloc = dwarf_getsrc_die(cudie, trace_addr - mod_bias); + + if (srcloc) { + const char *srcfile = dwarf_linesrc(srcloc, 0, 0); + if (srcfile) { + trace.source.filename = srcfile; + } + int line = 0, col = 0; + dwarf_lineno(srcloc, &line); + dwarf_linecol(srcloc, &col); + trace.source.line = static_cast(line); + trace.source.col = static_cast(col); + } + + deep_first_search_by_pc(cudie, trace_addr - mod_bias, + inliners_search_cb(trace)); + if (trace.source.function.size() == 0) { + // fallback. + trace.source.function = trace.object_function; + } + + return trace; + } + +private: + typedef details::handle> + dwfl_handle_t; + details::handle> + _dwfl_cb; + dwfl_handle_t _dwfl_handle; + bool _dwfl_handle_initialized; + + // defined here because in C++98, template function cannot take locally + // defined types... grrr. + struct inliners_search_cb { + void operator()(Dwarf_Die *die) { + switch (dwarf_tag(die)) { + const char *name; + case DW_TAG_subprogram: + if ((name = dwarf_diename(die))) { + trace.source.function = name; + } + break; + + case DW_TAG_inlined_subroutine: + ResolvedTrace::SourceLoc sloc; + Dwarf_Attribute attr_mem; + + if ((name = dwarf_diename(die))) { + sloc.function = name; + } + if ((name = die_call_file(die))) { + sloc.filename = name; + } + + Dwarf_Word line = 0, col = 0; + dwarf_formudata(dwarf_attr(die, DW_AT_call_line, &attr_mem), &line); + dwarf_formudata(dwarf_attr(die, DW_AT_call_column, &attr_mem), &col); + sloc.line = static_cast(line); + sloc.col = static_cast(col); + + trace.inliners.push_back(sloc); + break; + }; + } + ResolvedTrace &trace; + inliners_search_cb(ResolvedTrace &t) : trace(t) {} + }; + + static bool die_has_pc(Dwarf_Die *die, Dwarf_Addr pc) { + Dwarf_Addr low, high; + + // continuous range + if (dwarf_hasattr(die, DW_AT_low_pc) && dwarf_hasattr(die, DW_AT_high_pc)) { + if (dwarf_lowpc(die, &low) != 0) { + return false; + } + if (dwarf_highpc(die, &high) != 0) { + Dwarf_Attribute attr_mem; + Dwarf_Attribute *attr = dwarf_attr(die, DW_AT_high_pc, &attr_mem); + Dwarf_Word value; + if (dwarf_formudata(attr, &value) != 0) { + return false; + } + high = low + value; + } + return pc >= low && pc < high; + } + + // non-continuous range. + Dwarf_Addr base; + ptrdiff_t offset = 0; + while ((offset = dwarf_ranges(die, offset, &base, &low, &high)) > 0) { + if (pc >= low && pc < high) { + return true; + } + } + return false; + } + + static Dwarf_Die *find_fundie_by_pc(Dwarf_Die *parent_die, Dwarf_Addr pc, + Dwarf_Die *result) { + if (dwarf_child(parent_die, result) != 0) { + return 0; + } + + Dwarf_Die *die = result; + do { + switch (dwarf_tag(die)) { + case DW_TAG_subprogram: + case DW_TAG_inlined_subroutine: + if (die_has_pc(die, pc)) { + return result; + } + }; + bool declaration = false; + Dwarf_Attribute attr_mem; + dwarf_formflag(dwarf_attr(die, DW_AT_declaration, &attr_mem), + &declaration); + if (!declaration) { + // let's be curious and look deeper in the tree, + // function are not necessarily at the first level, but + // might be nested inside a namespace, structure etc. + Dwarf_Die die_mem; + Dwarf_Die *indie = find_fundie_by_pc(die, pc, &die_mem); + if (indie) { + *result = die_mem; + return result; + } + } + } while (dwarf_siblingof(die, result) == 0); + return 0; + } + + template + static bool deep_first_search_by_pc(Dwarf_Die *parent_die, Dwarf_Addr pc, + CB cb) { + Dwarf_Die die_mem; + if (dwarf_child(parent_die, &die_mem) != 0) { + return false; + } + + bool branch_has_pc = false; + Dwarf_Die *die = &die_mem; + do { + bool declaration = false; + Dwarf_Attribute attr_mem; + dwarf_formflag(dwarf_attr(die, DW_AT_declaration, &attr_mem), + &declaration); + if (!declaration) { + // let's be curious and look deeper in the tree, function are + // not necessarily at the first level, but might be nested + // inside a namespace, structure, a function, an inlined + // function etc. + branch_has_pc = deep_first_search_by_pc(die, pc, cb); + } + if (!branch_has_pc) { + branch_has_pc = die_has_pc(die, pc); + } + if (branch_has_pc) { + cb(die); + } + } while (dwarf_siblingof(die, &die_mem) == 0); + return branch_has_pc; + } + + static const char *die_call_file(Dwarf_Die *die) { + Dwarf_Attribute attr_mem; + Dwarf_Word file_idx = 0; + + dwarf_formudata(dwarf_attr(die, DW_AT_call_file, &attr_mem), &file_idx); + + if (file_idx == 0) { + return 0; + } + + Dwarf_Die die_mem; + Dwarf_Die *cudie = dwarf_diecu(die, &die_mem, 0, 0); + if (!cudie) { + return 0; + } + + Dwarf_Files *files = 0; + size_t nfiles; + dwarf_getsrcfiles(cudie, &files, &nfiles); + if (!files) { + return 0; + } + + return dwarf_filesrc(files, file_idx, 0, 0); + } +}; +#endif // BACKWARD_HAS_DW == 1 + +#if BACKWARD_HAS_DWARF == 1 + +template <> +class TraceResolverLinuxImpl + : public TraceResolverLinuxBase { +public: + TraceResolverLinuxImpl() : _dwarf_loaded(false) {} + + ResolvedTrace resolve(ResolvedTrace trace) override { + // trace.addr is a virtual address in memory pointing to some code. + // Let's try to find from which loaded object it comes from. + // The loaded object can be yourself btw. + + Dl_info symbol_info; + int dladdr_result = 0; +#if defined(__GLIBC__) + link_map *link_map; + // We request the link map so we can get information about offsets + dladdr_result = + dladdr1(trace.addr, &symbol_info, reinterpret_cast(&link_map), + RTLD_DL_LINKMAP); +#else + // Android doesn't have dladdr1. Don't use the linker map. + dladdr_result = dladdr(trace.addr, &symbol_info); +#endif + if (!dladdr_result) { + return trace; // dat broken trace... + } + + // Now we get in symbol_info: + // .dli_fname: + // pathname of the shared object that contains the address. + // .dli_fbase: + // where the object is loaded in memory. + // .dli_sname: + // the name of the nearest symbol to trace.addr, we expect a + // function name. + // .dli_saddr: + // the exact address corresponding to .dli_sname. + // + // And in link_map: + // .l_addr: + // difference between the address in the ELF file and the address + // in memory + // l_name: + // absolute pathname where the object was found + + if (symbol_info.dli_sname) { + trace.object_function = demangle(symbol_info.dli_sname); + } + + if (!symbol_info.dli_fname) { + return trace; + } + + trace.object_filename = resolve_exec_path(symbol_info); + dwarf_fileobject &fobj = load_object_with_dwarf(symbol_info.dli_fname); + if (!fobj.dwarf_handle) { + return trace; // sad, we couldn't load the object :( + } + +#if defined(__GLIBC__) + // Convert the address to a module relative one by looking at + // the module's loading address in the link map + Dwarf_Addr address = reinterpret_cast(trace.addr) - + reinterpret_cast(link_map->l_addr); +#else + Dwarf_Addr address = reinterpret_cast(trace.addr); +#endif + + if (trace.object_function.empty()) { + symbol_cache_t::iterator it = fobj.symbol_cache.lower_bound(address); + + if (it != fobj.symbol_cache.end()) { + if (it->first != address) { + if (it != fobj.symbol_cache.begin()) { + --it; + } + } + trace.object_function = demangle(it->second.c_str()); + } + } + + // Get the Compilation Unit DIE for the address + Dwarf_Die die = find_die(fobj, address); + + if (!die) { + return trace; // this time we lost the game :/ + } + + // libdwarf doesn't give us direct access to its objects, it always + // allocates a copy for the caller. We keep that copy alive in a cache + // and we deallocate it later when it's no longer required. + die_cache_entry &die_object = get_die_cache(fobj, die); + if (die_object.isEmpty()) + return trace; // We have no line section for this DIE + + die_linemap_t::iterator it = die_object.line_section.lower_bound(address); + + if (it != die_object.line_section.end()) { + if (it->first != address) { + if (it == die_object.line_section.begin()) { + // If we are on the first item of the line section + // but the address does not match it means that + // the address is below the range of the DIE. Give up. + return trace; + } else { + --it; + } + } + } else { + return trace; // We didn't find the address. + } + + // Get the Dwarf_Line that the address points to and call libdwarf + // to get source file, line and column info. + Dwarf_Line line = die_object.line_buffer[it->second]; + Dwarf_Error error = DW_DLE_NE; + + char *filename; + if (dwarf_linesrc(line, &filename, &error) == DW_DLV_OK) { + trace.source.filename = std::string(filename); + dwarf_dealloc(fobj.dwarf_handle.get(), filename, DW_DLA_STRING); + } + + Dwarf_Unsigned number = 0; + if (dwarf_lineno(line, &number, &error) == DW_DLV_OK) { + trace.source.line = number; + } else { + trace.source.line = 0; + } + + if (dwarf_lineoff_b(line, &number, &error) == DW_DLV_OK) { + trace.source.col = number; + } else { + trace.source.col = 0; + } + + std::vector namespace_stack; + deep_first_search_by_pc(fobj, die, address, namespace_stack, + inliners_search_cb(trace, fobj, die)); + + dwarf_dealloc(fobj.dwarf_handle.get(), die, DW_DLA_DIE); + + return trace; + } + +public: + static int close_dwarf(Dwarf_Debug dwarf) { + return dwarf_finish(dwarf, NULL); + } + +private: + bool _dwarf_loaded; + + typedef details::handle> + dwarf_file_t; + + typedef details::handle> + dwarf_elf_t; + + typedef details::handle> + dwarf_handle_t; + + typedef std::map die_linemap_t; + + typedef std::map die_specmap_t; + + struct die_cache_entry { + die_specmap_t spec_section; + die_linemap_t line_section; + Dwarf_Line *line_buffer; + Dwarf_Signed line_count; + Dwarf_Line_Context line_context; + + inline bool isEmpty() { + return line_buffer == NULL || line_count == 0 || line_context == NULL || + line_section.empty(); + } + + die_cache_entry() : line_buffer(0), line_count(0), line_context(0) {} + + ~die_cache_entry() { + if (line_context) { + dwarf_srclines_dealloc_b(line_context); + } + } + }; + + typedef std::map die_cache_t; + + typedef std::map symbol_cache_t; + + struct dwarf_fileobject { + dwarf_file_t file_handle; + dwarf_elf_t elf_handle; + dwarf_handle_t dwarf_handle; + symbol_cache_t symbol_cache; + + // Die cache + die_cache_t die_cache; + die_cache_entry *current_cu; + }; + + typedef details::hashtable::type + fobj_dwarf_map_t; + fobj_dwarf_map_t _fobj_dwarf_map; + + static bool cstrings_eq(const char *a, const char *b) { + if (!a || !b) { + return false; + } + return strcmp(a, b) == 0; + } + + dwarf_fileobject &load_object_with_dwarf(const std::string &filename_object) { + + if (!_dwarf_loaded) { + // Set the ELF library operating version + // If that fails there's nothing we can do + _dwarf_loaded = elf_version(EV_CURRENT) != EV_NONE; + } + + fobj_dwarf_map_t::iterator it = _fobj_dwarf_map.find(filename_object); + if (it != _fobj_dwarf_map.end()) { + return it->second; + } + + // this new object is empty for now + dwarf_fileobject &r = _fobj_dwarf_map[filename_object]; + + dwarf_file_t file_handle; + file_handle.reset(open(filename_object.c_str(), O_RDONLY)); + if (file_handle.get() < 0) { + return r; + } + + // Try to get an ELF handle. We need to read the ELF sections + // because we want to see if there is a .gnu_debuglink section + // that points to a split debug file + dwarf_elf_t elf_handle; + elf_handle.reset(elf_begin(file_handle.get(), ELF_C_READ, NULL)); + if (!elf_handle) { + return r; + } + + const char *e_ident = elf_getident(elf_handle.get(), 0); + if (!e_ident) { + return r; + } + + // Get the number of sections + // We use the new APIs as elf_getshnum is deprecated + size_t shdrnum = 0; + if (elf_getshdrnum(elf_handle.get(), &shdrnum) == -1) { + return r; + } + + // Get the index to the string section + size_t shdrstrndx = 0; + if (elf_getshdrstrndx(elf_handle.get(), &shdrstrndx) == -1) { + return r; + } + + std::string debuglink; + // Iterate through the ELF sections to try to get a gnu_debuglink + // note and also to cache the symbol table. + // We go the preprocessor way to avoid having to create templated + // classes or using gelf (which might throw a compiler error if 64 bit + // is not supported +#define ELF_GET_DATA(ARCH) \ + Elf_Scn *elf_section = 0; \ + Elf_Data *elf_data = 0; \ + Elf##ARCH##_Shdr *section_header = 0; \ + Elf_Scn *symbol_section = 0; \ + size_t symbol_count = 0; \ + size_t symbol_strings = 0; \ + Elf##ARCH##_Sym *symbol = 0; \ + const char *section_name = 0; \ + \ + while ((elf_section = elf_nextscn(elf_handle.get(), elf_section)) != NULL) { \ + section_header = elf##ARCH##_getshdr(elf_section); \ + if (section_header == NULL) { \ + return r; \ + } \ + \ + if ((section_name = elf_strptr(elf_handle.get(), shdrstrndx, \ + section_header->sh_name)) == NULL) { \ + return r; \ + } \ + \ + if (cstrings_eq(section_name, ".gnu_debuglink")) { \ + elf_data = elf_getdata(elf_section, NULL); \ + if (elf_data && elf_data->d_size > 0) { \ + debuglink = \ + std::string(reinterpret_cast(elf_data->d_buf)); \ + } \ + } \ + \ + switch (section_header->sh_type) { \ + case SHT_SYMTAB: \ + symbol_section = elf_section; \ + symbol_count = section_header->sh_size / section_header->sh_entsize; \ + symbol_strings = section_header->sh_link; \ + break; \ + \ + /* We use .dynsyms as a last resort, we prefer .symtab */ \ + case SHT_DYNSYM: \ + if (!symbol_section) { \ + symbol_section = elf_section; \ + symbol_count = section_header->sh_size / section_header->sh_entsize; \ + symbol_strings = section_header->sh_link; \ + } \ + break; \ + } \ + } \ + \ + if (symbol_section && symbol_count && symbol_strings) { \ + elf_data = elf_getdata(symbol_section, NULL); \ + symbol = reinterpret_cast(elf_data->d_buf); \ + for (size_t i = 0; i < symbol_count; ++i) { \ + int type = ELF##ARCH##_ST_TYPE(symbol->st_info); \ + if (type == STT_FUNC && symbol->st_value > 0) { \ + r.symbol_cache[symbol->st_value] = std::string( \ + elf_strptr(elf_handle.get(), symbol_strings, symbol->st_name)); \ + } \ + ++symbol; \ + } \ + } + + if (e_ident[EI_CLASS] == ELFCLASS32) { + ELF_GET_DATA(32) + } else if (e_ident[EI_CLASS] == ELFCLASS64) { + // libelf might have been built without 64 bit support +#if __LIBELF64 + ELF_GET_DATA(64) +#endif + } + + if (!debuglink.empty()) { + // We have a debuglink section! Open an elf instance on that + // file instead. If we can't open the file, then return + // the elf handle we had already opened. + dwarf_file_t debuglink_file; + debuglink_file.reset(open(debuglink.c_str(), O_RDONLY)); + if (debuglink_file.get() > 0) { + dwarf_elf_t debuglink_elf; + debuglink_elf.reset(elf_begin(debuglink_file.get(), ELF_C_READ, NULL)); + + // If we have a valid elf handle, return the new elf handle + // and file handle and discard the original ones + if (debuglink_elf) { + elf_handle = move(debuglink_elf); + file_handle = move(debuglink_file); + } + } + } + + // Ok, we have a valid ELF handle, let's try to get debug symbols + Dwarf_Debug dwarf_debug; + Dwarf_Error error = DW_DLE_NE; + dwarf_handle_t dwarf_handle; + + int dwarf_result = dwarf_elf_init(elf_handle.get(), DW_DLC_READ, NULL, NULL, + &dwarf_debug, &error); + + // We don't do any special handling for DW_DLV_NO_ENTRY specially. + // If we get an error, or the file doesn't have debug information + // we just return. + if (dwarf_result != DW_DLV_OK) { + return r; + } + + dwarf_handle.reset(dwarf_debug); + + r.file_handle = move(file_handle); + r.elf_handle = move(elf_handle); + r.dwarf_handle = move(dwarf_handle); + + return r; + } + + die_cache_entry &get_die_cache(dwarf_fileobject &fobj, Dwarf_Die die) { + Dwarf_Error error = DW_DLE_NE; + + // Get the die offset, we use it as the cache key + Dwarf_Off die_offset; + if (dwarf_dieoffset(die, &die_offset, &error) != DW_DLV_OK) { + die_offset = 0; + } + + die_cache_t::iterator it = fobj.die_cache.find(die_offset); + + if (it != fobj.die_cache.end()) { + fobj.current_cu = &it->second; + return it->second; + } + + die_cache_entry &de = fobj.die_cache[die_offset]; + fobj.current_cu = &de; + + Dwarf_Addr line_addr; + Dwarf_Small table_count; + + // The addresses in the line section are not fully sorted (they might + // be sorted by block of code belonging to the same file), which makes + // it necessary to do so before searching is possible. + // + // As libdwarf allocates a copy of everything, let's get the contents + // of the line section and keep it around. We also create a map of + // program counter to line table indices so we can search by address + // and get the line buffer index. + // + // To make things more difficult, the same address can span more than + // one line, so we need to keep the index pointing to the first line + // by using insert instead of the map's [ operator. + + // Get the line context for the DIE + if (dwarf_srclines_b(die, 0, &table_count, &de.line_context, &error) == + DW_DLV_OK) { + // Get the source lines for this line context, to be deallocated + // later + if (dwarf_srclines_from_linecontext(de.line_context, &de.line_buffer, + &de.line_count, + &error) == DW_DLV_OK) { + + // Add all the addresses to our map + for (int i = 0; i < de.line_count; i++) { + if (dwarf_lineaddr(de.line_buffer[i], &line_addr, &error) != + DW_DLV_OK) { + line_addr = 0; + } + de.line_section.insert(std::pair(line_addr, i)); + } + } + } + + // For each CU, cache the function DIEs that contain the + // DW_AT_specification attribute. When building with -g3 the function + // DIEs are separated in declaration and specification, with the + // declaration containing only the name and parameters and the + // specification the low/high pc and other compiler attributes. + // + // We cache those specifications so we don't skip over the declarations, + // because they have no pc, and we can do namespace resolution for + // DWARF function names. + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + Dwarf_Die current_die = 0; + if (dwarf_child(die, ¤t_die, &error) == DW_DLV_OK) { + for (;;) { + Dwarf_Die sibling_die = 0; + + Dwarf_Half tag_value; + dwarf_tag(current_die, &tag_value, &error); + + if (tag_value == DW_TAG_subprogram || + tag_value == DW_TAG_inlined_subroutine) { + + Dwarf_Bool has_attr = 0; + if (dwarf_hasattr(current_die, DW_AT_specification, &has_attr, + &error) == DW_DLV_OK) { + if (has_attr) { + Dwarf_Attribute attr_mem; + if (dwarf_attr(current_die, DW_AT_specification, &attr_mem, + &error) == DW_DLV_OK) { + Dwarf_Off spec_offset = 0; + if (dwarf_formref(attr_mem, &spec_offset, &error) == + DW_DLV_OK) { + Dwarf_Off spec_die_offset; + if (dwarf_dieoffset(current_die, &spec_die_offset, &error) == + DW_DLV_OK) { + de.spec_section[spec_offset] = spec_die_offset; + } + } + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + } + } + + int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); + if (result == DW_DLV_ERROR) { + break; + } else if (result == DW_DLV_NO_ENTRY) { + break; + } + + if (current_die != die) { + dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); + current_die = 0; + } + + current_die = sibling_die; + } + } + return de; + } + + static Dwarf_Die get_referenced_die(Dwarf_Debug dwarf, Dwarf_Die die, + Dwarf_Half attr, bool global) { + Dwarf_Error error = DW_DLE_NE; + Dwarf_Attribute attr_mem; + + Dwarf_Die found_die = NULL; + if (dwarf_attr(die, attr, &attr_mem, &error) == DW_DLV_OK) { + Dwarf_Off offset; + int result = 0; + if (global) { + result = dwarf_global_formref(attr_mem, &offset, &error); + } else { + result = dwarf_formref(attr_mem, &offset, &error); + } + + if (result == DW_DLV_OK) { + if (dwarf_offdie(dwarf, offset, &found_die, &error) != DW_DLV_OK) { + found_die = NULL; + } + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + return found_die; + } + + static std::string get_referenced_die_name(Dwarf_Debug dwarf, Dwarf_Die die, + Dwarf_Half attr, bool global) { + Dwarf_Error error = DW_DLE_NE; + std::string value; + + Dwarf_Die found_die = get_referenced_die(dwarf, die, attr, global); + + if (found_die) { + char *name; + if (dwarf_diename(found_die, &name, &error) == DW_DLV_OK) { + if (name) { + value = std::string(name); + } + dwarf_dealloc(dwarf, name, DW_DLA_STRING); + } + dwarf_dealloc(dwarf, found_die, DW_DLA_DIE); + } + + return value; + } + + // Returns a spec DIE linked to the passed one. The caller should + // deallocate the DIE + static Dwarf_Die get_spec_die(dwarf_fileobject &fobj, Dwarf_Die die) { + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + Dwarf_Error error = DW_DLE_NE; + Dwarf_Off die_offset; + if (fobj.current_cu && + dwarf_die_CU_offset(die, &die_offset, &error) == DW_DLV_OK) { + die_specmap_t::iterator it = + fobj.current_cu->spec_section.find(die_offset); + + // If we have a DIE that completes the current one, check if + // that one has the pc we are looking for + if (it != fobj.current_cu->spec_section.end()) { + Dwarf_Die spec_die = 0; + if (dwarf_offdie(dwarf, it->second, &spec_die, &error) == DW_DLV_OK) { + return spec_die; + } + } + } + + // Maybe we have an abstract origin DIE with the function information? + return get_referenced_die(fobj.dwarf_handle.get(), die, + DW_AT_abstract_origin, true); + } + + static bool die_has_pc(dwarf_fileobject &fobj, Dwarf_Die die, Dwarf_Addr pc) { + Dwarf_Addr low_pc = 0, high_pc = 0; + Dwarf_Half high_pc_form = 0; + Dwarf_Form_Class return_class; + Dwarf_Error error = DW_DLE_NE; + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + bool has_lowpc = false; + bool has_highpc = false; + bool has_ranges = false; + + if (dwarf_lowpc(die, &low_pc, &error) == DW_DLV_OK) { + // If we have a low_pc check if there is a high pc. + // If we don't have a high pc this might mean we have a base + // address for the ranges list or just an address. + has_lowpc = true; + + if (dwarf_highpc_b(die, &high_pc, &high_pc_form, &return_class, &error) == + DW_DLV_OK) { + // We do have a high pc. In DWARF 4+ this is an offset from the + // low pc, but in earlier versions it's an absolute address. + + has_highpc = true; + // In DWARF 2/3 this would be a DW_FORM_CLASS_ADDRESS + if (return_class == DW_FORM_CLASS_CONSTANT) { + high_pc = low_pc + high_pc; + } + + // We have low and high pc, check if our address + // is in that range + return pc >= low_pc && pc < high_pc; + } + } else { + // Reset the low_pc, in case dwarf_lowpc failing set it to some + // undefined value. + low_pc = 0; + } + + // Check if DW_AT_ranges is present and search for the PC in the + // returned ranges list. We always add the low_pc, as it not set it will + // be 0, in case we had a DW_AT_low_pc and DW_AT_ranges pair + bool result = false; + + Dwarf_Attribute attr; + if (dwarf_attr(die, DW_AT_ranges, &attr, &error) == DW_DLV_OK) { + + Dwarf_Off offset; + if (dwarf_global_formref(attr, &offset, &error) == DW_DLV_OK) { + Dwarf_Ranges *ranges; + Dwarf_Signed ranges_count = 0; + Dwarf_Unsigned byte_count = 0; + + if (dwarf_get_ranges_a(dwarf, offset, die, &ranges, &ranges_count, + &byte_count, &error) == DW_DLV_OK) { + has_ranges = ranges_count != 0; + for (int i = 0; i < ranges_count; i++) { + if (ranges[i].dwr_addr1 != 0 && + pc >= ranges[i].dwr_addr1 + low_pc && + pc < ranges[i].dwr_addr2 + low_pc) { + result = true; + break; + } + } + dwarf_ranges_dealloc(dwarf, ranges, ranges_count); + } + } + } + + // Last attempt. We might have a single address set as low_pc. + if (!result && low_pc != 0 && pc == low_pc) { + result = true; + } + + // If we don't have lowpc, highpc and ranges maybe this DIE is a + // declaration that relies on a DW_AT_specification DIE that happens + // later. Use the specification cache we filled when we loaded this CU. + if (!result && (!has_lowpc && !has_highpc && !has_ranges)) { + Dwarf_Die spec_die = get_spec_die(fobj, die); + if (spec_die) { + result = die_has_pc(fobj, spec_die, pc); + dwarf_dealloc(dwarf, spec_die, DW_DLA_DIE); + } + } + + return result; + } + + static void get_type(Dwarf_Debug dwarf, Dwarf_Die die, std::string &type) { + Dwarf_Error error = DW_DLE_NE; + + Dwarf_Die child = 0; + if (dwarf_child(die, &child, &error) == DW_DLV_OK) { + get_type(dwarf, child, type); + } + + if (child) { + type.insert(0, "::"); + dwarf_dealloc(dwarf, child, DW_DLA_DIE); + } + + char *name; + if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { + type.insert(0, std::string(name)); + dwarf_dealloc(dwarf, name, DW_DLA_STRING); + } else { + type.insert(0, ""); + } + } + + static std::string get_type_by_signature(Dwarf_Debug dwarf, Dwarf_Die die) { + Dwarf_Error error = DW_DLE_NE; + + Dwarf_Sig8 signature; + Dwarf_Bool has_attr = 0; + if (dwarf_hasattr(die, DW_AT_signature, &has_attr, &error) == DW_DLV_OK) { + if (has_attr) { + Dwarf_Attribute attr_mem; + if (dwarf_attr(die, DW_AT_signature, &attr_mem, &error) == DW_DLV_OK) { + if (dwarf_formsig8(attr_mem, &signature, &error) != DW_DLV_OK) { + return std::string(""); + } + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + } + + Dwarf_Unsigned next_cu_header; + Dwarf_Sig8 tu_signature; + std::string result; + bool found = false; + + while (dwarf_next_cu_header_d(dwarf, 0, 0, 0, 0, 0, 0, 0, &tu_signature, 0, + &next_cu_header, 0, &error) == DW_DLV_OK) { + + if (strncmp(signature.signature, tu_signature.signature, 8) == 0) { + Dwarf_Die type_cu_die = 0; + if (dwarf_siblingof_b(dwarf, 0, 0, &type_cu_die, &error) == DW_DLV_OK) { + Dwarf_Die child_die = 0; + if (dwarf_child(type_cu_die, &child_die, &error) == DW_DLV_OK) { + get_type(dwarf, child_die, result); + found = !result.empty(); + dwarf_dealloc(dwarf, child_die, DW_DLA_DIE); + } + dwarf_dealloc(dwarf, type_cu_die, DW_DLA_DIE); + } + } + } + + if (found) { + while (dwarf_next_cu_header_d(dwarf, 0, 0, 0, 0, 0, 0, 0, 0, 0, + &next_cu_header, 0, &error) == DW_DLV_OK) { + // Reset the cu header state. Unfortunately, libdwarf's + // next_cu_header API keeps its own iterator per Dwarf_Debug + // that can't be reset. We need to keep fetching elements until + // the end. + } + } else { + // If we couldn't resolve the type just print out the signature + std::ostringstream string_stream; + string_stream << "<0x" << std::hex << std::setfill('0'); + for (int i = 0; i < 8; ++i) { + string_stream << std::setw(2) << std::hex + << (int)(unsigned char)(signature.signature[i]); + } + string_stream << ">"; + result = string_stream.str(); + } + return result; + } + + struct type_context_t { + bool is_const; + bool is_typedef; + bool has_type; + bool has_name; + std::string text; + + type_context_t() + : is_const(false), is_typedef(false), has_type(false), has_name(false) { + } + }; + + // Types are resolved from right to left: we get the variable name first + // and then all specifiers (like const or pointer) in a chain of DW_AT_type + // DIEs. Call this function recursively until we get a complete type + // string. + static void set_parameter_string(dwarf_fileobject &fobj, Dwarf_Die die, + type_context_t &context) { + char *name; + Dwarf_Error error = DW_DLE_NE; + + // typedefs contain also the base type, so we skip it and only + // print the typedef name + if (!context.is_typedef) { + if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { + if (!context.text.empty()) { + context.text.insert(0, " "); + } + context.text.insert(0, std::string(name)); + dwarf_dealloc(fobj.dwarf_handle.get(), name, DW_DLA_STRING); + } + } else { + context.is_typedef = false; + context.has_type = true; + if (context.is_const) { + context.text.insert(0, "const "); + context.is_const = false; + } + } + + bool next_type_is_const = false; + bool is_keyword = true; + + Dwarf_Half tag = 0; + Dwarf_Bool has_attr = 0; + if (dwarf_tag(die, &tag, &error) == DW_DLV_OK) { + switch (tag) { + case DW_TAG_structure_type: + case DW_TAG_union_type: + case DW_TAG_class_type: + case DW_TAG_enumeration_type: + context.has_type = true; + if (dwarf_hasattr(die, DW_AT_signature, &has_attr, &error) == + DW_DLV_OK) { + // If we have a signature it means the type is defined + // in .debug_types, so we need to load the DIE pointed + // at by the signature and resolve it + if (has_attr) { + std::string type = + get_type_by_signature(fobj.dwarf_handle.get(), die); + if (context.is_const) + type.insert(0, "const "); + + if (!context.text.empty()) + context.text.insert(0, " "); + context.text.insert(0, type); + } + + // Treat enums like typedefs, and skip printing its + // base type + context.is_typedef = (tag == DW_TAG_enumeration_type); + } + break; + case DW_TAG_const_type: + next_type_is_const = true; + break; + case DW_TAG_pointer_type: + context.text.insert(0, "*"); + break; + case DW_TAG_reference_type: + context.text.insert(0, "&"); + break; + case DW_TAG_restrict_type: + context.text.insert(0, "restrict "); + break; + case DW_TAG_rvalue_reference_type: + context.text.insert(0, "&&"); + break; + case DW_TAG_volatile_type: + context.text.insert(0, "volatile "); + break; + case DW_TAG_typedef: + // Propagate the const-ness to the next type + // as typedefs are linked to its base type + next_type_is_const = context.is_const; + context.is_typedef = true; + context.has_type = true; + break; + case DW_TAG_base_type: + context.has_type = true; + break; + case DW_TAG_formal_parameter: + context.has_name = true; + break; + default: + is_keyword = false; + break; + } + } + + if (!is_keyword && context.is_const) { + context.text.insert(0, "const "); + } + + context.is_const = next_type_is_const; + + Dwarf_Die ref = + get_referenced_die(fobj.dwarf_handle.get(), die, DW_AT_type, true); + if (ref) { + set_parameter_string(fobj, ref, context); + dwarf_dealloc(fobj.dwarf_handle.get(), ref, DW_DLA_DIE); + } + + if (!context.has_type && context.has_name) { + context.text.insert(0, "void "); + context.has_type = true; + } + } + + // Resolve the function return type and parameters + static void set_function_parameters(std::string &function_name, + std::vector &ns, + dwarf_fileobject &fobj, Dwarf_Die die) { + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + Dwarf_Error error = DW_DLE_NE; + Dwarf_Die current_die = 0; + std::string parameters; + bool has_spec = true; + // Check if we have a spec DIE. If we do we use it as it contains + // more information, like parameter names. + Dwarf_Die spec_die = get_spec_die(fobj, die); + if (!spec_die) { + has_spec = false; + spec_die = die; + } + + std::vector::const_iterator it = ns.begin(); + std::string ns_name; + for (it = ns.begin(); it < ns.end(); ++it) { + ns_name.append(*it).append("::"); + } + + if (!ns_name.empty()) { + function_name.insert(0, ns_name); + } + + // See if we have a function return type. It can be either on the + // current die or in its spec one (usually true for inlined functions) + std::string return_type = + get_referenced_die_name(dwarf, die, DW_AT_type, true); + if (return_type.empty()) { + return_type = get_referenced_die_name(dwarf, spec_die, DW_AT_type, true); + } + if (!return_type.empty()) { + return_type.append(" "); + function_name.insert(0, return_type); + } + + if (dwarf_child(spec_die, ¤t_die, &error) == DW_DLV_OK) { + for (;;) { + Dwarf_Die sibling_die = 0; + + Dwarf_Half tag_value; + dwarf_tag(current_die, &tag_value, &error); + + if (tag_value == DW_TAG_formal_parameter) { + // Ignore artificial (ie, compiler generated) parameters + bool is_artificial = false; + Dwarf_Attribute attr_mem; + if (dwarf_attr(current_die, DW_AT_artificial, &attr_mem, &error) == + DW_DLV_OK) { + Dwarf_Bool flag = 0; + if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) { + is_artificial = flag != 0; + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + + if (!is_artificial) { + type_context_t context; + set_parameter_string(fobj, current_die, context); + + if (parameters.empty()) { + parameters.append("("); + } else { + parameters.append(", "); + } + parameters.append(context.text); + } + } + + int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); + if (result == DW_DLV_ERROR) { + break; + } else if (result == DW_DLV_NO_ENTRY) { + break; + } + + if (current_die != die) { + dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); + current_die = 0; + } + + current_die = sibling_die; + } + } + if (parameters.empty()) + parameters = "("; + parameters.append(")"); + + // If we got a spec DIE we need to deallocate it + if (has_spec) + dwarf_dealloc(dwarf, spec_die, DW_DLA_DIE); + + function_name.append(parameters); + } + + // defined here because in C++98, template function cannot take locally + // defined types... grrr. + struct inliners_search_cb { + void operator()(Dwarf_Die die, std::vector &ns) { + Dwarf_Error error = DW_DLE_NE; + Dwarf_Half tag_value; + Dwarf_Attribute attr_mem; + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + + dwarf_tag(die, &tag_value, &error); + + switch (tag_value) { + char *name; + case DW_TAG_subprogram: + if (!trace.source.function.empty()) + break; + if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { + trace.source.function = std::string(name); + dwarf_dealloc(dwarf, name, DW_DLA_STRING); + } else { + // We don't have a function name in this DIE. + // Check if there is a referenced non-defining + // declaration. + trace.source.function = + get_referenced_die_name(dwarf, die, DW_AT_abstract_origin, true); + if (trace.source.function.empty()) { + trace.source.function = + get_referenced_die_name(dwarf, die, DW_AT_specification, true); + } + } + + // Append the function parameters, if available + set_function_parameters(trace.source.function, ns, fobj, die); + + // If the object function name is empty, it's possible that + // there is no dynamic symbol table (maybe the executable + // was stripped or not built with -rdynamic). See if we have + // a DWARF linkage name to use instead. We try both + // linkage_name and MIPS_linkage_name because the MIPS tag + // was the unofficial one until it was adopted in DWARF4. + // Old gcc versions generate MIPS_linkage_name + if (trace.object_function.empty()) { + details::demangler demangler; + + if (dwarf_attr(die, DW_AT_linkage_name, &attr_mem, &error) != + DW_DLV_OK) { + if (dwarf_attr(die, DW_AT_MIPS_linkage_name, &attr_mem, &error) != + DW_DLV_OK) { + break; + } + } + + char *linkage; + if (dwarf_formstring(attr_mem, &linkage, &error) == DW_DLV_OK) { + trace.object_function = demangler.demangle(linkage); + dwarf_dealloc(dwarf, linkage, DW_DLA_STRING); + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + break; + + case DW_TAG_inlined_subroutine: + ResolvedTrace::SourceLoc sloc; + + if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { + sloc.function = std::string(name); + dwarf_dealloc(dwarf, name, DW_DLA_STRING); + } else { + // We don't have a name for this inlined DIE, it could + // be that there is an abstract origin instead. + // Get the DW_AT_abstract_origin value, which is a + // reference to the source DIE and try to get its name + sloc.function = + get_referenced_die_name(dwarf, die, DW_AT_abstract_origin, true); + } + + set_function_parameters(sloc.function, ns, fobj, die); + + std::string file = die_call_file(dwarf, die, cu_die); + if (!file.empty()) + sloc.filename = file; + + Dwarf_Unsigned number = 0; + if (dwarf_attr(die, DW_AT_call_line, &attr_mem, &error) == DW_DLV_OK) { + if (dwarf_formudata(attr_mem, &number, &error) == DW_DLV_OK) { + sloc.line = number; + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + + if (dwarf_attr(die, DW_AT_call_column, &attr_mem, &error) == + DW_DLV_OK) { + if (dwarf_formudata(attr_mem, &number, &error) == DW_DLV_OK) { + sloc.col = number; + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + + trace.inliners.push_back(sloc); + break; + }; + } + ResolvedTrace &trace; + dwarf_fileobject &fobj; + Dwarf_Die cu_die; + inliners_search_cb(ResolvedTrace &t, dwarf_fileobject &f, Dwarf_Die c) + : trace(t), fobj(f), cu_die(c) {} + }; + + static Dwarf_Die find_fundie_by_pc(dwarf_fileobject &fobj, + Dwarf_Die parent_die, Dwarf_Addr pc, + Dwarf_Die result) { + Dwarf_Die current_die = 0; + Dwarf_Error error = DW_DLE_NE; + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + + if (dwarf_child(parent_die, ¤t_die, &error) != DW_DLV_OK) { + return NULL; + } + + for (;;) { + Dwarf_Die sibling_die = 0; + Dwarf_Half tag_value; + dwarf_tag(current_die, &tag_value, &error); + + switch (tag_value) { + case DW_TAG_subprogram: + case DW_TAG_inlined_subroutine: + if (die_has_pc(fobj, current_die, pc)) { + return current_die; + } + }; + bool declaration = false; + Dwarf_Attribute attr_mem; + if (dwarf_attr(current_die, DW_AT_declaration, &attr_mem, &error) == + DW_DLV_OK) { + Dwarf_Bool flag = 0; + if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) { + declaration = flag != 0; + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + + if (!declaration) { + // let's be curious and look deeper in the tree, functions are + // not necessarily at the first level, but might be nested + // inside a namespace, structure, a function, an inlined + // function etc. + Dwarf_Die die_mem = 0; + Dwarf_Die indie = find_fundie_by_pc(fobj, current_die, pc, die_mem); + if (indie) { + result = die_mem; + return result; + } + } + + int res = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); + if (res == DW_DLV_ERROR) { + return NULL; + } else if (res == DW_DLV_NO_ENTRY) { + break; + } + + if (current_die != parent_die) { + dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); + current_die = 0; + } + + current_die = sibling_die; + } + return NULL; + } + + template + static bool deep_first_search_by_pc(dwarf_fileobject &fobj, + Dwarf_Die parent_die, Dwarf_Addr pc, + std::vector &ns, CB cb) { + Dwarf_Die current_die = 0; + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + Dwarf_Error error = DW_DLE_NE; + + if (dwarf_child(parent_die, ¤t_die, &error) != DW_DLV_OK) { + return false; + } + + bool branch_has_pc = false; + bool has_namespace = false; + for (;;) { + Dwarf_Die sibling_die = 0; + + Dwarf_Half tag; + if (dwarf_tag(current_die, &tag, &error) == DW_DLV_OK) { + if (tag == DW_TAG_namespace || tag == DW_TAG_class_type) { + char *ns_name = NULL; + if (dwarf_diename(current_die, &ns_name, &error) == DW_DLV_OK) { + if (ns_name) { + ns.push_back(std::string(ns_name)); + } else { + ns.push_back(""); + } + dwarf_dealloc(dwarf, ns_name, DW_DLA_STRING); + } else { + ns.push_back(""); + } + has_namespace = true; + } + } + + bool declaration = false; + Dwarf_Attribute attr_mem; + if (tag != DW_TAG_class_type && + dwarf_attr(current_die, DW_AT_declaration, &attr_mem, &error) == + DW_DLV_OK) { + Dwarf_Bool flag = 0; + if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) { + declaration = flag != 0; + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + + if (!declaration) { + // let's be curious and look deeper in the tree, function are + // not necessarily at the first level, but might be nested + // inside a namespace, structure, a function, an inlined + // function etc. + branch_has_pc = deep_first_search_by_pc(fobj, current_die, pc, ns, cb); + } + + if (!branch_has_pc) { + branch_has_pc = die_has_pc(fobj, current_die, pc); + } + + if (branch_has_pc) { + cb(current_die, ns); + } + + int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); + if (result == DW_DLV_ERROR) { + return false; + } else if (result == DW_DLV_NO_ENTRY) { + break; + } + + if (current_die != parent_die) { + dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); + current_die = 0; + } + + if (has_namespace) { + has_namespace = false; + ns.pop_back(); + } + current_die = sibling_die; + } + + if (has_namespace) { + ns.pop_back(); + } + return branch_has_pc; + } + + static std::string die_call_file(Dwarf_Debug dwarf, Dwarf_Die die, + Dwarf_Die cu_die) { + Dwarf_Attribute attr_mem; + Dwarf_Error error = DW_DLE_NE; + Dwarf_Unsigned file_index; + + std::string file; + + if (dwarf_attr(die, DW_AT_call_file, &attr_mem, &error) == DW_DLV_OK) { + if (dwarf_formudata(attr_mem, &file_index, &error) != DW_DLV_OK) { + file_index = 0; + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + + if (file_index == 0) { + return file; + } + + char **srcfiles = 0; + Dwarf_Signed file_count = 0; + if (dwarf_srcfiles(cu_die, &srcfiles, &file_count, &error) == DW_DLV_OK) { + if (file_count > 0 && + file_index <= static_cast(file_count)) { + file = std::string(srcfiles[file_index - 1]); + } + + // Deallocate all strings! + for (int i = 0; i < file_count; ++i) { + dwarf_dealloc(dwarf, srcfiles[i], DW_DLA_STRING); + } + dwarf_dealloc(dwarf, srcfiles, DW_DLA_LIST); + } + } + return file; + } + + Dwarf_Die find_die(dwarf_fileobject &fobj, Dwarf_Addr addr) { + // Let's get to work! First see if we have a debug_aranges section so + // we can speed up the search + + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + Dwarf_Error error = DW_DLE_NE; + Dwarf_Arange *aranges; + Dwarf_Signed arange_count; + + Dwarf_Die returnDie; + bool found = false; + if (dwarf_get_aranges(dwarf, &aranges, &arange_count, &error) != + DW_DLV_OK) { + aranges = NULL; + } + + if (aranges) { + // We have aranges. Get the one where our address is. + Dwarf_Arange arange; + if (dwarf_get_arange(aranges, arange_count, addr, &arange, &error) == + DW_DLV_OK) { + + // We found our address. Get the compilation-unit DIE offset + // represented by the given address range. + Dwarf_Off cu_die_offset; + if (dwarf_get_cu_die_offset(arange, &cu_die_offset, &error) == + DW_DLV_OK) { + // Get the DIE at the offset returned by the aranges search. + // We set is_info to 1 to specify that the offset is from + // the .debug_info section (and not .debug_types) + int dwarf_result = + dwarf_offdie_b(dwarf, cu_die_offset, 1, &returnDie, &error); + + found = dwarf_result == DW_DLV_OK; + } + dwarf_dealloc(dwarf, arange, DW_DLA_ARANGE); + } + } + + if (found) + return returnDie; // The caller is responsible for freeing the die + + // The search for aranges failed. Try to find our address by scanning + // all compilation units. + Dwarf_Unsigned next_cu_header; + Dwarf_Half tag = 0; + returnDie = 0; + + while (!found && + dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, + &next_cu_header, 0, &error) == DW_DLV_OK) { + + if (returnDie) + dwarf_dealloc(dwarf, returnDie, DW_DLA_DIE); + + if (dwarf_siblingof(dwarf, 0, &returnDie, &error) == DW_DLV_OK) { + if ((dwarf_tag(returnDie, &tag, &error) == DW_DLV_OK) && + tag == DW_TAG_compile_unit) { + if (die_has_pc(fobj, returnDie, addr)) { + found = true; + } + } + } + } + + if (found) { + while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, + &next_cu_header, 0, &error) == DW_DLV_OK) { + // Reset the cu header state. Libdwarf's next_cu_header API + // keeps its own iterator per Dwarf_Debug that can't be reset. + // We need to keep fetching elements until the end. + } + } + + if (found) + return returnDie; + + // We couldn't find any compilation units with ranges or a high/low pc. + // Try again by looking at all DIEs in all compilation units. + Dwarf_Die cudie; + while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, + &next_cu_header, 0, &error) == DW_DLV_OK) { + if (dwarf_siblingof(dwarf, 0, &cudie, &error) == DW_DLV_OK) { + Dwarf_Die die_mem = 0; + Dwarf_Die resultDie = find_fundie_by_pc(fobj, cudie, addr, die_mem); + + if (resultDie) { + found = true; + break; + } + } + } + + if (found) { + while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, + &next_cu_header, 0, &error) == DW_DLV_OK) { + // Reset the cu header state. Libdwarf's next_cu_header API + // keeps its own iterator per Dwarf_Debug that can't be reset. + // We need to keep fetching elements until the end. + } + } + + if (found) + return cudie; + + // We failed. + return NULL; + } +}; +#endif // BACKWARD_HAS_DWARF == 1 + +template <> +class TraceResolverImpl + : public TraceResolverLinuxImpl {}; + +#endif // BACKWARD_SYSTEM_LINUX + +#ifdef BACKWARD_SYSTEM_DARWIN + +template class TraceResolverDarwinImpl; + +template <> +class TraceResolverDarwinImpl + : public TraceResolverImplBase { +public: + void load_addresses(void *const *addresses, int address_count) override { + if (address_count == 0) { + return; + } + _symbols.reset(backtrace_symbols(addresses, address_count)); + } + + ResolvedTrace resolve(ResolvedTrace trace) override { + // parse: + // + + char *filename = _symbols[trace.idx]; + + // skip " " + while (*filename && *filename != ' ') + filename++; + while (*filename == ' ') + filename++; + + // find start of from end ( may contain a space) + char *p = filename + strlen(filename) - 1; + // skip to start of " + " + while (p > filename && *p != ' ') + p--; + while (p > filename && *p == ' ') + p--; + while (p > filename && *p != ' ') + p--; + while (p > filename && *p == ' ') + p--; + char *funcname_end = p + 1; + + // skip to start of "" + while (p > filename && *p != ' ') + p--; + char *funcname = p + 1; + + // skip to start of " " + while (p > filename && *p == ' ') + p--; + while (p > filename && *p != ' ') + p--; + while (p > filename && *p == ' ') + p--; + + // skip "", handling the case where it contains a + char *filename_end = p + 1; + if (p == filename) { + // something went wrong, give up + filename_end = filename + strlen(filename); + funcname = filename_end; + } + trace.object_filename.assign( + filename, filename_end); // ok even if filename_end is the ending \0 + // (then we assign entire string) + + if (*funcname) { // if it's not end of string + *funcname_end = '\0'; + + trace.object_function = this->demangle(funcname); + trace.object_function += " "; + trace.object_function += (funcname_end + 1); + trace.source.function = trace.object_function; // we cannot do better. + } + return trace; + } + +private: + details::handle _symbols; +}; + +template <> +class TraceResolverImpl + : public TraceResolverDarwinImpl {}; + +#endif // BACKWARD_SYSTEM_DARWIN + +#ifdef BACKWARD_SYSTEM_WINDOWS + +// Load all symbol info +// Based on: +// https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227 + +struct module_data { + std::string image_name; + std::string module_name; + void *base_address; + DWORD load_size; +}; + +class get_mod_info { + HANDLE process; + static const int buffer_length = 4096; + +public: + get_mod_info(HANDLE h) : process(h) {} + + module_data operator()(HMODULE module) { + module_data ret; + char temp[buffer_length]; + MODULEINFO mi; + + GetModuleInformation(process, module, &mi, sizeof(mi)); + ret.base_address = mi.lpBaseOfDll; + ret.load_size = mi.SizeOfImage; + + GetModuleFileNameExA(process, module, temp, sizeof(temp)); + ret.image_name = temp; + GetModuleBaseNameA(process, module, temp, sizeof(temp)); + ret.module_name = temp; + std::vector img(ret.image_name.begin(), ret.image_name.end()); + std::vector mod(ret.module_name.begin(), ret.module_name.end()); + SymLoadModule64(process, 0, &img[0], &mod[0], (DWORD64)ret.base_address, + ret.load_size); + return ret; + } +}; + +template <> +class TraceResolverImpl + : public TraceResolverImplBase { +public: + TraceResolverImpl() { + + HANDLE process = GetCurrentProcess(); + + std::vector modules; + DWORD cbNeeded; + std::vector module_handles(1); + SymInitialize(process, NULL, false); + DWORD symOptions = SymGetOptions(); + symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; + SymSetOptions(symOptions); + EnumProcessModules( + process, &module_handles[0], + static_cast(module_handles.size() * sizeof(HMODULE)), &cbNeeded); + module_handles.resize(cbNeeded / sizeof(HMODULE)); + EnumProcessModules( + process, &module_handles[0], + static_cast(module_handles.size() * sizeof(HMODULE)), &cbNeeded); + std::transform(module_handles.begin(), module_handles.end(), + std::back_inserter(modules), get_mod_info(process)); + void *base = modules[0].base_address; + IMAGE_NT_HEADERS *h = ImageNtHeader(base); + image_type = h->FileHeader.Machine; + } + + static const int max_sym_len = 255; + struct symbol_t { + SYMBOL_INFO sym; + char buffer[max_sym_len]; + } sym; + + DWORD64 displacement; + + ResolvedTrace resolve(ResolvedTrace t) override { + HANDLE process = GetCurrentProcess(); + + char name[256]; + + memset(&sym, 0, sizeof(sym)); + sym.sym.SizeOfStruct = sizeof(SYMBOL_INFO); + sym.sym.MaxNameLen = max_sym_len; + + if (!SymFromAddr(process, (ULONG64)t.addr, &displacement, &sym.sym)) { + // TODO: error handling everywhere + char *lpMsgBuf; + DWORD dw = GetLastError(); + + if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (char *)&lpMsgBuf, 0, NULL)) { + std::fprintf(stderr, "%s\n", lpMsgBuf); + LocalFree(lpMsgBuf); + } + + // abort(); + } + UnDecorateSymbolName(sym.sym.Name, (PSTR)name, 256, UNDNAME_COMPLETE); + + DWORD offset = 0; + IMAGEHLP_LINE line; + if (SymGetLineFromAddr(process, (ULONG64)t.addr, &offset, &line)) { + t.object_filename = line.FileName; + t.source.filename = line.FileName; + t.source.line = line.LineNumber; + t.source.col = offset; + } + + t.source.function = name; + t.object_filename = ""; + t.object_function = name; + + return t; + } + + DWORD machine_type() const { return image_type; } + +private: + DWORD image_type; +}; + +#endif + +class TraceResolver : public TraceResolverImpl {}; + +/*************** CODE SNIPPET ***************/ + +class SourceFile { +public: + typedef std::vector> lines_t; + + SourceFile() {} + SourceFile(const std::string &path) { + // 1. If BACKWARD_CXX_SOURCE_PREFIXES is set then assume it contains + // a colon-separated list of path prefixes. Try prepending each + // to the given path until a valid file is found. + const std::vector &prefixes = get_paths_from_env_variable(); + for (size_t i = 0; i < prefixes.size(); ++i) { + // Double slashes (//) should not be a problem. + std::string new_path = prefixes[i] + '/' + path; + _file.reset(new std::ifstream(new_path.c_str())); + if (is_open()) + break; + } + // 2. If no valid file found then fallback to opening the path as-is. + if (!_file || !is_open()) { + _file.reset(new std::ifstream(path.c_str())); + } + } + bool is_open() const { return _file->is_open(); } + + lines_t &get_lines(unsigned line_start, unsigned line_count, lines_t &lines) { + using namespace std; + // This function make uses of the dumbest algo ever: + // 1) seek(0) + // 2) read lines one by one and discard until line_start + // 3) read line one by one until line_start + line_count + // + // If you are getting snippets many time from the same file, it is + // somewhat a waste of CPU, feel free to benchmark and propose a + // better solution ;) + + _file->clear(); + _file->seekg(0); + string line; + unsigned line_idx; + + for (line_idx = 1; line_idx < line_start; ++line_idx) { + std::getline(*_file, line); + if (!*_file) { + return lines; + } + } + + // think of it like a lambda in C++98 ;) + // but look, I will reuse it two times! + // What a good boy am I. + struct isspace { + bool operator()(char c) { return std::isspace(c); } + }; + + bool started = false; + for (; line_idx < line_start + line_count; ++line_idx) { + getline(*_file, line); + if (!*_file) { + return lines; + } + if (!started) { + if (std::find_if(line.begin(), line.end(), not_isspace()) == line.end()) + continue; + started = true; + } + lines.push_back(make_pair(line_idx, line)); + } + + lines.erase( + std::find_if(lines.rbegin(), lines.rend(), not_isempty()).base(), + lines.end()); + return lines; + } + + lines_t get_lines(unsigned line_start, unsigned line_count) { + lines_t lines; + return get_lines(line_start, line_count, lines); + } + + // there is no find_if_not in C++98, lets do something crappy to + // workaround. + struct not_isspace { + bool operator()(char c) { return !std::isspace(c); } + }; + // and define this one here because C++98 is not happy with local defined + // struct passed to template functions, fuuuu. + struct not_isempty { + bool operator()(const lines_t::value_type &p) { + return !(std::find_if(p.second.begin(), p.second.end(), not_isspace()) == + p.second.end()); + } + }; + + void swap(SourceFile &b) { _file.swap(b._file); } + +#ifdef BACKWARD_ATLEAST_CXX11 + SourceFile(SourceFile &&from) : _file(nullptr) { swap(from); } + SourceFile &operator=(SourceFile &&from) { + swap(from); + return *this; + } +#else + explicit SourceFile(const SourceFile &from) { + // some sort of poor man's move semantic. + swap(const_cast(from)); + } + SourceFile &operator=(const SourceFile &from) { + // some sort of poor man's move semantic. + swap(const_cast(from)); + return *this; + } +#endif + + // Allow adding to paths gotten from BACKWARD_CXX_SOURCE_PREFIXES after + // loading the library; this can be useful when the library is loaded when the + // locations are unknown Warning: Because this edits the static paths + // variable, it is *not* intrinsiclly thread safe + static void add_paths_to_env_variable_impl(const std::string &to_add) { + get_mutable_paths_from_env_variable().push_back(to_add); + } + +private: + details::handle> + _file; + + static std::vector get_paths_from_env_variable_impl() { + std::vector paths; + const char *prefixes_str = std::getenv("BACKWARD_CXX_SOURCE_PREFIXES"); + if (prefixes_str && prefixes_str[0]) { + paths = details::split_source_prefixes(prefixes_str); + } + return paths; + } + + static std::vector &get_mutable_paths_from_env_variable() { + static volatile std::vector paths = + get_paths_from_env_variable_impl(); + return const_cast &>(paths); + } + + static const std::vector &get_paths_from_env_variable() { + return get_mutable_paths_from_env_variable(); + } + +#ifdef BACKWARD_ATLEAST_CXX11 + SourceFile(const SourceFile &) = delete; + SourceFile &operator=(const SourceFile &) = delete; +#endif +}; + +class SnippetFactory { +public: + typedef SourceFile::lines_t lines_t; + + lines_t get_snippet(const std::string &filename, unsigned line_start, + unsigned context_size) { + + SourceFile &src_file = get_src_file(filename); + unsigned start = line_start - context_size / 2; + return src_file.get_lines(start, context_size); + } + + lines_t get_combined_snippet(const std::string &filename_a, unsigned line_a, + const std::string &filename_b, unsigned line_b, + unsigned context_size) { + SourceFile &src_file_a = get_src_file(filename_a); + SourceFile &src_file_b = get_src_file(filename_b); + + lines_t lines = + src_file_a.get_lines(line_a - context_size / 4, context_size / 2); + src_file_b.get_lines(line_b - context_size / 4, context_size / 2, lines); + return lines; + } + + lines_t get_coalesced_snippet(const std::string &filename, unsigned line_a, + unsigned line_b, unsigned context_size) { + SourceFile &src_file = get_src_file(filename); + + using std::max; + using std::min; + unsigned a = min(line_a, line_b); + unsigned b = max(line_a, line_b); + + if ((b - a) < (context_size / 3)) { + return src_file.get_lines((a + b - context_size + 1) / 2, context_size); + } + + lines_t lines = src_file.get_lines(a - context_size / 4, context_size / 2); + src_file.get_lines(b - context_size / 4, context_size / 2, lines); + return lines; + } + +private: + typedef details::hashtable::type src_files_t; + src_files_t _src_files; + + SourceFile &get_src_file(const std::string &filename) { + src_files_t::iterator it = _src_files.find(filename); + if (it != _src_files.end()) { + return it->second; + } + SourceFile &new_src_file = _src_files[filename]; + new_src_file = SourceFile(filename); + return new_src_file; + } +}; + +/*************** PRINTER ***************/ + +namespace ColorMode { +enum type { automatic, never, always }; +} + +class cfile_streambuf : public std::streambuf { +public: + cfile_streambuf(FILE *_sink) : sink(_sink) {} + int_type underflow() override { return traits_type::eof(); } + int_type overflow(int_type ch) override { + if (traits_type::not_eof(ch) && fputc(ch, sink) != EOF) { + return ch; + } + return traits_type::eof(); + } + + std::streamsize xsputn(const char_type *s, std::streamsize count) override { + return static_cast( + fwrite(s, sizeof *s, static_cast(count), sink)); + } + +#ifdef BACKWARD_ATLEAST_CXX11 +public: + cfile_streambuf(const cfile_streambuf &) = delete; + cfile_streambuf &operator=(const cfile_streambuf &) = delete; +#else +private: + cfile_streambuf(const cfile_streambuf &); + cfile_streambuf &operator=(const cfile_streambuf &); +#endif + +private: + FILE *sink; + std::vector buffer; +}; + +#ifdef BACKWARD_SYSTEM_LINUX + +namespace Color { +enum type { yellow = 33, purple = 35, reset = 39 }; +} // namespace Color + +class Colorize { +public: + Colorize(std::ostream &os) : _os(os), _reset(false), _enabled(false) {} + + void activate(ColorMode::type mode) { _enabled = mode == ColorMode::always; } + + void activate(ColorMode::type mode, FILE *fp) { activate(mode, fileno(fp)); } + + void set_color(Color::type ccode) { + if (!_enabled) + return; + + // I assume that the terminal can handle basic colors. Seriously I + // don't want to deal with all the termcap shit. + _os << "\033[" << static_cast(ccode) << "m"; + _reset = (ccode != Color::reset); + } + + ~Colorize() { + if (_reset) { + set_color(Color::reset); + } + } + +private: + void activate(ColorMode::type mode, int fd) { + activate(mode == ColorMode::automatic && isatty(fd) ? ColorMode::always + : mode); + } + + std::ostream &_os; + bool _reset; + bool _enabled; +}; + +#else // ndef BACKWARD_SYSTEM_LINUX + +namespace Color { +enum type { yellow = 0, purple = 0, reset = 0 }; +} // namespace Color + +class Colorize { +public: + Colorize(std::ostream &) {} + void activate(ColorMode::type) {} + void activate(ColorMode::type, FILE *) {} + void set_color(Color::type) {} +}; + +#endif // BACKWARD_SYSTEM_LINUX + +class Printer { +public: + bool snippet; + ColorMode::type color_mode; + bool address; + bool object; + int inliner_context_size; + int trace_context_size; + bool reverse; + + Printer() + : snippet(true), color_mode(ColorMode::automatic), address(false), + object(false), inliner_context_size(5), trace_context_size(7), + reverse(true) {} + + template FILE *print(ST &st, FILE *fp = stderr) { + cfile_streambuf obuf(fp); + std::ostream os(&obuf); + Colorize colorize(os); + colorize.activate(color_mode, fp); + print_stacktrace(st, os, colorize); + return fp; + } + + template std::ostream &print(ST &st, std::ostream &os) { + Colorize colorize(os); + colorize.activate(color_mode); + print_stacktrace(st, os, colorize); + return os; + } + + template + FILE *print(IT begin, IT end, FILE *fp = stderr, size_t thread_id = 0) { + cfile_streambuf obuf(fp); + std::ostream os(&obuf); + Colorize colorize(os); + colorize.activate(color_mode, fp); + print_stacktrace(begin, end, os, thread_id, colorize); + return fp; + } + + template + std::ostream &print(IT begin, IT end, std::ostream &os, + size_t thread_id = 0) { + Colorize colorize(os); + colorize.activate(color_mode); + print_stacktrace(begin, end, os, thread_id, colorize); + return os; + } + + TraceResolver const &resolver() const { return _resolver; } + +private: + TraceResolver _resolver; + SnippetFactory _snippets; + + template + void print_stacktrace(ST &st, std::ostream &os, Colorize &colorize) { + print_header(os, st.thread_id()); + _resolver.load_stacktrace(st); + if (reverse) { + for (size_t trace_idx = st.size(); trace_idx > 0; --trace_idx) { + print_trace(os, _resolver.resolve(st[trace_idx - 1]), colorize); + } + } else { + for (size_t trace_idx = 0; trace_idx < st.size(); ++trace_idx) { + print_trace(os, _resolver.resolve(st[trace_idx]), colorize); + } + } + } + + template + void print_stacktrace(IT begin, IT end, std::ostream &os, size_t thread_id, + Colorize &colorize) { + print_header(os, thread_id); + for (; begin != end; ++begin) { + print_trace(os, *begin, colorize); + } + } + + void print_header(std::ostream &os, size_t thread_id) { + os << "Stack trace (most recent call last)"; + if (thread_id) { + os << " in thread " << thread_id; + } + os << ":\n"; + } + + void print_trace(std::ostream &os, const ResolvedTrace &trace, + Colorize &colorize) { + os << "#" << std::left << std::setw(2) << trace.idx << std::right; + bool already_indented = true; + + if (!trace.source.filename.size() || object) { + os << " Object \"" << trace.object_filename << "\", at " << trace.addr + << ", in " << trace.object_function << "\n"; + already_indented = false; + } + + for (size_t inliner_idx = trace.inliners.size(); inliner_idx > 0; + --inliner_idx) { + if (!already_indented) { + os << " "; + } + const ResolvedTrace::SourceLoc &inliner_loc = + trace.inliners[inliner_idx - 1]; + print_source_loc(os, " | ", inliner_loc); + if (snippet) { + print_snippet(os, " | ", inliner_loc, colorize, Color::purple, + inliner_context_size); + } + already_indented = false; + } + + if (trace.source.filename.size()) { + if (!already_indented) { + os << " "; + } + print_source_loc(os, " ", trace.source, trace.addr); + if (snippet) { + print_snippet(os, " ", trace.source, colorize, Color::yellow, + trace_context_size); + } + } + } + + void print_snippet(std::ostream &os, const char *indent, + const ResolvedTrace::SourceLoc &source_loc, + Colorize &colorize, Color::type color_code, + int context_size) { + using namespace std; + typedef SnippetFactory::lines_t lines_t; + + lines_t lines = _snippets.get_snippet(source_loc.filename, source_loc.line, + static_cast(context_size)); + + for (lines_t::const_iterator it = lines.begin(); it != lines.end(); ++it) { + if (it->first == source_loc.line) { + colorize.set_color(color_code); + os << indent << ">"; + } else { + os << indent << " "; + } + os << std::setw(4) << it->first << ": " << it->second << "\n"; + if (it->first == source_loc.line) { + colorize.set_color(Color::reset); + } + } + } + + void print_source_loc(std::ostream &os, const char *indent, + const ResolvedTrace::SourceLoc &source_loc, + void *addr = nullptr) { + os << indent << "Source \"" << source_loc.filename << "\", line " + << source_loc.line << ", in " << source_loc.function; + + if (address && addr != nullptr) { + os << " [" << addr << "]"; + } + os << "\n"; + } +}; + +/*************** SIGNALS HANDLING ***************/ + +#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) + +class SignalHandling { +public: + static std::vector make_default_signals() { + const int posix_signals[] = { + // Signals for which the default action is "Core". + SIGABRT, // Abort signal from abort(3) + SIGBUS, // Bus error (bad memory access) + SIGFPE, // Floating point exception + SIGILL, // Illegal Instruction + SIGIOT, // IOT trap. A synonym for SIGABRT + SIGQUIT, // Quit from keyboard + SIGSEGV, // Invalid memory reference + SIGSYS, // Bad argument to routine (SVr4) + SIGTRAP, // Trace/breakpoint trap + SIGXCPU, // CPU time limit exceeded (4.2BSD) + SIGXFSZ, // File size limit exceeded (4.2BSD) +#if defined(BACKWARD_SYSTEM_DARWIN) + SIGEMT, // emulation instruction executed +#endif + }; + return std::vector(posix_signals, + posix_signals + + sizeof posix_signals / sizeof posix_signals[0]); + } + + SignalHandling(const std::vector &posix_signals = make_default_signals()) + : _loaded(false) { + bool success = true; + + const size_t stack_size = 1024 * 1024 * 8; + _stack_content.reset(static_cast(malloc(stack_size))); + if (_stack_content) { + stack_t ss; + ss.ss_sp = _stack_content.get(); + ss.ss_size = stack_size; + ss.ss_flags = 0; + if (sigaltstack(&ss, nullptr) < 0) { + success = false; + } + } else { + success = false; + } + + for (size_t i = 0; i < posix_signals.size(); ++i) { + struct sigaction action; + memset(&action, 0, sizeof action); + action.sa_flags = + static_cast(SA_SIGINFO | SA_ONSTACK | SA_NODEFER | SA_RESETHAND); + sigfillset(&action.sa_mask); + sigdelset(&action.sa_mask, posix_signals[i]); +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#endif + action.sa_sigaction = &sig_handler; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + int r = sigaction(posix_signals[i], &action, nullptr); + if (r < 0) + success = false; + } + + _loaded = success; + } + + bool loaded() const { return _loaded; } + + static void handleSignal(int, siginfo_t *info, void *_ctx) { + ucontext_t *uctx = static_cast(_ctx); + + StackTrace st; + void *error_addr = nullptr; +#ifdef REG_RIP // x86_64 + error_addr = reinterpret_cast(uctx->uc_mcontext.gregs[REG_RIP]); +#elif defined(REG_EIP) // x86_32 + error_addr = reinterpret_cast(uctx->uc_mcontext.gregs[REG_EIP]); +#elif defined(__arm__) + error_addr = reinterpret_cast(uctx->uc_mcontext.arm_pc); +#elif defined(__aarch64__) +#if defined(__APPLE__) + error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__pc); +#else + error_addr = reinterpret_cast(uctx->uc_mcontext.pc); +#endif +#elif defined(__mips__) + error_addr = reinterpret_cast( + reinterpret_cast(&uctx->uc_mcontext)->sc_pc); +#elif defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || \ + defined(__POWERPC__) + error_addr = reinterpret_cast(uctx->uc_mcontext.regs->nip); +#elif defined(__riscv) + error_addr = reinterpret_cast(uctx->uc_mcontext.__gregs[REG_PC]); +#elif defined(__s390x__) + error_addr = reinterpret_cast(uctx->uc_mcontext.psw.addr); +#elif defined(__APPLE__) && defined(__x86_64__) + error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__rip); +#elif defined(__APPLE__) + error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__eip); +#else +#warning ":/ sorry, ain't know no nothing none not of your architecture!" +#endif + if (error_addr) { + st.load_from(error_addr, 32, reinterpret_cast(uctx), + info->si_addr); + } else { + st.load_here(32, reinterpret_cast(uctx), info->si_addr); + } + + Printer printer; + printer.address = true; + printer.print(st, stderr); + +#if (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700) || \ + (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L) + psiginfo(info, nullptr); +#else + (void)info; +#endif + } + +private: + details::handle _stack_content; + bool _loaded; + +#ifdef __GNUC__ + __attribute__((noreturn)) +#endif + static void + sig_handler(int signo, siginfo_t *info, void *_ctx) { + handleSignal(signo, info, _ctx); + + // try to forward the signal. + raise(info->si_signo); + + // terminate the process immediately. + puts("watf? exit"); + _exit(EXIT_FAILURE); + } +}; + +#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN + +#ifdef BACKWARD_SYSTEM_WINDOWS + +class SignalHandling { +public: + SignalHandling(const std::vector & = std::vector()) + : reporter_thread_([]() { + /* We handle crashes in a utility thread: + backward structures and some Windows functions called here + need stack space, which we do not have when we encounter a + stack overflow. + To support reporting stack traces during a stack overflow, + we create a utility thread at startup, which waits until a + crash happens or the program exits normally. */ + + { + std::unique_lock lk(mtx()); + cv().wait(lk, [] { return crashed() != crash_status::running; }); + } + if (crashed() == crash_status::crashed) { + handle_stacktrace(skip_recs()); + } + { + std::unique_lock lk(mtx()); + crashed() = crash_status::ending; + } + cv().notify_one(); + }) { + SetUnhandledExceptionFilter(crash_handler); + + signal(SIGABRT, signal_handler); + _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + + std::set_terminate(&terminator); +#ifndef BACKWARD_ATLEAST_CXX17 + std::set_unexpected(&terminator); +#endif + _set_purecall_handler(&terminator); + _set_invalid_parameter_handler(&invalid_parameter_handler); + } + bool loaded() const { return true; } + + ~SignalHandling() { + { + std::unique_lock lk(mtx()); + crashed() = crash_status::normal_exit; + } + + cv().notify_one(); + + reporter_thread_.join(); + } + +private: + static CONTEXT *ctx() { + static CONTEXT data; + return &data; + } + + enum class crash_status { running, crashed, normal_exit, ending }; + + static crash_status &crashed() { + static crash_status data; + return data; + } + + static std::mutex &mtx() { + static std::mutex data; + return data; + } + + static std::condition_variable &cv() { + static std::condition_variable data; + return data; + } + + static HANDLE &thread_handle() { + static HANDLE handle; + return handle; + } + + std::thread reporter_thread_; + + // TODO: how not to hardcode these? + static const constexpr int signal_skip_recs = +#ifdef __clang__ + // With clang, RtlCaptureContext also captures the stack frame of the + // current function Below that, there are 3 internal Windows functions + 4 +#else + // With MSVC cl, RtlCaptureContext misses the stack frame of the current + // function The first entries during StackWalk are the 3 internal Windows + // functions + 3 +#endif + ; + + static int &skip_recs() { + static int data; + return data; + } + + static inline void terminator() { + crash_handler(signal_skip_recs); + abort(); + } + + static inline void signal_handler(int) { + crash_handler(signal_skip_recs); + abort(); + } + + static inline void __cdecl invalid_parameter_handler(const wchar_t *, + const wchar_t *, + const wchar_t *, + unsigned int, + uintptr_t) { + crash_handler(signal_skip_recs); + abort(); + } + + NOINLINE static LONG WINAPI crash_handler(EXCEPTION_POINTERS *info) { + // The exception info supplies a trace from exactly where the issue was, + // no need to skip records + crash_handler(0, info->ContextRecord); + return EXCEPTION_CONTINUE_SEARCH; + } + + NOINLINE static void crash_handler(int skip, CONTEXT *ct = nullptr) { + + if (ct == nullptr) { + RtlCaptureContext(ctx()); + } else { + memcpy(ctx(), ct, sizeof(CONTEXT)); + } + DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), + GetCurrentProcess(), &thread_handle(), 0, FALSE, + DUPLICATE_SAME_ACCESS); + + skip_recs() = skip; + + { + std::unique_lock lk(mtx()); + crashed() = crash_status::crashed; + } + + cv().notify_one(); + + { + std::unique_lock lk(mtx()); + cv().wait(lk, [] { return crashed() != crash_status::crashed; }); + } + } + + static void handle_stacktrace(int skip_frames = 0) { + // printer creates the TraceResolver, which can supply us a machine type + // for stack walking. Without this, StackTrace can only guess using some + // macros. + // StackTrace also requires that the PDBs are already loaded, which is done + // in the constructor of TraceResolver + Printer printer; + + StackTrace st; + st.set_machine_type(printer.resolver().machine_type()); + st.set_thread_handle(thread_handle()); + st.load_here(32 + skip_frames, ctx()); + st.skip_n_firsts(skip_frames); + + printer.address = true; + printer.print(st, std::cerr); + } +}; + +#endif // BACKWARD_SYSTEM_WINDOWS + +#ifdef BACKWARD_SYSTEM_UNKNOWN + +class SignalHandling { +public: + SignalHandling(const std::vector & = std::vector()) {} + bool init() { return false; } + bool loaded() { return false; } +}; + +#endif // BACKWARD_SYSTEM_UNKNOWN + +} // namespace backward + +#endif /* H_GUARD */ +#endif // defined(PROJECT_ENABLE_BACKWARD_CPP) diff --git a/support/include/utils/all.hpp b/support/include/utils/all.hpp new file mode 100644 index 0000000..4a7851f --- /dev/null +++ b/support/include/utils/all.hpp @@ -0,0 +1,59 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_ALL_HPP_ +#define MONITARR_INCLUDE_UTILS_ALL_HPP_ + +#include "utils/config.hpp" + +#include "utils/base64.hpp" +#include "utils/collection.hpp" +#if defined(_WIN32) +#include "utils/com_init_wrapper.hpp" +#endif // defined(_WIN32) +#include "utils/common.hpp" +#if defined(PROJECT_ENABLE_SQLITE) +#include "utils/db/sqlite/db_common.hpp" +#include "utils/db/sqlite/db_delete.hpp" +#include "utils/db/sqlite/db_insert.hpp" +#include "utils/db/sqlite/db_select.hpp" +#include "utils/db/sqlite/db_update.hpp" +#endif // defined(PROJECT_ENABLE_SQLITE) +#if defined(PROJECT_ENABLE_LIBSODIUM) +#include "utils/encrypting_reader.hpp" +#include "utils/encryption.hpp" +#endif // defined(PROJECT_ENABLE_LIBSODIUM) +#include "utils/error.hpp" +#include "utils/file.hpp" +#if defined(PROJECT_ENABLE_LIBSODIUM) +#include "utils/hash.hpp" +#endif // defined(PROJECT_ENABLE_LIBSODIUM) +#include "utils/path.hpp" +#include "utils/string.hpp" +#include "utils/time.hpp" +#if !defined(_WIN32) +#include "utils/unix.hpp" +#endif // !defined(_WIN32) +#if defined(_WIN32) +#include "utils/windows.hpp" +#endif // defined(_WIN32) + +#endif // MONITARR_INCLUDE_UTILS_ALL_HPP_ diff --git a/support/include/utils/base64.hpp b/support/include/utils/base64.hpp new file mode 100644 index 0000000..2c63569 --- /dev/null +++ b/support/include/utils/base64.hpp @@ -0,0 +1,174 @@ +// NOLINTBEGIN +#ifndef _MACARON_BASE64_H_ +#define _MACARON_BASE64_H_ + +/** + * The MIT License (MIT) + * Copyright (c) 2016 tomykaira + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wuseless-cast" +#endif + +#include +#include +#include + +namespace macaron::Base64 { +static std::string Encode(const unsigned char *data, std::size_t len) { + static constexpr std::array sEncodingTable{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', + }; + + auto in_len{len}; + std::string ret; + if (in_len > 0) { + std::size_t out_len{4U * ((in_len + 2U) / 3U)}; + ret = std::string(out_len, '\0'); + std::size_t i; + auto *p = reinterpret_cast(ret.data()); + + for (i = 0U; i < in_len - 2U; i += 3U) { + *p++ = sEncodingTable[(data[i] >> 2U) & 0x3F]; + *p++ = sEncodingTable[((data[i] & 0x3) << 4U) | + ((int)(data[i + 1U] & 0xF0) >> 4U)]; + *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | + ((int)(data[i + 2U] & 0xC0) >> 6U)]; + *p++ = sEncodingTable[data[i + 2U] & 0x3F]; + } + if (i < in_len) { + *p++ = sEncodingTable[(data[i] >> 2U) & 0x3F]; + if (i == (in_len - 1U)) { + *p++ = sEncodingTable[((data[i] & 0x3) << 4U)]; + *p++ = '='; + } else { + *p++ = sEncodingTable[((data[i] & 0x3) << 4U) | + ((int)(data[i + 1U] & 0xF0) >> 4U)]; + *p++ = sEncodingTable[((data[i + 1U] & 0xF) << 2U)]; + } + *p++ = '='; + } + } + + return ret; +} + +[[maybe_unused]] static std::string Encode(std::string_view data) { + return Encode(reinterpret_cast(data.data()), + data.size()); +} + +[[maybe_unused]] static std::vector +Decode(std::string_view input) { + static constexpr std::array kDecodingTable{ + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, + }; + + std::vector out; + if (not input.empty()) { + auto in_len{input.size()}; + if (in_len % 4U != 0U) { + throw std::runtime_error("Input data size is not a multiple of 4"); + } + + std::size_t out_len{in_len / 4U * 3U}; + if (input[in_len - 1U] == '=') { + out_len--; + } + if (input[in_len - 2U] == '=') { + out_len--; + } + + out.resize(out_len); + + for (std::size_t i = 0U, j = 0U; i < in_len;) { + std::uint32_t a = + input.at(i) == '=' + ? 0U & i++ + : kDecodingTable[static_cast(input.at(i++))]; + std::uint32_t b = + input.at(i) == '=' + ? 0U & i++ + : kDecodingTable[static_cast(input.at(i++))]; + std::uint32_t c = + input.at(i) == '=' + ? 0U & i++ + : kDecodingTable[static_cast(input.at(i++))]; + std::uint32_t d = + input.at(i) == '=' + ? 0U & i++ + : kDecodingTable[static_cast(input.at(i++))]; + + std::uint32_t triple = + (a << 3U * 6U) + (b << 2U * 6U) + (c << 1U * 6U) + (d << 0U * 6U); + + if (j < out_len) + out[j++] = (triple >> 2U * 8U) & 0xFF; + if (j < out_len) + out[j++] = (triple >> 1U * 8U) & 0xFF; + if (j < out_len) + out[j++] = (triple >> 0U * 8U) & 0xFF; + } + } + + return out; +} +} // namespace macaron::Base64 + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif /* _MACARON_BASE64_H_ */ + +// NOLINTEND diff --git a/support/include/utils/collection.hpp b/support/include/utils/collection.hpp new file mode 100644 index 0000000..e65c149 --- /dev/null +++ b/support/include/utils/collection.hpp @@ -0,0 +1,178 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_COLLECTION_HPP_ +#define MONITARR_INCLUDE_UTILS_COLLECTION_HPP_ + +#include "utils/config.hpp" + +#include "utils/error.hpp" +#include "utils/string.hpp" + +namespace monitarr::utils::collection { +template +[[nodiscard]] inline auto excludes(const col_t &collection, + const typename col_t::value_type &val) + -> bool; + +template +[[nodiscard]] inline auto includes(const col_t &collection, + const typename col_t::value_type &val) + -> bool; + +template +[[nodiscard]] inline auto from_hex_string(std::string_view str, val_t &val) + -> bool; + +template +[[nodiscard]] inline auto from_hex_string(std::wstring_view str, val_t &val) + -> bool; +template +inline auto remove_element(col_t &collection, + const typename col_t::value_type &value) -> col_t &; + +template +[[nodiscard]] inline auto to_hex_string(const col_t &collection) -> std::string; + +template +[[nodiscard]] inline auto to_hex_wstring(const col_t &collection) + -> std::wstring; + +template +inline auto excludes(const col_t &collection, + const typename col_t::value_type &val) -> bool { + return std::find(collection.begin(), collection.end(), val) == + collection.end(); +} + +template +inline auto includes(const col_t &collection, + const typename col_t::value_type &val) -> bool { + return std::find(collection.begin(), collection.end(), val) != + collection.end(); +} + +template +[[nodiscard]] inline auto from_hex_string_t(std::string_view str, val_t &val) + -> bool { + MONITARR_USES_FUNCTION_NAME(); + + static constexpr const auto base16{16}; + + try { + val.clear(); + + std::string fmt_val{str}; + utils::string::trim(fmt_val); + if (fmt_val.empty()) { + return true; + } + + fmt_val = utils::string::to_lower(fmt_val); + if (utils::string::begins_with(fmt_val, "0x")) { + fmt_val = fmt_val.substr(2U); + } + + if (fmt_val.empty()) { + throw utils::error::create_exception(function_name, + { + "hex string is invalid", + str, + }); + } + + if (fmt_val.length() % 2U) { + fmt_val = '0' + fmt_val; + } + + auto iter = std::find_if_not( + fmt_val.begin(), fmt_val.end(), [](auto cur_char) -> bool { + auto check = static_cast(cur_char); + return ((check >= 48U && check <= 57U) || + (check >= 97U && check <= 102U)); + }); + if (iter != fmt_val.end()) { + auto invalid_idx{std::distance(fmt_val.begin(), iter)}; + throw std::range_error(utils::error::create_error_message({ + function_name, + "invalid character in hex string", + std::to_string(invalid_idx), + std::string(1U, str.at(invalid_idx)), + str, + })); + } + + val.resize(fmt_val.length() / 2U); + for (std::size_t idx = 0U; idx < fmt_val.length(); idx += 2U) { + val.at(idx / 2U) = static_cast( + std::strtoul(fmt_val.substr(idx, 2U).c_str(), nullptr, base16)); + } + + return true; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + val.clear(); + return false; +} +template +inline auto from_hex_string(std::string_view str, val_t &val) -> bool { + return from_hex_string_t(str, val); +} + +template +inline auto from_hex_string(std::wstring_view str, val_t &val) -> bool { + return from_hex_string_t(utils::string::to_utf8(str), val); +} + +template +inline auto remove_element(col_t &collection, + const typename col_t::value_type &value) -> col_t & { + collection.erase(std::remove(collection.begin(), collection.end(), value), + collection.end()); + return collection; +} + +template +inline auto to_hex_string(const col_t &collection) -> std::string { + static_assert(sizeof(typename col_t::value_type) == 1U, + "value_type must be 1 byte in size"); + static constexpr const auto mask = 0xFF; + + std::stringstream stream{}; + for (auto &&val : collection) { + stream << std::setfill('0') << std::setw(2) << std::hex + << (static_cast(val) & mask); + } + + return stream.str(); +} + +template +inline auto to_hex_wstring(const col_t &collection) -> std::wstring { + return utils::string::from_utf8(to_hex_string(collection)); +} +} // namespace monitarr::utils::collection + +#endif // MONITARR_INCLUDE_UTILS_COLLECTION_HPP_ diff --git a/support/include/utils/com_init_wrapper.hpp b/support/include/utils/com_init_wrapper.hpp new file mode 100644 index 0000000..12237d9 --- /dev/null +++ b/support/include/utils/com_init_wrapper.hpp @@ -0,0 +1,54 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_COM_INIT_WRAPPER_HPP_ +#define MONITARR_INCLUDE_UTILS_COM_INIT_WRAPPER_HPP_ +#if defined(_WIN32) + +#include "utils/config.hpp" + +namespace monitarr::utils { +struct com_init_wrapper final { + com_init_wrapper() + : initialized_( + SUCCEEDED(::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) {} + + com_init_wrapper(const com_init_wrapper &) = delete; + com_init_wrapper(com_init_wrapper &&) = delete; + + ~com_init_wrapper() { + if (initialized_) { + ::CoUninitialize(); + } + } + + auto operator=(const com_init_wrapper &) -> com_init_wrapper & = delete; + auto operator=(com_init_wrapper &&) -> com_init_wrapper & = delete; + + [[nodiscard]] auto is_initialized() const -> bool { return initialized_; } + +private: + BOOL initialized_; +}; +} // namespace monitarr::utils + +#endif // defined(_WIN32) +#endif // MONITARR_INCLUDE_UTILS_COM_INIT_WRAPPER_HPP_ diff --git a/support/include/utils/common.hpp b/support/include/utils/common.hpp new file mode 100644 index 0000000..b949921 --- /dev/null +++ b/support/include/utils/common.hpp @@ -0,0 +1,145 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_COMMON_HPP_ +#define MONITARR_INCLUDE_UTILS_COMMON_HPP_ + +#include "utils/config.hpp" + +namespace monitarr::utils { +struct result final { + std::string function_name; + bool ok{true}; + std::string reason{"success"}; + + [[nodiscard]] operator bool() const { return ok; } +}; + +using retryable_action_t = std::function; + +[[nodiscard]] inline constexpr auto +calculate_read_size(std::uint64_t total_size, std::size_t read_size, + std::uint64_t offset) -> std::size_t { + return static_cast( + ((offset + read_size) > total_size) + ? ((offset < total_size) ? total_size - offset : 0U) + : read_size); +} + +[[nodiscard]] auto compare_version_strings(std::string version1, + std::string version2) + -> std::int32_t; + +[[nodiscard]] auto compare_version_strings(std::wstring_view version1, + std::wstring_view version2) + -> std::int32_t; + +#if defined(PROJECT_ENABLE_STDUUID) +[[nodiscard]] auto create_uuid_string() -> std::string; + +[[nodiscard]] auto create_uuid_wstring() -> std::wstring; +#endif // defined(PROJECT_ENABLE_STDUUID) + +template +[[nodiscard]] inline constexpr auto divide_with_ceiling(result_t numerator, + data_t denominator) + -> result_t; + +template +[[nodiscard]] inline auto generate_random_between(data_t begin, data_t end) + -> data_t; + +[[nodiscard]] auto generate_random_string(std::size_t length) -> std::string; + +[[nodiscard]] auto generate_random_wstring(std::size_t length) -> std::wstring; + +#if defined(PROJECT_ENABLE_LIBSODIUM) +template +[[nodiscard]] inline auto generate_secure_random() -> data_t; + +template +[[nodiscard]] inline auto generate_secure_random(std::size_t size) -> data_t; +#endif // defined(PROJECT_ENABLE_LIBSODIUM) + +[[nodiscard]] auto get_environment_variable(std::string_view variable) + -> std::string; + +[[nodiscard]] auto get_environment_variable(std::wstring_view variable) + -> std::wstring; + +#if defined(PROJECT_ENABLE_BOOST) +[[nodiscard]] auto get_next_available_port(std::uint16_t first_port, + std::uint16_t &available_port) + -> bool; +#endif // defined(PROJECT_ENABLE_BOOST) + +[[nodiscard]] auto retry_action( + retryable_action_t action, std::size_t retry_count = 200U, + std::chrono::milliseconds retry_wait = std::chrono::milliseconds(10)) + -> bool; + +template +inline constexpr auto divide_with_ceiling(result_t numerator, + data_t denominator) -> result_t { + static_assert(std::is_integral_v>, + "denominator must be an integral type"); + + return denominator == 0 + ? 0 + : (numerator / denominator) + (numerator % denominator != 0); +} + +template +inline auto generate_random_between(data_t begin, data_t end) -> data_t { + static_assert(std::is_integral_v>, + "data_t must be an integral type"); + if (end <= begin) { + throw std::range_error("end must be greater than begin"); + } + + thread_local std::mt19937 gen( + static_cast(std::time(nullptr) ^ std::random_device{}())); + std::uniform_int_distribution dis(begin, end); + return dis(gen); +} + +#if defined(PROJECT_ENABLE_LIBSODIUM) +template inline auto generate_secure_random() -> data_t { + static_assert(!is_collection>::value, + "data_t is a vector or collection"); + data_t ret{}; + randombytes_buf(&ret, sizeof(ret)); + return ret; +} + +template +inline auto generate_secure_random(std::size_t size) -> data_t { + static_assert(is_collection>::value, + "data_t is not a vector or collection"); + data_t ret; + ret.resize(size); + randombytes_buf(ret.data(), ret.size() * sizeof(typename data_t::value_type)); + return ret; +} +#endif // defined(PROJECT_ENABLE_LIBSODIUM) +} // namespace monitarr::utils + +#endif // MONITARR_INCLUDE_UTILS_COMMON_HPP_ diff --git a/support/include/utils/config.hpp b/support/include/utils/config.hpp new file mode 100644 index 0000000..ac59ec2 --- /dev/null +++ b/support/include/utils/config.hpp @@ -0,0 +1,482 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_CONFIG_HPP_ +#define MONITARR_INCLUDE_UTILS_CONFIG_HPP_ + +#define NOMINMAX + +#if defined(_WIN32) +#define WINVER 0x0602 +#define _WIN32_WINNT WINVER +#define WIN32_LEAN_AND_MEAN + +#include +#include + +#include +#if defined(PROJECT_ENABLE_WINFSP) +#include +#endif // defined(PROJECT_ENABLE_WINFSP) + +#include +#if !defined(__cplusplus) +#include +#endif // !defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !defined(__cplusplus) +#include +#endif // !defined(__cplusplus) +#include +#include +#if !defined(__cplusplus) +#include +#endif // !defined(__cplusplus) +#else // !defined(_WIN32) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__LFS64__) +#include +#else // !defined(__LFS64__) +#include +#endif // defined(__LFS64__) + +#if defined(__linux__) +#include +#endif // defined(HAS_SETXATTR) + +#if defined(HAS_SETXATTR) +#include +#include +#endif // defined(HAS_SETXATTR) + +#if defined(__APPLE__) +#include +#include +#include +#include +#include +#endif // defined(__APPLE__) + +#include +#endif // defined(_WIN32) + +#if defined(HAS_WORDEXP_H) +#include +#endif // defined(HAS_WORDEXP_H) + +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif // defined(__cplusplus) + +#if defined(PROJECT_ENABLE_CURL) +#include "curl/curl.h" +#include "curl/multi.h" +#endif // defined(PROJECT_ENABLE_CURL) + +#if defined(PROJECT_ENABLE_FUSE) +#if FUSE_USE_VERSION >= 30 +#include +#include +#else +#include +#endif +#endif // defined(PROJECT_ENABLE_FUSE) + +#if defined(PROJECT_ENABLE_FZF) +#if defined(__cplusplus) +extern "C" { +#endif // defined(__cplusplus) +#include "fzf.h" +#if defined(__cplusplus) +} +#endif // defined(__cplusplus) +#endif // defined(PROJECT_ENABLE_FZF) + +#if defined(PROJECT_ENABLE_OPENSSL) +#include "openssl/ssl.h" +#endif // defined(PROJECT_ENABLE_OPENSSL) + +#if defined(PROJECT_ENABLE_LIBDSM) +#if defined(__cplusplus) +extern "C" { +#endif // defined(__cplusplus) +#include "bdsm/bdsm.h" +#if defined(__cplusplus) +} + +struct netbios_ns_deleter final { + void operator()(netbios_ns *ns) const { + if (ns != nullptr) { + netbios_ns_destroy(ns); + } + } +}; +using netbios_ns_t = std::unique_ptr; + +inline const auto smb_session_deleter = [](smb_session *session) { + if (session != nullptr) { + smb_session_destroy(session); + } +}; +using smb_session_t = std::shared_ptr; + +struct smb_stat_deleter final { + void operator()(smb_stat st) const { + if (st != nullptr) { + smb_stat_destroy(st); + } + } +}; +using smb_stat_t = std::unique_ptr; + +struct smb_stat_list_deleter final { + void operator()(smb_file *list) const { + if (list != nullptr) { + smb_stat_list_destroy(list); + } + } +}; +using smb_stat_list_t = std::unique_ptr; +#endif // defined(__cplusplus) +#endif // defined(PROJECT_ENABLE_LIBDSM) + +#if defined(PROJECT_ENABLE_LIBEVENT) +#include "event2/buffer.h" +#include "event2/bufferevent.h" +#include "event2/listener.h" +#include "event2/thread.h" +#include "event2/util.h" +#endif // defined(PROJECT_ENABLE_LIBEVENT) + +#if defined(PROJECT_ENABLE_LIBSODIUM) +#include "sodium.h" +#endif // defined(PROJECT_ENABLE_LIBSODIUM) + +#if defined(PROJECT_ENABLE_SDL) +#include "SDL.h" +#include "SDL_gamecontroller.h" +#include "SDL_joystick.h" +#endif // defined(PROJECT_ENABLE_SDL) + +#if defined(PROJECT_ENABLE_SQLITE) +#include "sqlite3.h" +#endif // defined(PROJECT_ENABLE_SQLITE) + +#if defined(PROJECT_ENABLE_VLC) +#include + +#if defined(__cplusplus) +[[nodiscard]] inline auto get_libvlc_error_msg() -> std::string { + const auto *msg = libvlc_errmsg(); + return msg == nullptr ? "none" : msg; +} + +struct vlc_deleter final { + void operator()(libvlc_instance_t *inst) const { + if (inst != nullptr) { + libvlc_release(inst); + } + } +}; +using vlc_t = std::unique_ptr; + +struct vlc_media_deleter final { + void operator()(libvlc_media_t *media) const { + if (media != nullptr) { + libvlc_media_release(media); + } + } +}; +using vlc_media_t = std::unique_ptr; + +struct vlc_media_list_deleter final { + void operator()(libvlc_media_list_t *media_list) const { + if (media_list != nullptr) { + libvlc_media_list_release(media_list); + } + } +}; +using vlc_media_list_t = + std::unique_ptr; + +struct vlc_string_deleter final { + void operator()(char *str) const { + if (str != nullptr) { + libvlc_free(str); + } + } +}; +using vlc_string_t = std::unique_ptr; +#endif // defined(__cplusplus) +#endif // defined(PROJECT_ENABLE_VLC) + +#if !defined(fstat64) +#define fstat64 fstat +#endif // !defined(fstat64) + +#if !defined(pread64) +#define pread64 pread +#endif // !defined(pread64) + +#if !defined(pwrite64) +#define pwrite64 pwrite +#endif // !defined(pwrite64) + +#if !defined(stat64) +#define stat64 stat +#endif // !defined(stat64) + +#if !defined(statfs64) +#define statfs64 statfs +#endif // !defined(statfs64) + +#if !defined(off64_t) +#define off64_t std::size_t +#endif // !defined(off64_t) + +#if !defined(__off64_t) +#define __off64_t off64_t +#endif // !defined(__off64_t) + +#if defined(__cplusplus) +#if defined(PROJECT_ENABLE_BOOST) +#include "boost/archive/text_iarchive.hpp" +#include "boost/archive/text_oarchive.hpp" +#include "boost/asio.hpp" +#include "boost/asio/io_context.hpp" +#include "boost/bind/bind.hpp" +#include "boost/dynamic_bitset.hpp" +#include "boost/dynamic_bitset/serialization.hpp" +#include "boost/endian/conversion.hpp" +#include "boost/integer.hpp" +#include "boost/interprocess/sync/named_mutex.hpp" +#include "boost/interprocess/sync/scoped_lock.hpp" +#include "boost/multiprecision/cpp_dec_float.hpp" +#include "boost/multiprecision/cpp_int.hpp" +#include "boost/serialization/vector.hpp" +#endif // defined(PROJECT_ENABLE_BOOST) + +#if defined(PROJECT_ENABLE_CLI11) +#if defined(PROJECT_IS_MINGW) && !defined(PROJECT_IS_MINGW_UNIX) +#include "CLI/CLI.hpp" +#else // !defined(PROJECT_IS_MINGW) || defined(PROJECT_IS_MINGW_UNIX) +#include "CLI11.hpp" +#endif // defined(PROJECT_IS_MINGW) && !defined(PROJECT_IS_MINGW_UNIX) +#endif // defined(PROJECT_ENABLE_CLI11) + +#if defined(PROJECT_ENABLE_CPP_HTTPLIB) +#include "httplib.h" +#endif // defined(PROJECT_ENABLE_JSON) + +#if defined(PROJECT_ENABLE_DTL) +#include "dtl/dtl.hpp" +#endif // defined(PROJECT_ENABLE_DTL) + +#if defined(PROJECT_ENABLE_JSON) +#include "json.hpp" +#endif // defined(PROJECT_ENABLE_JSON) + +#if defined(PROJECT_ENABLE_NANA) +#include "nana/gui.hpp" +#include "nana/gui/timer.hpp" +#include "nana/gui/widgets/button.hpp" +#include "nana/gui/widgets/combox.hpp" +#include "nana/gui/widgets/group.hpp" +#include "nana/gui/widgets/label.hpp" +#include "nana/gui/widgets/panel.hpp" +#include "nana/gui/widgets/picture.hpp" +#include "nana/gui/widgets/tabbar.hpp" +#endif // defined(PROJECT_ENABLE_NANA) + +#if defined(PROJECT_ENABLE_PUGIXML) +#include "pugixml.hpp" +#endif // defined(PROJECT_ENABLE_PUGIXML) + +#if defined(PROJECT_ENABLE_ROCKSDB) +#include "rocksdb/db.h" +#include "rocksdb/utilities/transaction_db.h" +#endif // defined(PROJECT_ENABLE_ROCKSDB) + +#if defined(PROJECT_ENABLE_SFML) +#include "RoundedRectangleShape.hpp" +#include "SFML/Graphics.hpp" +#include "Text2.hpp" +#endif // defined(PROJECT_ENABLE_SFML) + +#if defined(PROJECT_ENABLE_SAGO_PLATFORM_FOLDERS) +#include "platform_folders.hpp" +#endif // defined(PROJECT_ENABLE_SAGO_PLATFORM_FOLDERS) + +#if defined(PROJECT_ENABLE_SPDLOG) +#include "spdlog/async.h" +#include "spdlog/fmt/bundled/core.h" +#include "spdlog/fmt/bundled/format.h" +#include "spdlog/fmt/bundled/ranges.h" +#include "spdlog/fmt/chrono.h" +#include "spdlog/sinks/rotating_file_sink.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#include "spdlog/spdlog.h" +#endif // defined(PROJECT_ENABLE_SPDLOG) + +#if defined(PROJECT_ENABLE_STDUUID) +#include "uuid.h" +#endif // defined(PROJECT_ENABLE_STDUUID) + +#if defined(PROJECT_ENABLE_TPL) +#include "process.hpp" +#endif // defined(PROJECT_ENABLE_TPL) + +#if defined(PROJECT_ENABLE_WINFSP) +#include "winfsp/winfsp.hpp" +#endif // defined(PROJECT_ENABLE_WINFSP) + +namespace monitarr { +using data_buffer = std::vector; +using mutex_lock = std::lock_guard; +using recur_mutex_lock = std::lock_guard; +using stop_type = std::atomic_bool; +using stop_type_callback = std::function; +using unique_mutex_lock = std::unique_lock; +using unique_recur_mutex_lock = std::unique_lock; + +#if defined(_WIN32) +#if defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) +inline constexpr const auto max_path_length = std::size_t{32767U}; +#else // !defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) +inline constexpr const auto max_path_length = std::size_t{MAX_PATH}; +#endif // defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) + +using native_handle = HANDLE; +#else // !defined(_WIN32) +inline constexpr const auto max_path_length = std::size_t{PATH_MAX}; +using native_handle = int; +#if !defined(INVALID_HANDLE_VALUE) +#define INVALID_HANDLE_VALUE (-1) +#endif // !defined(INVALID_HANDLE_VALUE) +#endif // defined(_WIN32) + +template struct overloaded : Ts... { + using Ts::operator()...; +}; +template overloaded(Ts...) -> overloaded; + +template struct is_collection { + static const bool value = false; +}; + +template +struct is_collection> : std::true_type {}; + +template struct is_collection> : std::true_type {}; + +template <> struct is_collection : std::true_type {}; + +template <> struct is_collection : std::true_type {}; + +struct file_deleter final { + void operator()(FILE *file) { + if (file != nullptr) { + fclose(file); + } + } +}; +using file_t = std::unique_ptr; + +struct http_range final { + std::uint64_t begin{}; + std::uint64_t end{}; +}; + +using http_headers = std::map; +using http_query_parameters = std::map; +using http_ranges = std::vector; +} // namespace monitarr +#endif // defined(__cplusplus) + +#define MONITARR_USES_FUNCTION_NAME() \ + static constexpr const std::string_view function_name { \ + static_cast(__FUNCTION__), \ + } + +#endif // MONITARR_INCLUDE_UTILS_CONFIG_HPP_ diff --git a/support/include/utils/db/sqlite/db_common.hpp b/support/include/utils/db/sqlite/db_common.hpp new file mode 100644 index 0000000..48fb005 --- /dev/null +++ b/support/include/utils/db/sqlite/db_common.hpp @@ -0,0 +1,170 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_COMMON_HPP_ +#define MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_COMMON_HPP_ +#if defined(PROJECT_ENABLE_SQLITE) + +#include "utils/config.hpp" + +#include "utils/error.hpp" + +namespace monitarr::utils::db::sqlite { +using db_types_t = std::variant; + +struct sqlite3_deleter final { + void operator()(sqlite3 *db3) const; +}; + +using db3_t = std::unique_ptr; + +struct sqlite3_statement_deleter final { + void operator()(sqlite3_stmt *stmt) const { + if (stmt != nullptr) { + sqlite3_finalize(stmt); + } + } +}; + +using db3_stmt_t = std::unique_ptr; + +[[nodiscard]] auto +create_db(std::string db_path, + const std::map &sql_create_tables) -> db3_t; + +[[nodiscard]] auto execute_sql(sqlite3 &db3, const std::string &sql, + std::string &err) -> bool; + +void set_journal_mode(sqlite3 &db3); + +struct db_comp_data_t final { + std::string column_name; + std::string op_type; +}; + +struct db_context_t { + db_context_t(sqlite3 *db3_, std::string table_name_) + : db3(db3_), table_name(std::move(table_name_)) {} + + sqlite3 *db3{}; + std::string table_name; +}; + +struct db_result final { + struct context final { + db3_stmt_t stmt; + }; + + class db_column final { + public: + db_column(std::int32_t index, std::string name, db_types_t value) noexcept; + + db_column() noexcept = default; + db_column(const db_column &) = default; + db_column(db_column &&column) noexcept = default; + + ~db_column() = default; + + auto operator=(const db_column &) -> db_column & = default; + auto operator=(db_column &&) -> db_column & = default; + + private: + std::int32_t index_{}; + std::string name_; + db_types_t value_; + + public: + [[nodiscard]] auto get_index() const -> std::int32_t { return index_; } + + [[nodiscard]] auto get_name() const -> std::string { return name_; } + + template + [[nodiscard]] auto get_value() const -> data_type { + MONITARR_USES_FUNCTION_NAME(); + + return std::visit( + overloaded{ + [](const data_type &value) -> data_type { return value; }, + [](auto &&) -> data_type { + throw utils::error::create_exception( + function_name, { + "data type not supported", + }); + }, + }, + value_); + } + +#if defined(PROJECT_ENABLE_JSON) + [[nodiscard]] auto get_value_as_json() const -> nlohmann::json; +#endif // defined(PROJECT_ENABLE_JSON) + }; + + class db_row final { + public: + db_row(std::shared_ptr ctx); + + private: + std::map columns_; + + public: + [[nodiscard]] auto get_columns() const -> std::vector; + + [[nodiscard]] auto get_column(std::int32_t index) const -> db_column; + + [[nodiscard]] auto get_column(std::string name) const -> db_column; + }; + + db_result(db3_stmt_t stmt, std::int32_t res); + + db_result() = default; + db_result(const db_result &) = default; + db_result(db_result &&) noexcept = default; + + auto operator=(const db_result &) -> db_result & = default; + auto operator=(db_result &&) -> db_result & = default; + + using row = db_row; + +private: + std::shared_ptr ctx_; + mutable std::int32_t res_{}; + +private: + void set_res(std::int32_t res) const { res_ = res; } + +public: + [[nodiscard]] auto get_error() const -> std::int32_t { return res_; } + + [[nodiscard]] auto get_error_str() const -> std::string; + + [[nodiscard]] auto get_row(std::optional &opt_row) const -> bool; + + [[nodiscard]] auto has_row() const -> bool; + + void next_row() const; + + [[nodiscard]] auto ok() const -> bool; +}; +} // namespace monitarr::utils::db::sqlite + +#endif // defined(PROJECT_ENABLE_SQLITE) +#endif // MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_COMMON_HPP_ diff --git a/support/include/utils/db/sqlite/db_delete.hpp b/support/include/utils/db/sqlite/db_delete.hpp new file mode 100644 index 0000000..8ae14cd --- /dev/null +++ b/support/include/utils/db/sqlite/db_delete.hpp @@ -0,0 +1,73 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_DELETE_HPP_ +#define MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_DELETE_HPP_ +#if defined(PROJECT_ENABLE_SQLITE) + +#include "utils/db/sqlite/db_common.hpp" + +#include "utils/db/sqlite/db_where_t.hpp" + +namespace monitarr::utils::db::sqlite { +class db_delete final { +public: + struct context final : db_context_t { + struct db_delete_op_t final { + std::shared_ptr ctx; + + [[nodiscard]] auto dump() const -> std::string; + + [[nodiscard]] auto go() const -> db_result; + }; + + context(sqlite3 *db3_, std::string table_name_) + : db_context_t(db3_, table_name_) {} + + using w_t = db_where_t; + using wd_t = where_data_t; + + std::unique_ptr where_data; + }; + +public: + db_delete(sqlite3 &db3, std::string table_name) + : ctx_(std::make_shared(&db3, table_name)) {} + + db_delete(std::shared_ptr ctx) : ctx_(std::move(ctx)) {} + +private: + std::shared_ptr ctx_; + +public: + [[nodiscard]] auto dump() const -> std::string; + + [[nodiscard]] auto go() const -> db_result; + + [[nodiscard]] auto + group(context::w_t::group_func_t func) -> context::w_t::wn_t; + + [[nodiscard]] auto where(std::string column_name) const -> context::w_t::cn_t; +}; +} // namespace monitarr::utils::db::sqlite + +#endif // defined(PROJECT_ENABLE_SQLITE) +#endif // MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_DELETE_HPP_ diff --git a/support/include/utils/db/sqlite/db_insert.hpp b/support/include/utils/db/sqlite/db_insert.hpp new file mode 100644 index 0000000..78050e1 --- /dev/null +++ b/support/include/utils/db/sqlite/db_insert.hpp @@ -0,0 +1,64 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_INSERT_HPP_ +#define MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_INSERT_HPP_ +#if defined(PROJECT_ENABLE_SQLITE) + +#include "utils/db/sqlite/db_common.hpp" + +namespace monitarr::utils::db::sqlite { +class db_insert final { +public: + struct context final : db_context_t { + context(sqlite3 *db3_, std::string table_name_) + : db_context_t(db3_, table_name_) {} + + bool or_replace{false}; + std::map values; + }; + +public: + db_insert(sqlite3 &db3, std::string table_name) + : ctx_(std::make_shared(&db3, table_name)) {} + + db_insert(std::shared_ptr ctx) : ctx_(std::move(ctx)) {} + +private: + std::shared_ptr ctx_; + +public: + [[nodiscard]] auto or_replace() -> db_insert { + ctx_->or_replace = true; + return *this; + } + + [[nodiscard]] auto column_value(std::string column_name, db_types_t value) + -> db_insert; + + [[nodiscard]] auto dump() const -> std::string; + + [[nodiscard]] auto go() const -> db_result; +}; +} // namespace monitarr::utils::db::sqlite + +#endif // defined(PROJECT_ENABLE_SQLITE) +#endif // MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_INSERT_HPP_ diff --git a/support/include/utils/db/sqlite/db_select.hpp b/support/include/utils/db/sqlite/db_select.hpp new file mode 100644 index 0000000..9deb29e --- /dev/null +++ b/support/include/utils/db/sqlite/db_select.hpp @@ -0,0 +1,104 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_SELECT_HPP_ +#define MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_SELECT_HPP_ +#if defined(PROJECT_ENABLE_SQLITE) + +#include "utils/db/sqlite/db_common.hpp" + +#include "utils/db/sqlite/db_where_t.hpp" + +namespace monitarr::utils::db::sqlite { +class db_select final { +public: + struct context final : db_context_t { + struct db_select_op_t final { + std::shared_ptr ctx; + + [[nodiscard]] auto dump() const -> std::string; + + [[nodiscard]] auto go() const -> db_result; + + [[nodiscard]] auto group_by(std::string column_name) -> db_select_op_t; + + [[nodiscard]] auto limit(std::int32_t value) -> db_select_op_t; + + [[nodiscard]] auto offset(std::int32_t value) -> db_select_op_t; + + [[nodiscard]] auto order_by(std::string column_name, bool ascending) + -> db_select_op_t; + }; + + context(sqlite3 *db3_, std::string table_name_) + : db_context_t(db3_, table_name_) {} + + using w_t = db_where_t; + using wd_t = where_data_t; + + std::vector columns; + std::map count_columns; + + std::vector group_by; + std::optional limit; + std::optional offset; + std::optional> order_by; + + std::unique_ptr where_data; + }; + +public: + db_select(sqlite3 &db3, std::string table_name) + : ctx_(std::make_shared(&db3, table_name)) {} + + db_select(std::shared_ptr ctx) : ctx_(std::move(ctx)) {} + +private: + std::shared_ptr ctx_; + +public: + [[nodiscard]] auto column(std::string column_name) -> db_select; + + [[nodiscard]] auto count(std::string column_name, std::string as_column_name) + -> db_select; + + [[nodiscard]] auto dump() const -> std::string; + + [[nodiscard]] auto go() const -> db_result; + + [[nodiscard]] auto group_by(std::string column_name) -> db_select; + [[nodiscard]] auto group(context::w_t::group_func_t func) + + -> context::w_t::wn_t; + + [[nodiscard]] auto limit(std::int32_t value) -> db_select; + + [[nodiscard]] auto offset(std::int32_t value) -> db_select; + + [[nodiscard]] auto order_by(std::string column_name, bool ascending) + -> db_select; + + [[nodiscard]] auto where(std::string column_name) const -> context::w_t::cn_t; +}; +} // namespace monitarr::utils::db::sqlite + +#endif // defined(PROJECT_ENABLE_SQLITE) +#endif // MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_SELECT_HPP_ diff --git a/support/include/utils/db/sqlite/db_update.hpp b/support/include/utils/db/sqlite/db_update.hpp new file mode 100644 index 0000000..ed5742a --- /dev/null +++ b/support/include/utils/db/sqlite/db_update.hpp @@ -0,0 +1,91 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_UPDATE_HPP_ +#define MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_UPDATE_HPP_ +#if defined(PROJECT_ENABLE_SQLITE) + +#include "utils/db/sqlite/db_common.hpp" + +#include "utils/db/sqlite/db_where_t.hpp" + +namespace monitarr::utils::db::sqlite { +class db_update final { +public: + struct context final : db_context_t { + context(sqlite3 *db3_, std::string table_name_) + : db_context_t(db3_, table_name_) {} + + struct db_update_op_t final { + std::shared_ptr ctx; + + [[nodiscard]] auto dump() const -> std::string; + + [[nodiscard]] auto go() const -> db_result; + + [[nodiscard]] auto limit(std::int32_t value) -> db_update_op_t; + + [[nodiscard]] auto order_by(std::string column_name, bool ascending) + + -> db_update_op_t; + }; + + using w_t = db_where_t; + using wd_t = where_data_t; + + std::map column_values; + std::optional limit; + std::optional> order_by; + + std::unique_ptr where_data; + }; + +public: + db_update(sqlite3 &db3, std::string table_name) + : ctx_(std::make_shared(&db3, table_name)) {} + + db_update(std::shared_ptr ctx) : ctx_(std::move(ctx)) {} + +private: + std::shared_ptr ctx_; + +public: + [[nodiscard]] auto column_value(std::string column_name, db_types_t value) + -> db_update; + + [[nodiscard]] auto dump() const -> std::string; + + [[nodiscard]] auto go() const -> db_result; + + [[nodiscard]] auto group(context::w_t::group_func_t func) + -> context::w_t::wn_t; + + [[nodiscard]] auto limit(std::int32_t value) -> db_update; + + [[nodiscard]] auto order_by(std::string column_name, bool ascending) + -> db_update; + + [[nodiscard]] auto where(std::string column_name) const -> context::w_t::cn_t; +}; +} // namespace monitarr::utils::db::sqlite + +#endif // defined(PROJECT_ENABLE_SQLITE) +#endif // MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_UPDATE_HPP_ diff --git a/support/include/utils/db/sqlite/db_where_t.hpp b/support/include/utils/db/sqlite/db_where_t.hpp new file mode 100644 index 0000000..17bac67 --- /dev/null +++ b/support/include/utils/db/sqlite/db_where_t.hpp @@ -0,0 +1,224 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_WHERE_T_HPP_ +#define MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_WHERE_T_HPP_ +#if defined(PROJECT_ENABLE_SQLITE) + +#include "utils/db/sqlite/db_common.hpp" + +namespace monitarr::utils::db::sqlite { +template struct where_data_t final { + w_t base; + std::map> actions; + std::vector values; +}; + +template +struct db_next_t final { + std::size_t action_idx{}; + std::shared_ptr ctx; + std::string action; + + using group_func_t = std::function; + + [[nodiscard]] auto where(std::string column_name) -> cn_t { + return w_t{action_idx, ctx}.where(column_name); + } + + [[nodiscard]] auto dump() const -> std::string { return op_t{ctx}.dump(); } + + [[nodiscard]] auto dump(std::int32_t &idx) const -> std::string { + return ctx->where_data->base.dump(idx); + } + + [[nodiscard]] auto go() const -> auto { return op_t{ctx}.go(); } + + [[nodiscard]] auto group(group_func_t func) -> wn_t { + return w_t{action_idx, ctx}.group(std::move(func)); + } + + [[nodiscard]] auto op() -> op_t { + return op_t{ + ctx, + }; + } +}; + +template +struct db_where_next_t final { + std::size_t action_idx{}; + std::shared_ptr ctx; + + using n_t = db_next_t; + + [[nodiscard]] auto and_() -> n_t { + n_t next{ + action_idx, + ctx, + "AND", + }; + + ctx->where_data->actions[action_idx].emplace_back(next); + return next; + } + + [[nodiscard]] auto dump() const -> std::string { return op_t{ctx}.dump(); } + + [[nodiscard]] auto dump(std::int32_t &idx) const -> std::string { + return ctx->where_data->base.dump(idx); + } + + [[nodiscard]] auto go() const -> auto { return op_t{ctx}.go(); } + + [[nodiscard]] auto op() -> op_t { + return op_t{ + ctx, + }; + } + + [[nodiscard]] auto or_() -> n_t { + n_t next{ + action_idx, + ctx, + "OR", + }; + + ctx->where_data->actions[action_idx].emplace_back(next); + return next; + } +}; + +template +struct db_comp_next_t final { + std::size_t action_idx{}; + std::shared_ptr ctx; + std::string column_name; + + using wn_t = db_where_next_t; + + [[nodiscard]] auto create(std::string operation, db_types_t value) { + ctx->where_data->actions[action_idx].emplace_back(db_comp_data_t{ + column_name, + operation, + }); + + ctx->where_data->values.push_back(value); + + return wn_t{ + action_idx, + ctx, + }; + } + + auto equals(db_types_t value) -> wn_t { return create("=", value); }; + + auto gt(db_types_t value) -> wn_t { return create(">", value); } + + auto gte(db_types_t value) -> wn_t { return create(">=", value); } + + auto like(db_types_t value) -> wn_t { return create("LIKE", value); } + + auto lt(db_types_t value) -> wn_t { return create("<", value); } + + auto lte(db_types_t value) -> wn_t { return create("<=", value); } + + auto not_equals(db_types_t value) -> wn_t { return create("!=", value); }; +}; + +template struct db_where_t final { + std::size_t action_idx{0U}; + std::shared_ptr ctx; + + using cn_t = db_comp_next_t; + using wn_t = db_where_next_t; + using n_t = db_next_t; + + using group_func_t = std::function; + + using action_t = std::variant; + + [[nodiscard]] static auto dump(std::int32_t &idx, + auto &&actions) -> std::string { + std::stringstream stream; + + for (auto &&action : actions) { + std::visit(overloaded{ + [&idx, &stream](const db_comp_data_t &comp) { + stream << '"' << comp.column_name << '"' << comp.op_type + << '?' + std::to_string(++idx); + }, + [&idx, &stream](const n_t &next) { + stream << ' ' << next.action << ' '; + }, + [&idx, &stream](const db_where_t &where) { + stream << '(' << dump(idx, where.get_actions()) << ')'; + }, + }, + action); + } + + return stream.str(); + } + + [[nodiscard]] auto dump() const -> std::string { return op_t{ctx}.dump(); } + + [[nodiscard]] auto dump(std::int32_t &idx) const -> std::string { + return dump(idx, ctx->where_data->actions[action_idx]); + } + + [[nodiscard]] auto get_actions() -> auto & { + return ctx->where_data->actions[action_idx]; + } + + [[nodiscard]] auto get_actions() const -> const auto & { + return ctx->where_data->actions[action_idx]; + } + + [[nodiscard]] auto group(group_func_t func) -> wn_t { + ctx->where_data->actions[action_idx]; + + db_where_t where{ctx->where_data->actions.size(), ctx}; + func(where); + + ctx->where_data->actions[action_idx].emplace_back(where); + + return wn_t{ + action_idx, + ctx, + }; + } + + [[nodiscard]] auto where(std::string column_name) -> cn_t { + ctx->where_data->actions[action_idx]; + + return cn_t{ + action_idx, + ctx, + column_name, + }; + } +}; +} // namespace monitarr::utils::db::sqlite + +#endif // defined(PROJECT_ENABLE_SQLITE) +#endif // MONITARR_INCLUDE_UTILS_DB_SQLITE_DB_WHERE_T_HPP_ diff --git a/support/include/utils/encrypting_reader.hpp b/support/include/utils/encrypting_reader.hpp new file mode 100644 index 0000000..0ed2967 --- /dev/null +++ b/support/include/utils/encrypting_reader.hpp @@ -0,0 +1,150 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_ENCRYPTING_READER_HPP_ +#define MONITARR_INCLUDE_UTILS_ENCRYPTING_READER_HPP_ +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + +#include "utils/config.hpp" + +#include "utils/hash.hpp" +#include "utils/types/file/i_file.hpp" + +namespace monitarr::utils::encryption { +class encrypting_reader final { +public: + encrypting_reader(std::string_view file_name, std::string_view source_path, + stop_type_callback stop_requested_cb, + std::string_view token, + std::optional relative_parent_path, + std::size_t error_return = 0U); + + encrypting_reader(std::string_view encrypted_file_path, + std::string_view source_path, + stop_type_callback stop_requested_cb, + std::string_view token, std::size_t error_return = 0U); + + encrypting_reader( + std::string_view encrypted_file_path, std::string_view source_path, + stop_type_callback stop_requested_cb, std::string_view token, + std::vector> + iv_list, + std::size_t error_return = 0U); + + encrypting_reader(const encrypting_reader &reader); + encrypting_reader(encrypting_reader &&) = delete; + + auto operator=(const encrypting_reader &) -> encrypting_reader & = delete; + auto operator=(encrypting_reader &&) -> encrypting_reader & = delete; + + ~encrypting_reader() noexcept = default; + +public: + using iostream = std::basic_iostream>; + using streambuf = std::basic_streambuf>; + +private: + utils::encryption::hash_256_t key_; + stop_type_callback stop_requested_cb_; + size_t error_return_; + std::unique_ptr source_file_; + +private: + std::unordered_map chunk_buffers_; + std::string encrypted_file_name_; + std::string encrypted_file_path_; + std::vector< + std::array> + iv_list_; + std::size_t last_data_chunk_{}; + std::size_t last_data_chunk_size_{}; + std::uint64_t read_offset_{}; + std::uint64_t total_size_{}; + +private: + static const std::size_t header_size_; + static const std::size_t data_chunk_size_; + static const std::size_t encrypted_chunk_size_; + +private: + auto reader_function(char *buffer, size_t size, size_t nitems) -> size_t; + +public: + [[nodiscard]] static auto calculate_decrypted_size(std::uint64_t total_size) + -> std::uint64_t; + + [[nodiscard]] static auto + calculate_encrypted_size(std::string_view source_path) -> std::uint64_t; + + [[nodiscard]] auto create_iostream() const -> std::shared_ptr; + + [[nodiscard]] static constexpr auto get_encrypted_chunk_size() + -> std::size_t { + return encrypted_chunk_size_; + } + + [[nodiscard]] static constexpr auto get_data_chunk_size() -> std::size_t { + return data_chunk_size_; + } + + [[nodiscard]] auto get_encrypted_file_name() const -> std::string { + return encrypted_file_name_; + } + + [[nodiscard]] auto get_encrypted_file_path() const -> std::string { + return encrypted_file_path_; + } + + [[nodiscard]] auto get_error_return() const -> std::size_t { + return error_return_; + } + + [[nodiscard]] static constexpr auto get_header_size() -> std::size_t { + return header_size_; + } + + [[nodiscard]] auto get_iv_list() -> std::vector< + std::array> { + return iv_list_; + } + + [[nodiscard]] auto get_stop_requested() const -> bool { + return stop_requested_cb_(); + } + + [[nodiscard]] auto get_total_size() const -> std::uint64_t { + return total_size_; + } + + [[nodiscard]] static auto reader_function(char *buffer, size_t size, + size_t nitems, void *instream) + -> size_t { + return reinterpret_cast(instream)->reader_function( + buffer, size, nitems); + } + + void set_read_position(std::uint64_t position) { read_offset_ = position; } +}; +} // namespace monitarr::utils::encryption + +#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +#endif // MONITARR_INCLUDE_UTILS_ENCRYPTING_READER_HPP_ diff --git a/support/include/utils/encryption.hpp b/support/include/utils/encryption.hpp new file mode 100644 index 0000000..8fb28f9 --- /dev/null +++ b/support/include/utils/encryption.hpp @@ -0,0 +1,224 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_ENCRYPTION_HPP_ +#define MONITARR_INCLUDE_UTILS_ENCRYPTION_HPP_ +#if defined(PROJECT_ENABLE_LIBSODIUM) + +#include "utils/config.hpp" + +#include "utils/error.hpp" +#include "utils/hash.hpp" + +namespace monitarr::utils::encryption { +inline constexpr const std::uint32_t encryption_header_size{ + crypto_aead_xchacha20poly1305_IETF_NPUBBYTES + + crypto_aead_xchacha20poly1305_IETF_ABYTES, +}; + +template +inline auto generate_key( + std::string_view password, + std::function hasher = + default_create_hash()) -> hash_t; + +template +inline auto generate_key( + std::wstring_view password, + std::function hasher = + default_create_hash()) -> hash_t; + +#if defined(PROJECT_ENABLE_BOOST) +[[nodiscard]] auto decrypt_file_name(std::string_view encryption_token, + std::string &file_name) -> bool; + +[[nodiscard]] auto decrypt_file_path(std::string_view encryption_token, + std::string &file_path) -> bool; + +template +[[nodiscard]] inline auto decrypt_data(const std::array &key, + const unsigned char *buffer, + std::size_t buffer_size, + result_t &res) -> bool { + if (buffer_size > encryption_header_size) { + const std::uint32_t size = + boost::endian::native_to_big(static_cast(buffer_size)); + res.resize(buffer_size - encryption_header_size); + return crypto_aead_xchacha20poly1305_ietf_decrypt_detached( + reinterpret_cast(res.data()), nullptr, + &buffer[encryption_header_size], res.size(), + &buffer[crypto_aead_xchacha20poly1305_IETF_NPUBBYTES], + reinterpret_cast(&size), sizeof(size), + buffer, key.data()) == 0; + } + + return false; +} + +template +[[nodiscard]] inline auto decrypt_data(const std::array &key, + const buffer_t &buf, + result_t &res) -> bool { + return decrypt_data( + key, reinterpret_cast(buf.data()), buf.size(), + res); +} + +template +[[nodiscard]] inline auto decrypt_data( + std::string_view password, const buffer_t &buf, result_t &res, + std::function hasher = + default_create_hash()) -> bool { + return decrypt_data(generate_key(password, hasher), buf, + res); +} + +template +[[nodiscard]] inline auto decrypt_data( + std::string_view password, const unsigned char *buffer, + std::size_t buffer_size, result_t &res, + std::function hasher = + default_create_hash()) -> bool { + return decrypt_data(generate_key(password, hasher), buffer, + buffer_size, res); +} + +template +inline void +encrypt_data(const std::array &iv, + const std::array &key, + const unsigned char *buffer, std::size_t buffer_size, + result_t &res) { + MONITARR_USES_FUNCTION_NAME(); + + std::array mac{}; + + const std::uint32_t size = boost::endian::native_to_big( + static_cast(buffer_size + encryption_header_size)); + + res.resize(buffer_size + encryption_header_size); + + unsigned long long mac_length{}; + if (crypto_aead_xchacha20poly1305_ietf_encrypt_detached( + reinterpret_cast(&res[encryption_header_size]), + mac.data(), &mac_length, buffer, buffer_size, + reinterpret_cast(&size), sizeof(size), nullptr, + iv.data(), key.data()) != 0) { + throw monitarr::utils::error::create_exception(function_name, + { + "encryption failed", + }); + } + + std::memcpy(res.data(), iv.data(), iv.size()); + std::memcpy(&res[iv.size()], mac.data(), mac.size()); +} + +template +inline void encrypt_data(const std::array &key, + const unsigned char *buffer, std::size_t buffer_size, + result_t &res) { + std::array iv{}; + randombytes_buf(iv.data(), iv.size()); + + encrypt_data(iv, key, buffer, buffer_size, res); +} + +template +inline void encrypt_data( + std::string_view password, const unsigned char *buffer, + std::size_t buffer_size, result_t &res, + std::function hasher = + default_create_hash()) { + encrypt_data(generate_key(password, hasher), buffer, buffer_size, + res); +} + +template +inline void encrypt_data( + std::string_view password, const buffer_t &buf, result_t &res, + std::function hasher = + default_create_hash()) { + encrypt_data(generate_key(password, hasher), + reinterpret_cast(buf.data()), + buf.size(), res); +} + +template +inline void encrypt_data(const std::array &key, + const buffer_t &buf, result_t &res) { + encrypt_data(key, + reinterpret_cast(buf.data()), + buf.size(), res); +} + +template +inline void +encrypt_data(const std::array &iv, + const std::array &key, const buffer_t &buf, + result_t &res) { + encrypt_data(iv, key, + reinterpret_cast(buf.data()), + buf.size(), res); +} + +using reader_func_t = + std::function; + +[[nodiscard]] auto +read_encrypted_range(const http_range &range, + const utils::encryption::hash_256_t &key, + reader_func_t reader_func, std::uint64_t total_size, + data_buffer &data) -> bool; + +[[nodiscard]] auto read_encrypted_range( + const http_range &range, const utils::encryption::hash_256_t &key, + reader_func_t reader_func, std::uint64_t total_size, unsigned char *data, + std::size_t size, std::size_t &bytes_read) -> bool; +#endif // defined(PROJECT_ENABLE_BOOST) + +template +inline auto generate_key( + std::string_view password, + std::function hasher) + -> hash_t { + return hasher(reinterpret_cast(password.data()), + password.size()); +} + +template +inline auto generate_key( + std::wstring_view password, + std::function hasher) + -> hash_t { + return hasher(reinterpret_cast(password.data()), + password.size() * sizeof(wchar_t)); +} +} // namespace monitarr::utils::encryption + +#endif // defined(PROJECT_ENABLE_LIBSODIUM) +#endif // MONITARR_INCLUDE_UTILS_ENCRYPTION_HPP_ diff --git a/support/include/utils/error.hpp b/support/include/utils/error.hpp new file mode 100644 index 0000000..4c577a3 --- /dev/null +++ b/support/include/utils/error.hpp @@ -0,0 +1,104 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_ERROR_HPP_ +#define MONITARR_INCLUDE_UTILS_ERROR_HPP_ + +#include "utils/config.hpp" + +namespace monitarr::utils::error { +[[nodiscard]] auto +create_error_message(std::vector items) -> std::string; + +[[nodiscard]] auto +create_exception(std::string_view function_name, + std::vector items) -> std::runtime_error; + +struct i_exception_handler { + virtual ~i_exception_handler() {} + + i_exception_handler(const i_exception_handler &) noexcept = delete; + i_exception_handler(i_exception_handler &&) noexcept = delete; + auto operator=(const i_exception_handler &) noexcept = delete; + auto operator=(i_exception_handler &&) noexcept = delete; + + virtual void handle_error(std::string_view function_name, + std::string_view msg) const = 0; + + virtual void handle_exception(std::string_view function_name) const = 0; + + virtual void handle_exception(std::string_view function_name, + const std::exception &ex) const = 0; + +protected: + i_exception_handler() = default; +}; + +struct iostream_exception_handler final : i_exception_handler { + void handle_error(std::string_view function_name, + std::string_view msg) const override { + std::cerr << create_error_message({ + function_name, + msg, + }) + << std::endl; + } + + void handle_exception(std::string_view function_name) const override { + std::cerr << create_error_message({ + function_name, + "exception", + "unknown", + }) + << std::endl; + } + + void handle_exception(std::string_view function_name, + const std::exception &ex) const override { + std::cerr << create_error_message({ + function_name, + "exception", + (ex.what() == nullptr ? "unknown" : ex.what()), + }) + << std::endl; + } +}; +inline const iostream_exception_handler default_exception_handler{}; + +extern std::atomic exception_handler; + +#if defined(PROJECT_ENABLE_TESTING) +[[nodiscard]] inline auto +get_exception_handler() -> const i_exception_handler * { + return exception_handler; +} +#endif // defined(PROJECT_ENABLE_TESTING) + +void handle_error(std::string_view function_name, std::string_view msg); + +void handle_exception(std::string_view function_name); + +void handle_exception(std::string_view function_name, const std::exception &ex); + +void set_exception_handler(const i_exception_handler *handler); +} // namespace monitarr::utils::error + +#endif // MONITARR_INCLUDE_UTILS_ERROR_HPP_ diff --git a/support/include/utils/file.hpp b/support/include/utils/file.hpp new file mode 100644 index 0000000..b1a8159 --- /dev/null +++ b/support/include/utils/file.hpp @@ -0,0 +1,182 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_FILE_HPP_ +#define MONITARR_INCLUDE_UTILS_FILE_HPP_ + +#include "utils/config.hpp" + +#include "utils/path.hpp" +#include "utils/string.hpp" +#include "utils/types/file/i_directory.hpp" +#include "utils/types/file/i_file.hpp" +#include "utils/types/file/i_fs_item.hpp" + +namespace monitarr::utils::file { +[[nodiscard]] auto change_to_process_directory() -> bool; + +// INFO: has test +[[nodiscard]] auto create_temp_name(std::string_view file_part) -> std::string; + +// INFO: has test +[[nodiscard]] auto +create_temp_name(std::wstring_view file_part) -> std::wstring; + +// INFO: has test +[[nodiscard]] inline auto +directory_exists_in_path(std::string_view path, + std::string_view sub_directory) -> bool; + +// INFO: has test +[[nodiscard]] inline auto +directory_exists_in_path(std::wstring_view path, + std::wstring_view sub_directory) -> bool; + +// INFO: has test +[[nodiscard]] inline auto +file_exists_in_path(std::string_view path, std::string_view file_name) -> bool; + +// INFO: has test +[[nodiscard]] inline auto +file_exists_in_path(std::wstring_view path, + std::wstring_view file_name) -> bool; + +// INFO: has test +[[nodiscard]] auto +get_free_drive_space(std::string_view path) -> std::optional; + +// INFO: has test +[[nodiscard]] auto +get_free_drive_space(std::wstring_view path) -> std::optional; + +// INFO: has test +[[nodiscard]] auto get_time(std::string_view path, + time_type type) -> std::optional; + +// INFO: has test +[[nodiscard]] auto get_time(std::wstring_view path, + time_type type) -> std::optional; + +// INFO: has test +[[nodiscard]] auto +get_times(std::string_view path) -> std::optional; + +// INFO: has test +[[nodiscard]] auto +get_times(std::wstring_view path) -> std::optional; + +// INFO: has test +[[nodiscard]] auto +get_total_drive_space(std::string_view path) -> std::optional; + +// INFO: has test +[[nodiscard]] auto +get_total_drive_space(std::wstring_view path) -> std::optional; + +#if defined(PROJECT_ENABLE_LIBDSM) +[[nodiscard]] auto +smb_create_and_validate_relative_path(std::string_view smb_path, + std::string_view rel_path) -> std::string; + +// INFO: has test +[[nodiscard]] auto +smb_create_relative_path(std::string_view smb_path) -> std::string; + +// INFO: has test +[[nodiscard]] auto +smb_create_search_path(std::string_view smb_path) -> std::string; + +// INFO: has test +[[nodiscard]] auto +smb_create_smb_path(std::string_view smb_path, + std::string_view rel_path) -> std::string; + +[[nodiscard]] auto +smb_get_parent_path(std::string_view smb_path) -> std::string; + +[[nodiscard]] auto smb_get_root_path(std::string_view smb_path) -> std::string; + +[[nodiscard]] auto smb_get_unc_path(std::string_view smb_path) -> std::string; + +[[nodiscard]] auto smb_get_uri_path(std::string_view smb_path) -> std::string; + +[[nodiscard]] auto smb_get_uri_path(std::string_view smb_path, + std::string_view user, + std::string_view password) -> std::string; + +// INFO: has test +[[nodiscard]] auto smb_parent_is_same(std::string_view smb_path1, + std::string_view smb_path2) -> bool; + +#define SMB_MOD_RW2 \ + (SMB_MOD_READ | SMB_MOD_WRITE | SMB_MOD_READ_EXT | SMB_MOD_WRITE_EXT | \ + SMB_MOD_READ_ATTR | SMB_MOD_WRITE_ATTR | SMB_MOD_READ_CTL) +#endif // defined(PROJECT_ENABLE_LIBDSM) + +#if defined(PROJECT_ENABLE_JSON) +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +// INFO: has test +[[nodiscard]] auto +read_json_file(std::string_view path, nlohmann::json &data, + std::optional password = std::nullopt) -> bool; + +// INFO: has test +[[nodiscard]] auto read_json_file( + std::wstring_view path, nlohmann::json &data, + std::optional password = std::nullopt) -> bool; + +// INFO: has test +[[nodiscard]] auto write_json_file( + std::string_view path, const nlohmann::json &data, + std::optional password = std::nullopt) -> bool; + +// INFO: has test +[[nodiscard]] auto write_json_file( + std::wstring_view path, const nlohmann::json &data, + std::optional password = std::nullopt) -> bool; +#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +// INFO: has test +[[nodiscard]] auto read_json_file(std::string_view path, + nlohmann::json &data) -> bool; + +// INFO: has test +[[nodiscard]] auto read_json_file(std::wstring_view path, + nlohmann::json &data) -> bool; + +// INFO: has test +[[nodiscard]] auto write_json_file(std::string_view path, + const nlohmann::json &data) -> bool; + +// INFO: has test +[[nodiscard]] auto write_json_file(std::wstring_view path, + const nlohmann::json &data) -> bool; +#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +#endif // defined(PROJECT_ENABLE_JSON) +} // namespace monitarr::utils::file + +#endif // MONITARR_INCLUDE_UTILS_FILE_HPP_ + +#include "utils/file_directory.hpp" +#include "utils/file_enc_file.hpp" +#include "utils/file_file.hpp" +#include "utils/file_smb_directory.hpp" +#include "utils/file_smb_file.hpp" +#include "utils/file_thread_file.hpp" diff --git a/support/include/utils/file_directory.hpp b/support/include/utils/file_directory.hpp new file mode 100644 index 0000000..95c3b88 --- /dev/null +++ b/support/include/utils/file_directory.hpp @@ -0,0 +1,124 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_FILE_DIRECTORY_HPP_ +#define MONITARR_INCLUDE_UTILS_FILE_DIRECTORY_HPP_ + +#include "utils/file.hpp" + +namespace monitarr::utils::file { +class directory final : public i_directory { +public: + using directory_t = std::unique_ptr; + + directory() noexcept = default; + + directory(std::string_view path, stop_type *stop_requested = nullptr) + : path_(utils::path::absolute(path)), stop_requested_(stop_requested) {} + + directory(std::wstring_view path, stop_type *stop_requested = nullptr) + : path_(utils::path::absolute(utils::string::to_utf8(path))), + stop_requested_(stop_requested) {} + + directory(const directory &) noexcept = delete; + + directory(directory &&move_dir) noexcept = default; + + ~directory() override = default; + +private: + std::string path_; + stop_type *stop_requested_{nullptr}; + +public: + [[nodiscard]] auto copy_to(std::string_view new_path, + bool overwrite) const -> bool override; + + [[nodiscard]] auto + count(bool recursive = false) const -> std::uint64_t override; + + [[nodiscard]] auto + create_directory(std::string_view path = "") const -> fs_directory_t override; + + [[nodiscard]] auto create_file(std::string_view file_name, + bool read_only) const -> fs_file_t override; + + [[nodiscard]] auto exists() const -> bool override; + + [[nodiscard]] auto + get_directory(std::string_view path) const -> fs_directory_t override; + + [[nodiscard]] auto + get_directories() const -> std::vector override; + + [[nodiscard]] auto + get_file(std::string_view path) const -> fs_file_t override; + + [[nodiscard]] auto get_files() const -> std::vector override; + + [[nodiscard]] auto get_items() const -> std::vector override; + + [[nodiscard]] auto get_path() const -> std::string override { return path_; } + + [[nodiscard]] auto is_stop_requested() const -> bool override; + + [[nodiscard]] auto is_symlink() const -> bool override; + + [[nodiscard]] auto move_to(std::string_view new_path) -> bool override; + + [[nodiscard]] auto remove() -> bool override; + + [[nodiscard]] auto remove_recursively() -> bool override; + + [[nodiscard]] auto + size(bool recursive = false) const -> std::uint64_t override; + +public: + auto operator=(const directory &) noexcept -> directory & = delete; + + auto operator=(directory &&move_dir) noexcept -> directory & = default; + + [[nodiscard]] operator bool() const override { return exists(); } +}; + +// INFO: has test +template +[[nodiscard]] inline auto directory_exists_in_path_t( + std::basic_string_view path, + std::basic_string_view sub_directory) + -> bool { + return directory(utils::path::combine(path, {sub_directory})).exists(); +} + +// INFO: has test +inline auto directory_exists_in_path(std::string_view path, + std::string_view sub_directory) -> bool { + return directory_exists_in_path_t(path, sub_directory); +} + +// INFO: has test +inline auto directory_exists_in_path(std::wstring_view path, + std::wstring_view sub_directory) -> bool { + return directory_exists_in_path_t(path, sub_directory); +} +} // namespace monitarr::utils::file + +#endif // MONITARR_INCLUDE_UTILS_FILE_DIRECTORY_HPP_ diff --git a/support/include/utils/file_enc_file.hpp b/support/include/utils/file_enc_file.hpp new file mode 100644 index 0000000..ac04041 --- /dev/null +++ b/support/include/utils/file_enc_file.hpp @@ -0,0 +1,124 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_FILE_ENC_FILE_HPP_ +#define MONITARR_INCLUDE_UTILS_FILE_ENC_FILE_HPP_ +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + +#include "utils/file.hpp" + +namespace monitarr::utils::file { +class enc_file final : public i_file { +public: + [[nodiscard]] static auto attach_file(fs_file_t file) -> fs_file_t; + +public: + enc_file() noexcept = default; + +protected: + enc_file(fs_file_t file); + +public: + enc_file(const enc_file &) = delete; + + enc_file(enc_file &&move_file) noexcept : file_(std::move(move_file.file_)) {} + + ~enc_file() override { close(); } + +private: + fs_file_t file_; + std::string encryption_token_; + +public: + void close() override; + + [[nodiscard]] auto copy_to(std::string_view new_path, bool overwrite) const + -> bool override; + + [[nodiscard]] auto exists() const -> bool override { return file_->exists(); } + + void flush() const override; + + [[nodiscard]] auto get_handle() const -> native_handle override { + return file_->get_handle(); + } + + [[nodiscard]] auto get_path() const -> std::string override { + return file_->get_path(); + } + + [[nodiscard]] auto get_read_buffer_size() const -> std::uint32_t override { + return file_->get_read_buffer_size(); + } + + [[nodiscard]] auto get_time(time_type type) const + -> std::optional override { + return file_->get_time(type); + } + + [[nodiscard]] auto is_read_only() const -> bool override { + return file_->is_read_only(); + } + + [[nodiscard]] auto is_symlink() const -> bool override { + return file_->is_symlink(); + } + + [[nodiscard]] auto move_to(std::string_view new_path) -> bool override; + + [[nodiscard]] auto read(unsigned char *data, std::size_t to_read, + std::uint64_t offset, + std::size_t *total_read = nullptr) -> bool override; + + [[nodiscard]] auto remove() -> bool override; + + auto set_read_buffer_size(std::uint32_t size) -> std::uint32_t override { + return file_->set_read_buffer_size(size); + } + + [[nodiscard]] auto size() const -> std::optional override; + + [[nodiscard]] auto truncate(std::size_t size) -> bool override; + + [[nodiscard]] auto write(const unsigned char *data, std::size_t to_write, + std::size_t offset, + std::size_t *total_written = nullptr) + -> bool override; + +public: + [[nodiscard]] operator bool() const override { + return static_cast(*file_); + } + + auto operator=(const enc_file &) noexcept -> enc_file & = delete; + + auto operator=(enc_file &&move_file) noexcept -> enc_file & { + if (&move_file != this) { + file_ = std::move(move_file.file_); + } + + return *this; + } +}; +} // namespace monitarr::utils::file + +#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +#endif // MONITARR_INCLUDE_UTILS_FILE_ENC_FILE_HPP_ diff --git a/support/include/utils/file_file.hpp b/support/include/utils/file_file.hpp new file mode 100644 index 0000000..bae3630 --- /dev/null +++ b/support/include/utils/file_file.hpp @@ -0,0 +1,176 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_FILE_FILE_HPP_ +#define MONITARR_INCLUDE_UTILS_FILE_FILE_HPP_ + +#include "utils/file.hpp" + +namespace monitarr::utils::file { +class file final : public i_file { +public: + // [[nodiscard]] static auto + // attach_file(native_handle handle, + // bool read_only = false) -> fs_file_t; + + // INFO: has test + [[nodiscard]] static auto open_file(std::string_view path, + bool read_only = false) -> fs_file_t; + + [[nodiscard]] static auto open_file(std::wstring_view path, + bool read_only = false) -> fs_file_t { + return open_file(utils::string::to_utf8(path), read_only); + } + + // INFO: has test + [[nodiscard]] static auto + open_or_create_file(std::string_view path, + bool read_only = false) -> fs_file_t; + + [[nodiscard]] static auto + open_or_create_file(std::wstring_view path, + bool read_only = false) -> fs_file_t { + return open_or_create_file(utils::string::to_utf8(path), read_only); + } + +public: + file() noexcept = default; + + file(std::string_view path) + : file_(nullptr), path_(utils::path::absolute(path)) {} + + file(std::wstring_view path) + : file_(nullptr), + path_(utils::path::absolute(utils::string::to_utf8(path))) {} + +private: + file(file_t file_ptr, std::string_view path, bool read_only) + : file_(std::move(file_ptr)), path_(path), read_only_(read_only) {} + +public: + file(const file &) = delete; + + file(file &&move_file) noexcept + : file_(std::move(move_file.file_)), + path_(std::move(move_file.path_)), + read_only_(move_file.read_only_) {} + + ~file() override { close(); } + +private: + file_t file_; + std::string path_; + bool read_only_{false}; + +private: + std::atomic_uint32_t read_buffer_size{65536U}; + +private: + void open(); + +public: + void close() override; + + [[nodiscard]] auto copy_to(std::string_view new_path, + bool overwrite) const -> bool override; + + [[nodiscard]] auto exists() const -> bool override; + + void flush() const override; + + [[nodiscard]] auto get_handle() const -> native_handle override; + + [[nodiscard]] auto get_path() const -> std::string override { return path_; } + + [[nodiscard]] auto get_read_buffer_size() const -> std::uint32_t override { + return read_buffer_size; + } + + [[nodiscard]] auto is_read_only() const -> bool override { + return read_only_; + } + + [[nodiscard]] auto is_symlink() const -> bool override; + + [[nodiscard]] auto move_to(std::string_view new_path) -> bool override; + + [[nodiscard]] auto read(unsigned char *data, std::size_t to_read, + std::uint64_t offset, + std::size_t *total_read = nullptr) -> bool override; + + [[nodiscard]] auto remove() -> bool override; + + auto set_read_buffer_size(std::uint32_t size) -> std::uint32_t override { + read_buffer_size = size; + return read_buffer_size; + } + +#if defined(PROJECT_ENABLE_LIBSODIUM) + [[nodiscard]] auto sha256() -> std::optional; + +#endif // defined(PROJECT_ENABLE_LIBSODIUM) + + [[nodiscard]] auto size() const -> std::optional override; + + [[nodiscard]] auto truncate(std::size_t size) -> bool override; + + [[nodiscard]] auto + write(const unsigned char *data, std::size_t to_write, std::size_t offset, + std::size_t *total_written = nullptr) -> bool override; + +public: + auto operator=(const file &) noexcept -> file & = delete; + + auto operator=(file &&move_file) noexcept -> file & { + if (&move_file != this) { + file_ = std::move(move_file.file_); + path_ = std::move(move_file.path_); + read_only_ = move_file.read_only_; + } + + return *this; + } + + [[nodiscard]] operator bool() const override { return file_ != nullptr; } +}; + +// INFO: has test +template +[[nodiscard]] inline auto file_exists_in_path_t( + std::basic_string_view path, + std::basic_string_view file_name) -> bool { + return file(utils::path::combine(path, {file_name})).exists(); +} + +// INFO: has test +inline auto file_exists_in_path(std::string_view path, + std::string_view file_name) -> bool { + return file_exists_in_path_t(path, file_name); +} + +// INFO: has test +inline auto file_exists_in_path(std::wstring_view path, + std::wstring_view file_name) -> bool { + return file_exists_in_path_t(path, file_name); +} +} // namespace monitarr::utils::file + +#endif // MONITARR_INCLUDE_UTILS_FILE_FILE_HPP_ diff --git a/support/include/utils/file_smb_directory.hpp b/support/include/utils/file_smb_directory.hpp new file mode 100644 index 0000000..2478589 --- /dev/null +++ b/support/include/utils/file_smb_directory.hpp @@ -0,0 +1,140 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_FILE_SMB_DIRECTORY_HPP_ +#define MONITARR_INCLUDE_UTILS_FILE_SMB_DIRECTORY_HPP_ +#if defined(PROJECT_ENABLE_LIBDSM) + +#include "utils/file.hpp" + +namespace monitarr::utils::file { +class smb_directory final : public i_directory { +public: + using smb_directory_t = std::unique_ptr; + + [[nodiscard]] static auto + open(std::string_view host, std::string_view user, std::string_view password, + std::string_view path, + stop_type *stop_requested = nullptr) -> smb_directory_t; + + [[nodiscard]] static auto + open(std::wstring_view host, std::wstring_view user, + std::wstring_view password, std::wstring_view path, + stop_type *stop_requested = nullptr) -> smb_directory_t; + +public: + smb_directory() noexcept = default; + + smb_directory(const smb_directory &) noexcept = delete; + + smb_directory(smb_directory &&) noexcept = default; + + ~smb_directory() override = default; + +private: + smb_directory(std::string path, smb_session_t session, + std::string_view share_name, smb_tid tid, + stop_type *stop_requested) + : path_(std::move(path)), + session_(std::move(session)), + share_name_(share_name), + tid_(tid), + stop_requested_(stop_requested) {} + +private: + std::string path_{}; + smb_session_t session_{}; + std::string share_name_{}; + smb_tid tid_{}; + stop_type *stop_requested_{nullptr}; + +public: + [[nodiscard]] auto + count(bool recursive = false) const -> std::uint64_t override; + + [[nodiscard]] auto copy_to(std::string_view new_path, + bool overwrite) const -> bool override; + + [[nodiscard]] auto + create_directory(std::string_view path = "") const -> fs_directory_t override; + + [[nodiscard]] auto create_file(std::string_view file_name, + bool read_only) const -> fs_file_t override; + + [[nodiscard]] auto exists() const -> bool override; + + [[nodiscard]] auto + get_directory(std::string_view path) const -> fs_directory_t override; + + [[nodiscard]] auto + get_directories() const -> std::vector override; + + [[nodiscard]] auto + get_file(std::string_view path) const -> fs_file_t override; + + [[nodiscard]] auto get_files() const -> std::vector override; + + [[nodiscard]] auto get_items() const -> std::vector override; + + [[nodiscard]] auto get_path() const -> std::string override { return path_; } + + [[nodiscard]] auto + get_time(time_type type) const -> std::optional override; + + [[nodiscard]] auto get_unc_path() const -> std::string { + return smb_get_unc_path(path_); + } + + [[nodiscard]] auto get_uri_path() const -> std::string { + return smb_get_uri_path(path_); + } + + [[nodiscard]] auto + get_uri_path(std::string_view user, + std::string_view password) const -> std::string { + return smb_get_uri_path(path_, user, password); + } + + [[nodiscard]] auto is_stop_requested() const -> bool override; + + [[nodiscard]] auto is_symlink() const -> bool override; + + [[nodiscard]] auto move_to(std::string_view new_path) -> bool override; + + [[nodiscard]] auto remove() -> bool override; + + [[nodiscard]] auto remove_recursively() -> bool override; + + [[nodiscard]] auto + size(bool recursive = false) const -> std::uint64_t override; + +public: + auto operator=(const smb_directory &) noexcept -> smb_directory & = delete; + + auto + operator=(smb_directory &&move_dir) noexcept -> smb_directory & = default; + + [[nodiscard]] operator bool() const override { return session_ != nullptr; } +}; +} // namespace monitarr::utils::file + +#endif // defined(PROJECT_ENABLE_LIBDSM) +#endif // MONITARR_INCLUDE_UTILS_FILE_SMB_DIRECTORY_HPP_ diff --git a/support/include/utils/file_smb_file.hpp b/support/include/utils/file_smb_file.hpp new file mode 100644 index 0000000..7ddd528 --- /dev/null +++ b/support/include/utils/file_smb_file.hpp @@ -0,0 +1,157 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_FILE_SMB_FILE_HPP_ +#define MONITARR_INCLUDE_UTILS_FILE_SMB_FILE_HPP_ +#if defined(PROJECT_ENABLE_LIBDSM) + +#include "utils/file.hpp" + +namespace monitarr::utils::file { +class smb_file final : public i_file { +public: + smb_file() = default; + + smb_file(std::optional fd, std::string path, smb_session_t session, + std::string_view share_name, smb_tid tid) + : fd_(std::move(fd)), + path_(std::move(path)), + session_(std::move(session)), + share_name_(share_name), + tid_(tid) {} + + smb_file(const smb_file &) = delete; + + smb_file(smb_file &&f) noexcept + : fd_(std::move(f.fd_)), + path_(std::move(f.path_)), + read_buffer_size(f.get_read_buffer_size()), + read_only_(f.read_only_), + session_(std::move(f.session_)), + share_name_(std::move(f.share_name_)), + tid_(f.tid_) {} + + ~smb_file() override { close(); } + +private: + std::optional fd_; + std::string path_; + std::atomic_uint32_t read_buffer_size{65536U}; + bool read_only_; + smb_session_t session_; + std::string share_name_; + smb_tid tid_; + +public: + void close() override; + + [[nodiscard]] auto copy_to(std::string_view new_path, + bool overwrite) const -> bool override; + + [[nodiscard]] auto exists() const -> bool override; + + void flush() const override; + + [[nodiscard]] auto get_handle() const -> native_handle override { + return INVALID_HANDLE_VALUE; + } + + [[nodiscard]] auto get_path() const -> std::string override { return path_; } + + [[nodiscard]] auto get_read_buffer_size() const -> std::uint32_t override { + return read_buffer_size; + } + + [[nodiscard]] static auto + get_time(smb_session *session, smb_tid tid, std::string path, + time_type type) -> std::optional; + + [[nodiscard]] auto + get_time(time_type type) const -> std::optional override { + return get_time(session_.get(), tid_, path_, type); + } + + [[nodiscard]] auto get_unc_path() const -> std::string { + return smb_get_unc_path(path_); + } + + [[nodiscard]] auto get_uri_path() const -> std::string { + return smb_get_uri_path(path_); + } + + [[nodiscard]] auto + get_uri_path(std::string_view user, + std::string_view password) const -> std::string { + return smb_get_uri_path(path_, user, password); + } + + [[nodiscard]] auto is_read_only() const -> bool override { + return read_only_; + } + + [[nodiscard]] auto is_symlink() const -> bool override; + + [[nodiscard]] auto move_to(std::string_view new_path) -> bool override; + + [[nodiscard]] auto open(bool read_only) -> bool; + + [[nodiscard]] auto read(unsigned char *data, std::size_t to_read, + std::uint64_t offset, + std::size_t *total_read = nullptr) -> bool override; + + [[nodiscard]] auto remove() -> bool override; + + auto set_read_buffer_size(std::uint32_t size) -> std::uint32_t override { + read_buffer_size = size; + return read_buffer_size; + } + + [[nodiscard]] auto size() const -> std::optional override; + + [[nodiscard]] auto truncate(std::size_t size) -> bool override; + + [[nodiscard]] auto + write(const unsigned char *data, std::size_t to_write, std::size_t offset, + std::size_t *total_written = nullptr) -> bool override; + +public: + auto operator=(const smb_file &) noexcept -> smb_file & = delete; + + auto operator=(smb_file &&move_file) noexcept -> smb_file & { + if (this != &move_file) { + fd_ = std::move(move_file.fd_); + path_ = std::move(move_file.path_); + read_buffer_size = move_file.get_read_buffer_size(); + read_only_ = move_file.read_only_; + session_ = std::move(move_file.session_); + share_name_ = std::move(move_file.share_name_); + tid_ = move_file.tid_; + } + + return *this; + } + + [[nodiscard]] operator bool() const override { return fd_.has_value(); } +}; +} // namespace monitarr::utils::file + +#endif // defined(PROJECT_ENABLE_LIBDSM) +#endif // MONITARR_INCLUDE_UTILS_FILE_SMB_FILE_HPP_ diff --git a/support/include/utils/file_thread_file.hpp b/support/include/utils/file_thread_file.hpp new file mode 100644 index 0000000..98d257a --- /dev/null +++ b/support/include/utils/file_thread_file.hpp @@ -0,0 +1,180 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_FILE_THREAD_FILE_HPP_ +#define MONITARR_INCLUDE_UTILS_FILE_THREAD_FILE_HPP_ + +#include "utils/file.hpp" + +namespace monitarr::utils::file { +class thread_file final : public i_file { +public: + // [[nodiscard]] static auto + // attach_file(native_handle handle, + // bool read_only = false) -> fs_file_t; + + [[nodiscard]] static auto attach_file(fs_file_t file) -> fs_file_t; + + [[nodiscard]] static auto open_file(std::string_view path, + bool read_only = false) -> fs_file_t; + + [[nodiscard]] static auto open_file(std::wstring_view path, + bool read_only = false) -> fs_file_t { + return open_file(utils::string::to_utf8(path), read_only); + } + + [[nodiscard]] static auto + open_or_create_file(std::string_view path, + bool read_only = false) -> fs_file_t; + + [[nodiscard]] static auto + open_or_create_file(std::wstring_view path, + bool read_only = false) -> fs_file_t { + return open_or_create_file(utils::string::to_utf8(path), read_only); + } + +public: + thread_file() noexcept = default; + + thread_file(std::string_view path); + + thread_file(std::wstring_view path); + +protected: + thread_file(fs_file_t file); + +public: + thread_file(const thread_file &) = delete; + + thread_file(thread_file &&move_file) noexcept + : file_(std::move(move_file.file_)) {} + + ~thread_file() override; + +private: + using action_t = std::function; + + struct io_item final { + action_t action; + bool complete{false}; + std::unique_ptr mtx{ + std::make_unique(), + }; + mutable std::unique_ptr notify{ + std::make_unique(), + }; + bool success{false}; + + void done(bool result); + + void wait() const; + }; + +private: + fs_file_t file_; + +private: + mutable std::vector> actions_; + mutable std::unique_ptr io_thread_; + mutable std::unique_ptr mtx_{std::make_unique()}; + mutable std::unique_ptr notify_{ + std::make_unique(), + }; + stop_type stop_requested_{false}; + +private: + auto do_io(action_t action) const -> bool; + + void thread_func() const; + +public: + void close() override; + + [[nodiscard]] auto copy_to(std::string_view new_path, + bool overwrite) const -> bool override; + + [[nodiscard]] auto exists() const -> bool override { return file_->exists(); } + + void flush() const override; + + [[nodiscard]] auto get_handle() const -> native_handle override { + return file_->get_handle(); + } + + [[nodiscard]] auto get_path() const -> std::string override { + return file_->get_path(); + } + + [[nodiscard]] auto get_read_buffer_size() const -> std::uint32_t override { + return file_->get_read_buffer_size(); + } + + [[nodiscard]] auto + get_time(time_type type) const -> std::optional override { + return file_->get_time(type); + } + + [[nodiscard]] auto is_read_only() const -> bool override { + return file_->is_read_only(); + } + + [[nodiscard]] auto is_symlink() const -> bool override { + return file_->is_symlink(); + } + + [[nodiscard]] auto move_to(std::string_view new_path) -> bool override; + + [[nodiscard]] auto read(unsigned char *data, std::size_t to_read, + std::uint64_t offset, + std::size_t *total_read = nullptr) -> bool override; + + [[nodiscard]] auto remove() -> bool override; + + auto set_read_buffer_size(std::uint32_t size) -> std::uint32_t override { + return file_->set_read_buffer_size(size); + } + + [[nodiscard]] auto size() const -> std::optional override; + + [[nodiscard]] auto truncate(std::size_t size) -> bool override; + + [[nodiscard]] auto + write(const unsigned char *data, std::size_t to_write, std::size_t offset, + std::size_t *total_written = nullptr) -> bool override; + +public: + [[nodiscard]] operator bool() const override { + return static_cast(*file_); + } + + auto operator=(const thread_file &) noexcept -> thread_file & = delete; + + auto operator=(thread_file &&move_file) noexcept -> thread_file & { + if (&move_file != this) { + file_ = std::move(move_file.file_); + } + + return *this; + } +}; +} // namespace monitarr::utils::file + +#endif // MONITARR_INCLUDE_UTILS_FILE_THREAD_FILE_HPP_ diff --git a/support/include/utils/hash.hpp b/support/include/utils/hash.hpp new file mode 100644 index 0000000..7f02e8c --- /dev/null +++ b/support/include/utils/hash.hpp @@ -0,0 +1,181 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_HASH_HPP_ +#define MONITARR_INCLUDE_UTILS_HASH_HPP_ +#if defined(PROJECT_ENABLE_LIBSODIUM) + +#include "utils/config.hpp" + +#include "utils/error.hpp" + +namespace monitarr::utils::encryption { +using hash_256_t = std::array; +using hash_384_t = std::array; +using hash_512_t = std::array; + +[[nodiscard]] auto create_hash_blake2b_256(std::string_view data) -> hash_256_t; + +[[nodiscard]] auto +create_hash_blake2b_256(std::wstring_view data) -> hash_256_t; + +[[nodiscard]] auto +create_hash_blake2b_256(const data_buffer &data) -> hash_256_t; + +[[nodiscard]] auto create_hash_blake2b_384(std::string_view data) -> hash_384_t; + +[[nodiscard]] auto +create_hash_blake2b_384(std::wstring_view data) -> hash_384_t; + +[[nodiscard]] auto +create_hash_blake2b_384(const data_buffer &data) -> hash_384_t; + +[[nodiscard]] auto +create_hash_blake2b_512(std::wstring_view data) -> hash_512_t; + +[[nodiscard]] auto +create_hash_blake2b_512(const data_buffer &data) -> hash_512_t; + +[[nodiscard]] auto create_hash_blake2b_512(std::string_view data) -> hash_512_t; + +template +[[nodiscard]] auto create_hash_blake2b_t(const unsigned char *data, + std::size_t data_size) -> hash_t; + +[[nodiscard]] auto create_hash_sha256(std::string_view data) -> hash_256_t; + +[[nodiscard]] auto create_hash_sha256(std::wstring_view data) -> hash_256_t; + +[[nodiscard]] auto create_hash_sha256(const data_buffer &data) -> hash_256_t; + +[[nodiscard]] auto create_hash_sha256(const unsigned char *data, + std::size_t data_size) -> hash_256_t; + +[[nodiscard]] auto create_hash_sha512(std::string_view data) -> hash_512_t; + +[[nodiscard]] auto create_hash_sha512(std::wstring_view data) -> hash_512_t; + +[[nodiscard]] auto create_hash_sha512(const data_buffer &data) -> hash_512_t; + +[[nodiscard]] auto create_hash_sha512(const unsigned char *data, + std::size_t data_size) -> hash_512_t; + +template +[[nodiscard]] inline auto default_create_hash() -> const + std::function &; + +template +auto create_hash_blake2b_t(const unsigned char *data, + std::size_t data_size) -> hash_t { + MONITARR_USES_FUNCTION_NAME(); + + hash_t hash{}; + + crypto_generichash_blake2b_state state{}; + auto res = crypto_generichash_blake2b_init(&state, nullptr, 0U, hash.size()); + if (res != 0) { + throw utils::error::create_exception(function_name, + { + "failed to initialize blake2b", + std::to_string(hash.size() * 8U), + std::to_string(res), + }); + } + + res = crypto_generichash_blake2b_update(&state, data, data_size); + if (res != 0) { + throw utils::error::create_exception(function_name, + { + "failed to update blake2b", + std::to_string(hash.size() * 8U), + std::to_string(res), + }); + } + + res = crypto_generichash_blake2b_final(&state, hash.data(), hash.size()); + if (res != 0) { + throw utils::error::create_exception(function_name, + { + "failed to finalize blake2b", + std::to_string(hash.size() * 8U), + std::to_string(res), + }); + } + + return hash; +} + +inline const std::function + blake2b_256_hasher = + [](const unsigned char *data, std::size_t data_size) -> hash_256_t { + return create_hash_blake2b_t(data, data_size); +}; + +inline const std::function + blake2b_384_hasher = + [](const unsigned char *data, std::size_t data_size) -> hash_384_t { + return create_hash_blake2b_t(data, data_size); +}; + +inline const std::function + blake2b_512_hasher = + [](const unsigned char *data, std::size_t data_size) -> hash_512_t { + return create_hash_blake2b_t(data, data_size); +}; + +inline const std::function + sha256_hasher = + [](const unsigned char *data, std::size_t data_size) -> hash_256_t { + return create_hash_sha256(data, data_size); +}; + +inline const std::function + sha512_hasher = + [](const unsigned char *data, std::size_t data_size) -> hash_512_t { + return create_hash_sha512(data, data_size); +}; + +template <> +[[nodiscard]] inline auto default_create_hash() -> const + std::function & { + return blake2b_256_hasher; +} + +template <> +[[nodiscard]] inline auto default_create_hash() -> const + std::function & { + return blake2b_384_hasher; +} + +template <> +[[nodiscard]] inline auto default_create_hash() -> const + std::function & { + return blake2b_512_hasher; +} +} // namespace monitarr::utils::encryption + +#endif // defined(PROJECT_ENABLE_LIBSODIUM) +#endif // MONITARR_INCLUDE_UTILS_HASH_HPP_ diff --git a/support/include/utils/path.hpp b/support/include/utils/path.hpp new file mode 100644 index 0000000..1a339c0 --- /dev/null +++ b/support/include/utils/path.hpp @@ -0,0 +1,526 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_PATH_HPP_ +#define MONITARR_INCLUDE_UTILS_PATH_HPP_ + +#include "utils/config.hpp" +#include "utils/string.hpp" + +namespace monitarr::utils::path { +inline constexpr const std::string_view backslash{"\\"}; +inline constexpr const std::wstring_view backslash_w{L"\\"}; +inline constexpr const std::string_view dot{"."}; +inline constexpr const std::wstring_view dot_w{L"."}; +inline constexpr const std::string_view dot_backslash{".\\"}; +inline constexpr const std::wstring_view dot_backslash_w{L".\\"}; +inline constexpr const std::string_view dot_slash{"./"}; +inline constexpr const std::wstring_view dot_slash_w{L"./"}; +inline constexpr const std::string_view long_notation{"\\\\?\\"}; +inline constexpr const std::wstring_view long_notation_w{L"\\\\?\\"}; +inline constexpr const std::string_view slash{"/"}; +inline constexpr const std::wstring_view slash_w{L"/"}; +#if defined(_WIN32) +inline constexpr const std::string_view directory_seperator{backslash}; +inline constexpr const std::wstring_view directory_seperator_w{backslash_w}; +inline constexpr const std::string_view not_directory_seperator{slash}; +inline constexpr const std::wstring_view not_directory_seperator_w{slash_w}; +inline constexpr const std::string_view unc_notation{"\\\\"}; +inline constexpr const std::wstring_view unc_notation_w{L"\\\\"}; +#else // !defined(_WIN32) +inline constexpr const std::string_view directory_seperator{slash}; +inline constexpr const std::wstring_view directory_seperator_w{slash_w}; +inline constexpr const std::string_view not_directory_seperator{backslash}; +inline constexpr const std::wstring_view not_directory_seperator_w{backslash_w}; +#endif // defined(_WIN32) + +template +[[nodiscard]] inline constexpr auto +get_backslash() -> std::basic_string_view; + +template <> +[[nodiscard]] inline constexpr auto +get_backslash() -> std::basic_string_view { + return backslash; +} + +template <> +[[nodiscard]] inline constexpr auto +get_backslash() -> std::basic_string_view { + return backslash_w; +} + +template +[[nodiscard]] inline constexpr auto +get_directory_seperator() -> std::basic_string_view; + +template <> +[[nodiscard]] inline constexpr auto +get_directory_seperator() -> std::basic_string_view { + return directory_seperator; +} + +template <> +[[nodiscard]] inline constexpr auto +get_directory_seperator() -> std::basic_string_view { + return directory_seperator_w; +} + +template +[[nodiscard]] inline constexpr auto +get_not_directory_seperator() -> std::basic_string_view; + +template <> +[[nodiscard]] inline constexpr auto +get_not_directory_seperator() -> std::basic_string_view { + return not_directory_seperator; +} + +template <> +[[nodiscard]] inline constexpr auto +get_not_directory_seperator() -> std::basic_string_view { + return not_directory_seperator_w; +} + +template +[[nodiscard]] inline constexpr auto get_dot() -> std::basic_string_view; + +template <> +[[nodiscard]] inline constexpr auto +get_dot() -> std::basic_string_view { + return dot; +} + +template <> +[[nodiscard]] inline constexpr auto +get_dot() -> std::basic_string_view { + return dot_w; +} + +template +[[nodiscard]] inline constexpr auto +get_dot_backslash() -> std::basic_string_view; + +template <> +[[nodiscard]] inline constexpr auto +get_dot_backslash() -> std::basic_string_view { + return dot_backslash; +} + +template <> +[[nodiscard]] inline constexpr auto +get_dot_backslash() -> std::basic_string_view { + return dot_backslash_w; +} + +template +[[nodiscard]] inline constexpr auto +get_dot_slash() -> std::basic_string_view; + +template <> +[[nodiscard]] inline constexpr auto +get_dot_slash() -> std::basic_string_view { + return dot_slash; +} + +template <> +[[nodiscard]] inline constexpr auto +get_dot_slash() -> std::basic_string_view { + return dot_slash_w; +} + +template +[[nodiscard]] inline constexpr auto +get_long_notation() -> std::basic_string_view; + +template <> +[[nodiscard]] inline constexpr auto +get_long_notation() -> std::basic_string_view { + return long_notation; +} + +template <> +[[nodiscard]] inline constexpr auto +get_long_notation() -> std::basic_string_view { + return long_notation_w; +} + +template +[[nodiscard]] inline constexpr auto +get_slash() -> std::basic_string_view; + +template <> +[[nodiscard]] inline constexpr auto +get_slash() -> std::basic_string_view { + return slash; +} + +template <> +[[nodiscard]] inline constexpr auto +get_slash() -> std::basic_string_view { + return slash_w; +} + +#if defined(_WIN32) +template +[[nodiscard]] inline constexpr auto +get_unc_notation() -> std::basic_string_view; + +template <> +[[nodiscard]] inline constexpr auto +get_unc_notation() -> std::basic_string_view { + return unc_notation; +} + +template <> +[[nodiscard]] inline constexpr auto +get_unc_notation() -> std::basic_string_view { + return unc_notation_w; +} +#endif // defined(_WIN32) + +template +[[nodiscard]] inline auto get_current_path() -> string_t; + +[[nodiscard]] auto absolute(std::string_view path) -> std::string; + +[[nodiscard]] auto absolute(std::wstring_view path) -> std::wstring; + +[[nodiscard]] inline auto +combine(std::string_view path, + const std::vector &paths) -> std::string; + +[[nodiscard]] inline auto +combine(std::wstring_view path, + const std::vector &paths) -> std::wstring; + +[[nodiscard]] auto contains_trash_directory(std::string_view path) -> bool; + +[[nodiscard]] auto contains_trash_directory(std::wstring_view path) -> bool; + +[[nodiscard]] auto inline create_api_path(std::string_view path) -> std::string; + +[[nodiscard]] auto inline create_api_path(std::wstring_view path) + -> std::wstring; + +[[nodiscard]] auto exists(std::string_view path) -> bool; + +[[nodiscard]] auto exists(std::wstring_view path) -> bool; + +[[nodiscard]] inline auto finalize(std::string_view path) -> std::string; + +[[nodiscard]] inline auto finalize(std::wstring_view path) -> std::wstring; + +[[nodiscard]] auto +find_program_in_path(const std::string &name_without_extension) -> std::string; + +[[nodiscard]] auto +find_program_in_path(std::wstring_view name_without_extension) -> std::wstring; + +template +inline auto +format_path(string_t &path, + std::basic_string_view sep, + std::basic_string_view not_sep) + -> string_t &; + +[[nodiscard]] inline auto +get_parent_api_path(std::string_view path) -> std::string; + +[[nodiscard]] inline auto +get_parent_api_path(std::wstring_view path) -> std::wstring; + +[[nodiscard]] auto get_parent_path(std::string_view path) -> std::string; + +[[nodiscard]] auto get_parent_path(std::wstring_view path) -> std::wstring; + +[[nodiscard]] inline auto +get_parts(std::string_view path) -> std::vector; + +[[nodiscard]] inline auto +get_parts_w(std::wstring_view path) -> std::vector; + +[[nodiscard]] auto get_relative_path(std::string_view path, + std::string_view root_path) -> std::string; + +[[nodiscard]] auto +get_relative_path(std::wstring_view path, + std::wstring_view root_path) -> std::wstring; + +[[nodiscard]] auto make_file_uri(std::string_view path) -> std::string; + +[[nodiscard]] auto make_file_uri(std::wstring_view path) -> std::wstring; + +[[nodiscard]] auto strip_to_file_name(std::string path) -> std::string; + +[[nodiscard]] auto strip_to_file_name(std::wstring path) -> std::wstring; + +[[nodiscard]] auto unmake_file_uri(std::string_view uri) -> std::string; + +[[nodiscard]] auto unmake_file_uri(std::wstring_view uri) -> std::wstring; + +template +[[nodiscard]] inline auto combine_t( + std::basic_string_view path, + const std::vector> + &paths) -> string_t { + auto dir_sep_t = + string_t{get_directory_seperator()}; + return absolute( + std::accumulate(paths.begin(), paths.end(), + std::basic_string{path}, + [&dir_sep_t](auto next_path, auto &&path_part) { + if (next_path.empty()) { + return string_t{path_part}; + } + + return next_path + dir_sep_t + string_t{path_part}; + })); +} + +inline auto combine(std::string_view path, + const std::vector &paths) -> std::string { + return combine_t(path, paths); +} + +inline auto +combine(std::wstring_view path, + const std::vector &paths) -> std::wstring { + return combine_t(path, paths); +} + +template +[[nodiscard]] inline auto create_api_path_t( + std::basic_string_view path) -> string_t { + auto backslash_t = get_backslash(); + auto dot_backslash_t = get_dot_backslash(); + auto dot_slash_t = get_dot_slash(); + auto dot_t = get_dot(); + auto slash_t = get_slash(); + +#if defined(_WIN32) + auto long_notation_t = get_long_notation(); + if (utils::string::begins_with(path, long_notation_t)) { + path = path.substr(long_notation_t.size()); + } +#endif // defined(_WIN32) + + if (path.empty() || path == backslash_t || path == dot_t || + path == dot_slash_t || path == slash_t || path == dot_backslash_t) { + return string_t{slash_t}; + } + + string_t api_path{path}; +#if defined(_WIN32) + if ((api_path.size() >= 2U) && (api_path.at(1U) == ':')) { + api_path = api_path.substr(2U); + } +#endif // defined(_WIN32) + + format_path(api_path, slash_t, backslash_t); + + while (utils::string::begins_with(api_path, dot_slash_t)) { + api_path = api_path.substr(dot_slash_t.size()); + } + + if (api_path.at(0U) != slash_t.at(0U)) { + return string_t{slash_t} + api_path; + } + + return api_path; +} + +inline auto create_api_path(std::string_view path) -> std::string { + return create_api_path_t(path); +} + +inline auto create_api_path(std::wstring_view path) -> std::wstring { + return create_api_path_t(path); +} + +template +[[nodiscard]] inline auto finalize_t( + std::basic_string_view path) -> string_t { + string_t dir_sep_t{get_directory_seperator()}; + string_t fmt_path{path}; + if (fmt_path.empty()) { + return fmt_path; + } + + format_path(fmt_path, dir_sep_t, + get_not_directory_seperator()); + +#if defined(_WIN32) + auto unc_notation_t = get_unc_notation(); + if (utils::string::begins_with(fmt_path, unc_notation_t)) { + return fmt_path; + } + + auto dot_t = get_dot(); + auto dot_sep_t = string_t{dot_t} + dir_sep_t; + if (fmt_path == dot_t || fmt_path == dot_sep_t) { + return get_current_path(); + } + + if (fmt_path == dir_sep_t) { +#if defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) + return get_current_path().substr(0U, long_notation.size() + 2U); +#else // !defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) + return get_current_path().substr(0U, 2U); +#endif // defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) + } + + if (utils::string::begins_with(fmt_path, dir_sep_t)) { +#if defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) + return get_current_path().substr(0U, long_notation.size() + 2U) + + fmt_path; +#else // !defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) + return get_current_path().substr(0U, 2U) + fmt_path; +#endif // defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) + } + + if (utils::string::begins_with(fmt_path, dot_sep_t)) { + return get_current_path() + dir_sep_t + fmt_path.substr(2U); + } + +#if defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) + return string_t{get_long_notation()} + + fmt_path; +#endif // defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) +#endif // defined(_WIN32) + + return fmt_path; +} + +inline auto finalize(std::string_view path) -> std::string { + return finalize_t(path); +} + +inline auto finalize(std::wstring_view path) -> std::wstring { + return finalize_t(path); +} + +template +inline auto +format_path(string_t &path, + std::basic_string_view sep, + std::basic_string_view not_sep) + -> string_t & { + utils::string::replace(path, not_sep, sep); + +#if defined(_WIN32) + auto is_unc{false}; + auto long_notation_t = get_long_notation(); + auto unc_notation_t = get_unc_notation(); + if (utils::string::begins_with(path, long_notation_t)) { + path = path.substr(long_notation_t.size()); + } else if (utils::string::begins_with(path, unc_notation_t)) { + path = path.substr(unc_notation_t.size()); + utils::string::left_trim(path, sep.at(0U)); + is_unc = true; + } +#endif // defined(_WIN32) + + string_t double_sep(2U, sep.at(0U)); + while (utils::string::contains(path, double_sep)) { + utils::string::replace(path, double_sep, sep); + } + + if (path != sep) { + utils::string::right_trim(path, sep.at(0U)); + } + +#if defined(_WIN32) + if (is_unc) { + path = string_t{unc_notation_t} + path; + } else if ((path.size() >= 2U) && (path.at(1U) == ':')) { + path[0U] = utils::string::to_lower(string_t(1U, path.at(0U))).at(0U); + } +#endif // defined(_WIN32) + + return path; +} + +template <> +[[nodiscard]] inline auto get_current_path() -> std::string { +#if defined(_WIN32) + std::string path; + path.resize(monitarr::max_path_length + 1U); + ::GetCurrentDirectoryA(static_cast(path.size()), path.data()); + path = path.c_str(); + return finalize(path); +#else // !defined(_WIN32) + return finalize(std::filesystem::current_path().string()); +#endif // defined(_WIN32) +} + +template <> +[[nodiscard]] inline auto get_current_path() -> std::wstring { + return utils::string::from_utf8(get_current_path()); +} + +template +[[nodiscard]] inline auto get_parent_api_path_t( + std::basic_string_view path) -> string_t { + auto slash_t = get_slash(); + + string_t ret{path}; + utils::string::right_trim(ret, slash_t.at(0U)); + + if (ret == slash_t || ret.empty()) { + return string_t{path}; + } + + auto sub_path = ret.substr(0, ret.rfind(slash_t) + 1); + if (sub_path == slash_t) { + return string_t{sub_path}; + } + + return sub_path; +} + +inline auto get_parent_api_path(std::string_view path) -> std::string { + return create_api_path(get_parent_api_path_t(path)); +} + +inline auto get_parent_api_path(std::wstring_view path) -> std::wstring { + return create_api_path(get_parent_api_path_t(path)); +} + +template +[[nodiscard]] inline auto +get_parts_t(std::basic_string_view path) + -> std::vector { + return utils::string::split( + path, get_directory_seperator().at(0U), + false); +} + +inline auto get_parts(std::string_view path) -> std::vector { + return get_parts_t(path); +} + +inline auto get_parts_w(std::wstring_view path) -> std::vector { + return get_parts_t(path); +} +} // namespace monitarr::utils::path + +#endif // MONITARR_INCLUDE_UTILS_PATH_HPP_ diff --git a/support/include/utils/string.hpp b/support/include/utils/string.hpp new file mode 100644 index 0000000..d05d6c3 --- /dev/null +++ b/support/include/utils/string.hpp @@ -0,0 +1,469 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_STRING_HPP_ +#define MONITARR_INCLUDE_UTILS_STRING_HPP_ + +#include "utils/config.hpp" + +namespace monitarr::utils::string { +template struct chain_replace_with_hex; + +template +[[nodiscard]] inline auto +char_to_hex(typename string_t::value_type character) -> string_t; + +[[nodiscard]] inline auto begins_with(std::string_view str, + std::string_view val) -> bool; + +[[nodiscard]] inline auto begins_with(std::wstring_view str, + std::wstring_view val) -> bool; + +template +[[nodiscard]] inline auto case_insensitive_find_string(string_t in_str, + string_t for_str) -> + typename string_t::const_iterator; + +[[nodiscard]] inline auto contains(std::string_view str, + std::string_view search) -> bool; + +[[nodiscard]] inline auto contains(std::wstring_view str, + std::wstring_view search) -> bool; + +[[nodiscard]] inline auto ends_with(std::string_view str, + std::string_view val) -> bool; + +[[nodiscard]] inline auto ends_with(std::wstring_view str, + std::wstring_view val) -> bool; + +[[nodiscard]] auto from_bool(bool val) -> std::string; + +#if defined(PROJECT_ENABLE_BOOST) +[[nodiscard]] auto +from_dynamic_bitset(const boost::dynamic_bitset<> &bitset) -> std::string; +#endif // defined(PROJECT_ENABLE_BOOST) + +[[nodiscard]] auto from_utf8(std::string_view str) -> std::wstring; + +[[nodiscard]] inline auto is_numeric(std::string_view str) -> bool; + +[[nodiscard]] inline auto is_numeric(std::wstring_view str) -> bool; + +template +[[nodiscard]] inline auto join(const std::vector &arr, + typename string_t::value_type delim) -> string_t; + +template +auto left_trim(string_t &str, + typename string_t::value_type trim_ch = ' ') -> string_t &; + +template +inline auto replace(string_t &src, typename string_t::value_type character, + typename string_t::value_type with, + std::size_t start_pos = 0U) -> string_t &; + +template +inline auto replace(string_t &src, + std::basic_string_view find, + std::basic_string_view with, + std::size_t start_pos = 0U) -> string_t &; + +template +[[nodiscard]] inline auto replace_copy(string_t src, + typename string_t::value_type character, + typename string_t::value_type with, + std::size_t start_pos = 0U) -> string_t; + +template +[[nodiscard]] inline auto +replace_copy(string_t src, + std::basic_string_view find, + std::basic_string_view with, + std::size_t start_pos = 0U) -> string_t; + +template +[[nodiscard]] inline auto +replace_with_hex(string_t &str, typename string_t::value_type character) + -> chain_replace_with_hex; + +template +inline auto +right_trim(string_t &str, + typename string_t::value_type trim_ch = ' ') -> string_t &; + +[[nodiscard]] inline auto split(std::string_view str, char delim, + bool should_trim) -> std::vector; + +[[nodiscard]] inline auto split(std::wstring_view str, wchar_t delim, + bool should_trim) -> std::vector; + +[[nodiscard]] inline auto split(std::string_view str, std::string_view delim, + bool should_trim) -> std::vector; + +[[nodiscard]] inline auto split(std::wstring_view str, std::wstring_view delim, + bool should_trim) -> std::vector; + +#if defined(PROJECT_ENABLE_SFML) +auto replace_sf(sf::String &src, const sf::String &find, const sf::String &with, + std::size_t start_pos = 0U) -> sf::String &; + +[[nodiscard]] auto split_sf(sf::String str, wchar_t delim, + bool should_trim) -> std::vector; +#endif // defined(PROJECT_ENABLE_SFML) + +[[nodiscard]] auto to_bool(std::string val) -> bool; + +[[nodiscard]] auto to_double(const std::string &str) -> double; + +#if defined(PROJECT_ENABLE_BOOST) +[[nodiscard]] auto +to_dynamic_bitset(const std::string &val) -> boost::dynamic_bitset<>; +#endif // defined(PROJECT_ENABLE_BOOST) + +template +[[nodiscard]] inline auto to_lower(string_t str) -> string_t; + +[[nodiscard]] auto to_int32(const std::string &val) -> std::int32_t; + +[[nodiscard]] auto to_int64(const std::string &val) -> std::int64_t; + +[[nodiscard]] auto to_size_t(const std::string &val) -> std::size_t; + +[[nodiscard]] auto to_uint8(const std::string &val) -> std::uint8_t; + +[[nodiscard]] auto to_uint16(const std::string &val) -> std::uint16_t; + +[[nodiscard]] auto to_uint32(const std::string &val) -> std::uint32_t; + +[[nodiscard]] auto to_uint64(const std::string &val) -> std::uint64_t; + +template +[[nodiscard]] inline auto to_upper(string_t str) -> string_t; + +[[nodiscard]] auto to_utf8(std::string_view str) -> std::string; + +[[nodiscard]] auto to_utf8(std::wstring_view str) -> std::string; + +template +[[nodiscard]] inline auto zero_pad(string_t str, std::size_t count) -> string_t; + +template struct chain_replace_with_hex final { + explicit chain_replace_with_hex(string_t &value) : str(value) {} + + chain_replace_with_hex(const chain_replace_with_hex &) = delete; + + chain_replace_with_hex(chain_replace_with_hex &&) = delete; + + ~chain_replace_with_hex() = default; + + auto operator=(const chain_replace_with_hex &) -> chain_replace_with_hex & = + delete; + + auto + operator=(chain_replace_with_hex &&) -> chain_replace_with_hex & = delete; + + auto operator()(typename string_t::value_type character) + -> chain_replace_with_hex { + return replace_with_hex(str, character); + } + + string_t &str; +}; + +template +inline auto trim(string_t &str, + typename string_t::value_type trim_ch = ' ') -> string_t &; + +template +[[nodiscard]] inline auto +trim_copy(string_t str, + typename string_t::value_type trim_ch = ' ') -> string_t; + +template +[[nodiscard]] inline auto +begins_with_t(std::basic_string_view str, + std::basic_string_view val) -> bool { + return (str.find(val) == 0U); +} + +inline auto begins_with(std::string_view str, std::string_view val) -> bool { + return begins_with_t(str, val); +} + +inline auto begins_with(std::wstring_view str, std::wstring_view val) -> bool { + return begins_with_t(str, val); +} + +template +inline auto case_insensitive_find_string(string_t in_str, string_t for_str) -> + typename string_t::const_iterator { + static const auto compare_chars = + [](typename string_t::value_type char_a, + typename string_t::value_type char_b) -> bool { + return (std::tolower(char_a) == std::tolower(char_b)); + }; + + return (std::search(in_str.begin(), in_str.end(), for_str.begin(), + for_str.end(), compare_chars)); +} + +template +inline auto char_to_hex(typename string_t::value_type character) -> string_t { + std::basic_stringstream stream; + stream << '%' << std::setfill('0') + << std::setw(sizeof(character)) << std::hex + << static_cast(character); + return stream.str(); +} + +template +[[nodiscard]] inline auto +contains_t(std::basic_string_view str, + std::basic_string_view search) -> bool { + return (str.find(search) != std::basic_string_view::npos); +} + +inline auto contains(std::string_view str, std::string_view search) -> bool { + return contains_t(str, search); +} + +inline auto contains(std::wstring_view str, std::wstring_view search) -> bool { + return contains_t(str, search); +} + +template +[[nodiscard]] inline auto +ends_with_t(std::basic_string_view str, + std::basic_string_view val) -> bool { + if (val.size() > str.size()) { + return false; + } + + return std::equal(val.rbegin(), val.rend(), str.rbegin()); +} + +inline auto ends_with(std::string_view str, std::string_view val) -> bool { + return ends_with_t(str, val); +} + +inline auto ends_with(std::wstring_view str, std::wstring_view val) -> bool { + return ends_with_t(str, val); +} + +template +[[nodiscard]] inline auto +is_numeric_t(std::basic_string_view str) -> bool { + if ((str.length() > 1U) && (str.at(0U) == '+' || str.at(0U) == '-')) { + str = str.substr(1U); + } + + if (str.empty()) { + return false; + } + + auto has_decimal{false}; + return std::find_if(str.begin(), str.end(), + [&has_decimal](auto &&cur_ch) -> bool { + if (has_decimal && cur_ch == '.') { + return true; + } + + has_decimal = has_decimal || cur_ch == '.'; + if (has_decimal) { + return false; + } + + return std::isdigit(cur_ch) == 0; + }) == str.end(); +} + +inline auto is_numeric(std::string_view str) -> bool { + return is_numeric_t(str); +} + +inline auto is_numeric(std::wstring_view str) -> bool { + return is_numeric_t(str); +} + +template +inline auto join(const std::vector &arr, + typename string_t::value_type delim) -> string_t { + if (arr.empty()) { + return {}; + } + + return std::accumulate( + std::next(arr.begin()), arr.end(), arr[0U], + [&delim](auto str, const auto &cur) { return str + delim + cur; }); +} + +template +auto left_trim(string_t &str, + typename string_t::value_type trim_ch) -> string_t & { + str.erase(0, str.find_first_not_of(trim_ch)); + return str; +} + +template +inline auto replace(string_t &src, typename string_t::value_type character, + typename string_t::value_type with, + std::size_t start_pos) -> string_t & { + if (start_pos < src.size()) { + std::replace(std::next(src.begin(), start_pos), src.end(), character, with); + } + + return src; +} + +template +inline auto replace(string_t &src, + std::basic_string_view find, + std::basic_string_view with, + std::size_t start_pos) -> string_t & { + if (start_pos < src.size()) { + while ((start_pos = src.find(find, start_pos)) != string_t::npos) { + src.replace(start_pos, find.size(), with); + start_pos += with.size(); + } + } + + return src; +} + +template +inline auto replace_copy(string_t src, typename string_t::value_type character, + typename string_t::value_type with, + std::size_t start_pos) -> string_t { + return replace(src, character, with, start_pos); +} + +template +inline auto +replace_copy(string_t src, + std::basic_string_view find, + std::basic_string_view with, + std::size_t start_pos) -> string_t { + return replace(src, find, with, start_pos); +} + +template +inline auto replace_with_hex(string_t &str, + typename string_t::value_type character) + -> chain_replace_with_hex { + return chain_replace_with_hex( + replace(str, string_t{1U, character}, char_to_hex(character))); +} + +template +inline auto right_trim(string_t &str, + typename string_t::value_type trim_ch) -> string_t & { + str.erase(str.find_last_not_of(trim_ch) + 1); + return str; +} + +template inline auto to_lower(string_t str) -> string_t { + std::transform(str.begin(), str.end(), str.begin(), ::tolower); + return str; +} + +template inline auto to_upper(string_t str) -> string_t { + std::transform(str.begin(), str.end(), str.begin(), ::toupper); + return str; +} + +template +inline auto trim(string_t &str, + typename string_t::value_type trim_ch) -> string_t & { + return right_trim(left_trim(str, trim_ch), trim_ch); +} + +template +inline auto trim_copy(string_t str, + typename string_t::value_type trim_ch) -> string_t { + return trim(str, trim_ch); +} + +template +[[nodiscard]] inline auto +split_t(std::basic_string_view str, + typename string_t::value_type delim, + bool should_trim) -> std::vector { + std::vector ret; + std::basic_stringstream stream{string_t{str}}; + + string_t val; + while (std::getline(stream, val, delim)) { + if (should_trim) { + trim(val); + } + ret.push_back(std::move(val)); + } + + return ret; +} + +inline auto split(std::string_view str, char delim, + bool should_trim) -> std::vector { + return split_t(str, delim, should_trim); +} + +inline auto split(std::wstring_view str, wchar_t delim, + bool should_trim) -> std::vector { + return split_t(str, delim, should_trim); +} + +template +[[nodiscard]] inline auto +split_t(std::basic_string_view str, + std::basic_string_view delim, + bool should_trim) -> std::vector { + auto result = std::views::split(str, delim); + + std::vector ret{}; + for (auto &&word : result) { + auto val = string_t{word.begin(), word.end()}; + if (should_trim) { + trim(val); + } + ret.push_back(std::move(val)); + } + + return ret; +} + +inline auto split(std::string_view str, std::string_view delim, + bool should_trim) -> std::vector { + return split_t(str, delim, should_trim); +} + +inline auto split(std::wstring_view str, std::wstring_view delim, + bool should_trim) -> std::vector { + return split_t(str, delim, should_trim); +} + +template +inline auto zero_pad(string_t str, std::size_t count) -> string_t { + str.insert(str.begin(), count - str.length(), '0'); + return str; +} +} // namespace monitarr::utils::string + +#endif // MONITARR_INCLUDE_UTILS_STRING_HPP_ diff --git a/support/include/utils/time.hpp b/support/include/utils/time.hpp new file mode 100644 index 0000000..7d77574 --- /dev/null +++ b/support/include/utils/time.hpp @@ -0,0 +1,69 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_TIME_HPP_ +#define MONITARR_INCLUDE_UTILS_TIME_HPP_ + +#include "utils/config.hpp" + +namespace monitarr::utils::time { +inline constexpr const auto NANOS_PER_SECOND{1000000000ULL}; +inline constexpr const auto WIN32_TIME_CONVERSION{116444736000000000ULL}; +inline constexpr const auto WIN32_TIME_NANOS_PER_TICK{100ULL}; + +#if defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT) +[[nodiscard]] inline auto convert_to_utc(time_t time) -> std::time_t { + auto calendar_time = fmt::gmtime(time); + return std::mktime(&calendar_time); +} +#endif // defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT) + +#if defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT) +[[nodiscard]] inline auto get_current_time_utc() -> std::time_t { + auto calendar_time = fmt::gmtime(std::time(nullptr)); + return std::mktime(&calendar_time); +} +#endif // defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT) + +void get_local_time_now(struct tm &local_time); + +[[nodiscard]] auto get_time_now() -> std::uint64_t; + +#if defined(_WIN32) +auto strptime(const char *s, const char *f, struct tm *tm) -> const char *; + +[[nodiscard]] auto unix_time_to_filetime(std::uint64_t unix_time) -> FILETIME; + +[[nodiscard]] auto +windows_file_time_to_unix_time(FILETIME win_time) -> std::uint64_t; + +[[nodiscard]] auto +windows_time_t_to_unix_time(__time64_t win_time) -> std::uint64_t; +#endif // defined(_WIN32) + +[[nodiscard]] auto +unix_time_to_windows_time(std::uint64_t unix_time) -> std::uint64_t; + +[[nodiscard]] auto +windows_time_to_unix_time(std::uint64_t win_time) -> std::uint64_t; +} // namespace monitarr::utils::time + +#endif // MONITARR_INCLUDE_UTILS_TIME_HPP_ diff --git a/support/include/utils/types/file/i_directory.hpp b/support/include/utils/types/file/i_directory.hpp new file mode 100644 index 0000000..a8505b3 --- /dev/null +++ b/support/include/utils/types/file/i_directory.hpp @@ -0,0 +1,81 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_TYPES_FILE_I_DIRECTORY_HPP_ +#define MONITARR_INCLUDE_TYPES_FILE_I_DIRECTORY_HPP_ + +#include "utils/config.hpp" + +#include "utils/types/file/i_file.hpp" +#include "utils/types/file/i_fs_item.hpp" + +namespace monitarr::utils::file { +struct i_directory : public i_fs_item { + using fs_directory_t = std::unique_ptr; + using fs_file_t = i_file::fs_file_t; + + virtual ~i_directory() = default; + + [[nodiscard]] virtual auto + count(bool recursive = false) const -> std::uint64_t = 0; + + [[nodiscard]] virtual auto + create_directory(std::string_view path = "") const -> fs_directory_t = 0; + + [[nodiscard]] virtual auto create_file(std::string_view file_name, + bool read_only) const -> fs_file_t = 0; + + [[nodiscard]] virtual auto + get_directory(std::string_view path) const -> fs_directory_t = 0; + + [[nodiscard]] virtual auto + get_directories() const -> std::vector = 0; + + [[nodiscard]] virtual auto + get_file(std::string_view path) const -> fs_file_t = 0; + + [[nodiscard]] virtual auto get_files() const -> std::vector = 0; + + [[nodiscard]] virtual auto get_items() const -> std::vector = 0; + + [[nodiscard]] auto is_directory_item() const -> bool override { return true; } + + [[nodiscard]] virtual auto is_stop_requested() const -> bool = 0; + + [[nodiscard]] virtual auto remove_recursively() -> bool = 0; + + [[nodiscard]] virtual auto + size(bool recursive = false) const -> std::uint64_t = 0; + +protected: + i_directory() noexcept = default; + + i_directory(const i_directory &) noexcept = default; + + i_directory(i_directory &&) noexcept = default; + + auto operator=(i_directory &&) noexcept -> i_directory & = default; + + auto operator=(const i_directory &) noexcept -> i_directory & = default; +}; +} // namespace monitarr::utils::file + +#endif // MONITARR_INCLUDE_TYPES_FILE_I_DIRECTORY_HPP_ diff --git a/support/include/utils/types/file/i_file.hpp b/support/include/utils/types/file/i_file.hpp new file mode 100644 index 0000000..7adeb3b --- /dev/null +++ b/support/include/utils/types/file/i_file.hpp @@ -0,0 +1,93 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_TYPES_FILE_I_FILE_HPP_ +#define MONITARR_INCLUDE_TYPES_FILE_I_FILE_HPP_ + +#include "utils/config.hpp" + +#include "utils/types/file/i_fs_item.hpp" + +namespace monitarr::utils::file { +struct i_file : public i_fs_item { + using fs_file_t = std::unique_ptr; + + virtual ~i_file() = default; + + virtual void close() = 0; + + virtual void flush() const = 0; + + [[nodiscard]] virtual auto get_handle() const -> native_handle = 0; + + [[nodiscard]] virtual auto get_read_buffer_size() const -> std::uint32_t = 0; + + [[nodiscard]] auto is_directory_item() const -> bool override { + return false; + } + + [[nodiscard]] virtual auto is_read_only() const -> bool = 0; + + [[nodiscard]] virtual auto read(data_buffer &data, std::uint64_t offset, + std::size_t *total_read = nullptr) -> bool { + return read(data.data(), data.size(), offset, total_read); + } + + [[nodiscard]] virtual auto + read(unsigned char *data, std::size_t to_read, std::uint64_t offset, + std::size_t *total_read = nullptr) -> bool = 0; + + [[nodiscard]] virtual auto + read_all(data_buffer &data, std::uint64_t offset, + std::size_t *total_read = nullptr) -> bool; + + virtual auto set_read_buffer_size(std::uint32_t size) -> std::uint32_t = 0; + + [[nodiscard]] virtual auto size() const -> std::optional = 0; + + [[nodiscard]] virtual auto truncate() -> bool { return truncate(0U); } + + [[nodiscard]] virtual auto truncate(std::size_t size) -> bool = 0; + + [[nodiscard]] virtual auto + write(const data_buffer &data, std::uint64_t offset, + std::size_t *total_written = nullptr) -> bool { + return write(data.data(), data.size(), offset, total_written); + } + + [[nodiscard]] virtual auto + write(const unsigned char *data, std::size_t to_write, std::size_t offset, + std::size_t *total_written = nullptr) -> bool = 0; + +protected: + i_file() noexcept = default; + + i_file(const i_file &) noexcept = default; + + i_file(i_file &&) noexcept = default; + + auto operator=(i_file &&) noexcept -> i_file & = default; + + auto operator=(const i_file &) noexcept -> i_file & = default; +}; +} // namespace monitarr::utils::file + +#endif // MONITARR_INCLUDE_TYPES_FILE_I_FILE_HPP_ diff --git a/support/include/utils/types/file/i_fs_item.hpp b/support/include/utils/types/file/i_fs_item.hpp new file mode 100644 index 0000000..9eee5df --- /dev/null +++ b/support/include/utils/types/file/i_fs_item.hpp @@ -0,0 +1,117 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_TYPES_FILE_I_FS_ITEM_HPP_ +#define MONITARR_INCLUDE_TYPES_FILE_I_FS_ITEM_HPP_ + +#include "utils/config.hpp" + +#include "utils/error.hpp" +#include "utils/string.hpp" + +namespace monitarr::utils::file { +enum class time_type { + accessed, + created, + modified, + written, +}; + +struct file_times final { + std::uint64_t accessed{}; + std::uint64_t created{}; + std::uint64_t modified{}; + std::uint64_t written{}; + + [[nodiscard]] auto get(time_type type) const -> std::uint64_t { + MONITARR_USES_FUNCTION_NAME(); + + switch (type) { + case time_type::accessed: + return accessed; + case time_type::created: + return created; + case time_type::modified: + return modified; + case time_type::written: + return written; + } + + throw utils::error::create_exception(function_name, + { + "type_type not supported", + }); + } +}; + +struct i_fs_item { + using fs_item_t = std::unique_ptr; + + virtual ~i_fs_item() = default; + + [[nodiscard]] virtual auto copy_to(std::string_view to_path, + bool overwrite) const -> bool = 0; + + [[nodiscard]] virtual auto copy_to(std::wstring_view new_path, + bool overwrite) -> bool { + return copy_to(utils::string::to_utf8(new_path), overwrite); + } + + [[nodiscard]] virtual auto exists() const -> bool = 0; + + [[nodiscard]] virtual auto get_path() const -> std::string = 0; + + [[nodiscard]] virtual auto + get_time(time_type type) const -> std::optional; + + [[nodiscard]] virtual auto is_directory_item() const -> bool = 0; + + [[nodiscard]] auto is_file_item() const -> bool { + return not is_directory_item(); + } + + [[nodiscard]] virtual auto is_symlink() const -> bool = 0; + + [[nodiscard]] virtual auto move_to(std::string_view new_path) -> bool = 0; + + [[nodiscard]] virtual auto move_to(std::wstring_view new_path) -> bool { + return move_to(utils::string::to_utf8(new_path)); + } + + [[nodiscard]] virtual auto remove() -> bool = 0; + +public: + [[nodiscard]] virtual operator bool() const = 0; + +protected: + i_fs_item() noexcept = default; + + i_fs_item(const i_fs_item &) noexcept = default; + + i_fs_item(i_fs_item &&) noexcept = default; + + auto operator=(i_fs_item &&) noexcept -> i_fs_item & = default; + + auto operator=(const i_fs_item &) noexcept -> i_fs_item & = default; +}; +} // namespace monitarr::utils::file + +#endif // MONITARR_INCLUDE_TYPES_FILE_I_FS_ITEM_HPP_ diff --git a/support/include/utils/unix.hpp b/support/include/utils/unix.hpp new file mode 100644 index 0000000..c625175 --- /dev/null +++ b/support/include/utils/unix.hpp @@ -0,0 +1,63 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_UNIX_HPP_ +#define MONITARR_INCLUDE_UTILS_UNIX_HPP_ +#if !defined(_WIN32) + +#include "utils/common.hpp" +#include "utils/config.hpp" + +namespace monitarr::utils { +using passwd_callback_t = std::function; + +#if defined(__APPLE__) +template +[[nodiscard]] auto convert_to_uint64(const thread_t *thread_ptr) + -> std::uint64_t; +#else // !defined(__APPLE__) +[[nodiscard]] auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t; +#endif // defined(__APPLE__) + +[[nodiscard]] auto get_last_error_code() -> int; + +[[nodiscard]] auto get_thread_id() -> std::uint64_t; + +[[nodiscard]] auto is_uid_member_of_group(uid_t uid, gid_t gid) -> bool; + +void set_last_error_code(int error_code); + +[[nodiscard]] auto use_getpwuid(uid_t uid, passwd_callback_t callback) + -> utils::result; + +// template implementations +#if defined(__APPLE__) +template +[[nodiscard]] auto convert_to_uint64(const thread_t *thread_ptr) + -> std::uint64_t { + return static_cast( + reinterpret_cast(thread_ptr)); +} +#endif // defined(__APPLE__) +} // namespace monitarr::utils + +#endif // !defined(_WIN32) +#endif // MONITARR_INCLUDE_UTILS_UNIX_HPP_ diff --git a/support/include/utils/windows.hpp b/support/include/utils/windows.hpp new file mode 100644 index 0000000..49c1d92 --- /dev/null +++ b/support/include/utils/windows.hpp @@ -0,0 +1,47 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_INCLUDE_UTILS_WINDOWS_HPP_ +#define MONITARR_INCLUDE_UTILS_WINDOWS_HPP_ +#if defined(_WIN32) + +#include "utils/config.hpp" + +namespace monitarr::utils { +void create_console(); + +void free_console(); + +[[nodiscard]] auto get_local_app_data_directory() -> const std::string &; + +[[nodiscard]] auto get_last_error_code() -> DWORD; + +[[nodiscard]] auto get_thread_id() -> std::uint64_t; + +[[nodiscard]] auto is_process_elevated() -> bool; + +[[nodiscard]] auto run_process_elevated(std::vector args) -> int; + +void set_last_error_code(DWORD errorCode); +} // namespace monitarr::utils + +#endif // defined(_WIN32) +#endif // MONITARR_INCLUDE_UTILS_WINDOWS_HPP_ diff --git a/support/src/backward.cpp b/support/src/backward.cpp new file mode 100644 index 0000000..c033016 --- /dev/null +++ b/support/src/backward.cpp @@ -0,0 +1,45 @@ +// Pick your poison. +// +// On GNU/Linux, you have few choices to get the most out of your stack trace. +// +// By default you get: +// - object filename +// - function name +// +// In order to add: +// - source filename +// - line and column numbers +// - source code snippet (assuming the file is accessible) + +// Install one of the following libraries then uncomment one of the macro (or +// better, add the detection of the lib and the macro definition in your build +// system) + +// - apt-get install libdw-dev ... +// - g++/clang++ -ldw ... +// #define BACKWARD_HAS_DW 1 + +// - apt-get install binutils-dev ... +// - g++/clang++ -lbfd ... +// #define BACKWARD_HAS_BFD 1 + +// - apt-get install libdwarf-dev ... +// - g++/clang++ -ldwarf ... +// #define BACKWARD_HAS_DWARF 1 + +// Regardless of the library you choose to read the debug information, +// for potentially more detailed stack traces you can use libunwind +// - apt-get install libunwind-dev +// - g++/clang++ -lunwind +// #define BACKWARD_HAS_LIBUNWIND 1 +#include "backward.hpp" + +#if defined(PROJECT_ENABLE_BACKWARD_CPP) + +namespace backward { + +backward::SignalHandling sh; + +} // namespace backward + +#endif // defined(PROJECT_ENABLE_BACKWARD_CPP) diff --git a/support/src/utils/common.cpp b/support/src/utils/common.cpp new file mode 100644 index 0000000..ff9d5bd --- /dev/null +++ b/support/src/utils/common.cpp @@ -0,0 +1,174 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "utils/common.hpp" + +#include "utils/error.hpp" +#include "utils/string.hpp" + +namespace monitarr::utils { +auto compare_version_strings(std::string version1, + std::string version2) -> std::int32_t { + + if (utils::string::contains(version1, "-")) { + version1 = utils::string::split(version1, '-', true)[0U]; + } + + if (utils::string::contains(version2, "-")) { + version2 = utils::string::split(version2, '-', true)[0U]; + } + + auto nums1 = utils::string::split(version1, '.', true); + auto nums2 = utils::string::split(version2, '.', true); + + while (nums1.size() > nums2.size()) { + nums2.emplace_back("0"); + } + + while (nums2.size() > nums1.size()) { + nums1.emplace_back("0"); + } + + for (std::size_t idx = 0U; idx < nums1.size(); idx++) { + auto int1 = utils::string::to_uint32(nums1[idx]); + auto int2 = utils::string::to_uint32(nums2[idx]); + auto res = std::memcmp(&int1, &int2, sizeof(int1)); + if (res != 0) { + return res; + } + } + + return 0; +} + +auto compare_version_strings(std::wstring_view version1, + std::wstring_view version2) -> std::int32_t { + return compare_version_strings(utils::string::to_utf8(version1), + utils::string::to_utf8(version2)); +} + +#if defined(PROJECT_ENABLE_STDUUID) +auto create_uuid_string() -> std::string { + std::random_device random_device{}; + auto seed_data = std::array{}; + std::generate(std::begin(seed_data), std::end(seed_data), + std::ref(random_device)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::mt19937 generator(seq); + uuids::uuid_random_generator gen{generator}; + + return uuids::to_string(gen()); +} + +auto create_uuid_wstring() -> std::wstring { + return utils::string::from_utf8(create_uuid_string()); +} +#endif // defined(PROJECT_ENABLE_STDUUID) + +auto generate_random_string(std::size_t length) -> std::string { + std::string ret; + if (length == 0U) { + return ret; + } + + ret.resize(length); + for (auto &ch : ret) { + std::array random_list{ + generate_random_between(0U, 57U), + generate_random_between(65U, 90U), + generate_random_between(97U, 255U), + }; + ch = static_cast( + random_list.at(generate_random_between(0U, 2U)) % 74U + 48U); + } + + return ret; +} + +auto generate_random_wstring(std::size_t length) -> std::wstring { + return utils::string::from_utf8(generate_random_string(length)); +} + +auto get_environment_variable(std::string_view variable) -> std::string { + static std::mutex mtx{}; + mutex_lock lock{mtx}; + + const auto *val = std::getenv(std::string{variable}.c_str()); + return std::string{val == nullptr ? "" : val}; +} + +auto get_environment_variable(std::wstring_view variable) -> std::wstring { + return utils::string::from_utf8( + get_environment_variable(utils::string::to_utf8(variable))); +} + +#if defined(PROJECT_ENABLE_BOOST) +auto get_next_available_port(std::uint16_t first_port, + std::uint16_t &available_port) -> bool { + if (first_port == 0U) { + return false; + } + + using namespace boost::asio; + using ip::tcp; + + boost::system::error_code error_code{}; + while (first_port != 0U) { + io_context ctx{}; + tcp::acceptor acceptor(ctx); + acceptor.open(tcp::v4(), error_code) || + acceptor.bind({tcp::v4(), first_port}, error_code); + if (not error_code) { + break; + } + + ++first_port; + } + + if (not error_code) { + available_port = first_port; + } + + return not error_code; +} +#endif // defined(PROJECT_ENABLE_BOOST) + +auto retry_action(retryable_action_t action, std::size_t retry_count, + std::chrono::milliseconds retry_wait) -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + for (std::size_t idx = 0U; idx < retry_count; ++idx) { + if (action()) { + return true; + } + + std::this_thread::sleep_for(retry_wait); + } + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} +} // namespace monitarr::utils diff --git a/support/src/utils/db/sqlite/db_common.cpp b/support/src/utils/db/sqlite/db_common.cpp new file mode 100644 index 0000000..5945930 --- /dev/null +++ b/support/src/utils/db/sqlite/db_common.cpp @@ -0,0 +1,250 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#if defined(PROJECT_ENABLE_SQLITE) + +#include "utils/db/sqlite/db_common.hpp" + +#include "utils/common.hpp" +#include "utils/error.hpp" + +namespace monitarr::utils::db::sqlite { +void sqlite3_deleter::operator()(sqlite3 *db3) const { + MONITARR_USES_FUNCTION_NAME(); + + if (db3 == nullptr) { + return; + } + + std::string err_msg; + if (not execute_sql(*db3, "VACUUM;", err_msg)) { + utils::error::handle_error(function_name, + utils::error::create_error_message({ + "failed to vacuum database", + err_msg, + })); + } + + if (not utils::retry_action([&db3]() -> bool { + auto res = sqlite3_close_v2(db3); + if (res == SQLITE_OK) { + return true; + } + + auto &&err_str = sqlite3_errstr(res); + utils::error::handle_error( + function_name, + utils::error::create_error_message({ + "failed to close database", + (err_str == nullptr ? std::to_string(res) : err_str), + })); + return false; + })) { + monitarr::utils::error::handle_error(function_name, + "failed to close database"); + } +} + +db_result::db_column::db_column(std::int32_t index, std::string name, + db_types_t value) noexcept + : index_(index), name_(std::move(name)), value_(std::move(value)) {} + +#if defined(PROJECT_ENABLE_JSON) +auto db_result::db_column::get_value_as_json() const -> nlohmann::json { + return std::visit( + overloaded{ + [this](std::int64_t value) -> auto { + return nlohmann::json({{name_, value}}); + }, + [](auto &&value) -> auto { return nlohmann::json::parse(value); }, + }, + value_); +} +#endif // defined(PROJECT_ENABLE_JSON) + +db_result::db_row::db_row(std::shared_ptr ctx) { + MONITARR_USES_FUNCTION_NAME(); + + auto column_count = sqlite3_column_count(ctx->stmt.get()); + for (std::int32_t col = 0; col < column_count; col++) { + std::string name{sqlite3_column_name(ctx->stmt.get(), col)}; + auto column_type = sqlite3_column_type(ctx->stmt.get(), col); + + db_types_t value; + switch (column_type) { + case SQLITE_INTEGER: { + value = sqlite3_column_int64(ctx->stmt.get(), col); + } break; + + case SQLITE_TEXT: { + const auto *text = reinterpret_cast( + sqlite3_column_text(ctx->stmt.get(), col)); + value = std::string(text == nullptr ? "" : text); + } break; + + default: + throw utils::error::create_exception(function_name, + { + "column type not implemented", + std::to_string(column_type), + }); + } + + columns_[name] = db_column{col, name, value}; + } +} + +auto db_result::db_row::get_columns() const -> std::vector { + std::vector ret; + for (const auto &item : columns_) { + ret.push_back(item.second); + } + return ret; +} + +auto db_result::db_row::get_column(std::int32_t index) const -> db_column { + auto iter = std::find_if( + columns_.begin(), columns_.end(), + [&index](auto &&col) -> bool { return col.second.get_index() == index; }); + if (iter == columns_.end()) { + throw std::out_of_range(""); + } + + return iter->second; +} + +auto db_result::db_row::get_column(std::string name) const -> db_column { + return columns_.at(name); +} + +db_result::db_result(db3_stmt_t stmt, std::int32_t res) + : ctx_(std::make_shared()), res_(res) { + ctx_->stmt = std::move(stmt); + if (res == SQLITE_OK) { + set_res(sqlite3_step(ctx_->stmt.get())); + } +} + +auto db_result::get_error_str() const -> std::string { + auto &&err_msg = sqlite3_errstr(res_); + return err_msg == nullptr ? std::to_string(res_) : err_msg; +} + +auto db_result::get_row(std::optional &opt_row) const -> bool { + opt_row.reset(); + + if (not has_row()) { + return false; + } + + opt_row = db_row{ctx_}; + set_res(sqlite3_step(ctx_->stmt.get())); + return true; +} + +auto db_result::has_row() const -> bool { return res_ == SQLITE_ROW; } + +void db_result::next_row() const { + if (not has_row()) { + return; + } + + set_res(sqlite3_step(ctx_->stmt.get())); +} + +auto db_result::ok() const -> bool { + return res_ == SQLITE_DONE || res_ == SQLITE_ROW; +} + +auto create_db(std::string db_path, + const std::map &sql_create_tables) + -> db3_t { + MONITARR_USES_FUNCTION_NAME(); + + sqlite3 *db_ptr{nullptr}; + auto db_res = + sqlite3_open_v2(db_path.c_str(), &db_ptr, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr); + if (db_res != SQLITE_OK) { + const auto *msg = sqlite3_errstr(db_res); + throw utils::error::create_exception( + function_name, { + "failed to open db", + db_path, + (msg == nullptr ? std::to_string(db_res) : msg), + }); + } + + auto db3 = db3_t{ + db_ptr, + sqlite3_deleter(), + }; + + for (auto &&create_item : sql_create_tables) { + std::string err_msg; + if (not sqlite::execute_sql(*db3, create_item.second, err_msg)) { + db3.reset(); + throw utils::error::create_exception(function_name, { + err_msg, + }); + } + } + + set_journal_mode(*db3); + + return db3; +} + +auto execute_sql(sqlite3 &db3, const std::string &sql, std::string &err) + -> bool { + MONITARR_USES_FUNCTION_NAME(); + + char *err_msg{nullptr}; + auto res = sqlite3_exec(&db3, sql.c_str(), nullptr, nullptr, &err_msg); + if (err_msg != nullptr) { + err = err_msg; + sqlite3_free(err_msg); + err_msg = nullptr; + } + + if (res == SQLITE_OK) { + return true; + } + + err = utils::error::create_error_message({ + function_name, + "failed to execute sql", + err, + sql, + }); + + return false; +} + +void set_journal_mode(sqlite3 &db3) { + sqlite3_exec(&db3, + "PRAGMA journal_mode = WAL;PRAGMA synchronous = NORMAL;PRAGMA " + "auto_vacuum = NONE;", + nullptr, nullptr, nullptr); +} +} // namespace monitarr::utils::db::sqlite + +#endif // defined(PROJECT_ENABLE_SQLITE) diff --git a/support/src/utils/db/sqlite/db_delete.cpp b/support/src/utils/db/sqlite/db_delete.cpp new file mode 100644 index 0000000..6de8fd7 --- /dev/null +++ b/support/src/utils/db/sqlite/db_delete.cpp @@ -0,0 +1,115 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#if defined(PROJECT_ENABLE_SQLITE) + +#include "utils/db/sqlite/db_delete.hpp" + +namespace monitarr::utils::db::sqlite { +auto db_delete::context::db_delete_op_t::dump() const -> std::string { + return db_delete{ctx}.dump(); +} + +auto db_delete::context::db_delete_op_t::go() const -> db_result { + return db_delete{ctx}.go(); +} + +auto db_delete::dump() const -> std::string { + std::stringstream query; + query << "DELETE FROM \"" << ctx_->table_name << "\""; + + if (ctx_->where_data) { + std::int32_t idx{}; + query << " WHERE " << ctx_->where_data->base.dump(idx); + } + + query << ';'; + + return query.str(); +} + +auto db_delete::go() const -> db_result { + sqlite3_stmt *stmt_ptr{nullptr}; + auto query_str = dump(); + auto res = + sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr); + + auto stmt = db3_stmt_t{ + stmt_ptr, + sqlite3_statement_deleter(), + }; + + if (res != SQLITE_OK) { + return {std::move(stmt), res}; + } + + if (not ctx_->where_data) { + return {std::move(stmt), res}; + } + + for (std::int32_t idx = 0; + idx < static_cast(ctx_->where_data->values.size()); + idx++) { + res = + std::visit(overloaded{ + [&stmt, &idx](std::int64_t data) -> std::int32_t { + return sqlite3_bind_int64(stmt.get(), idx + 1, data); + }, + [&stmt, &idx](const std::string &data) -> std::int32_t { + return sqlite3_bind_text(stmt.get(), idx + 1, + data.c_str(), -1, nullptr); + }, + }, + ctx_->where_data->values.at(static_cast(idx))); + if (res != SQLITE_OK) { + return {std::move(stmt), res}; + } + } + + return {std::move(stmt), res}; +} + +auto db_delete::group(context::w_t::group_func_t func) -> context::w_t::wn_t { + if (not ctx_->where_data) { + ctx_->where_data = std::make_unique(context::wd_t{ + context::w_t{0U, ctx_}, + {}, + {}, + }); + } + + return ctx_->where_data->base.group(std::move(func)); +} + +auto db_delete::where(std::string column_name) const -> context::w_t::cn_t { + if (not ctx_->where_data) { + ctx_->where_data = std::make_unique(context::wd_t{ + context::w_t{0U, ctx_}, + {}, + {}, + }); + } + + return ctx_->where_data->base.where(column_name); +} +} // namespace monitarr::utils::db::sqlite + +#endif // defined(PROJECT_ENABLE_SQLITE) diff --git a/support/src/utils/db/sqlite/db_insert.cpp b/support/src/utils/db/sqlite/db_insert.cpp new file mode 100644 index 0000000..ff7a5da --- /dev/null +++ b/support/src/utils/db/sqlite/db_insert.cpp @@ -0,0 +1,99 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#if defined(PROJECT_ENABLE_SQLITE) + +#include "utils/db/sqlite/db_insert.hpp" + +namespace monitarr::utils::db::sqlite { +auto db_insert::column_value(std::string column_name, db_types_t value) + -> db_insert { + ctx_->values[column_name] = value; + return *this; +} + +auto db_insert::dump() const -> std::string { + std::stringstream query; + query << "INSERT "; + if (ctx_->or_replace) { + query << "OR REPLACE "; + } + query << "INTO \"" << ctx_->table_name << "\" ("; + + for (std::int32_t idx = 0; + idx < static_cast(ctx_->values.size()); idx++) { + if (idx > 0) { + query << ", "; + } + query << '"' << std::next(ctx_->values.begin(), idx)->first << '"'; + } + + query << ") VALUES ("; + for (std::int32_t idx = 0; + idx < static_cast(ctx_->values.size()); idx++) { + if (idx > 0) { + query << ", "; + } + query << "?" << (idx + 1); + } + query << ");"; + + return query.str(); +} + +auto db_insert::go() const -> db_result { + sqlite3_stmt *stmt_ptr{nullptr}; + auto query_str = dump(); + auto res = + sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr); + + auto stmt = db3_stmt_t{ + stmt_ptr, + sqlite3_statement_deleter(), + }; + + if (res != SQLITE_OK) { + return {std::move(stmt), res}; + } + + for (std::int32_t idx = 0; + idx < static_cast(ctx_->values.size()); idx++) { + res = + std::visit(overloaded{ + [&idx, &stmt](std::int64_t data) -> std::int32_t { + return sqlite3_bind_int64(stmt.get(), idx + 1, data); + }, + [&idx, &stmt](const std::string &data) -> std::int32_t { + return sqlite3_bind_text(stmt.get(), idx + 1, + data.c_str(), -1, nullptr); + }, + }, + std::next(ctx_->values.begin(), idx)->second); + if (res != SQLITE_OK) { + return {std::move(stmt), res}; + } + } + + return {std::move(stmt), res}; +} +} // namespace monitarr::utils::db::sqlite + +#endif // defined(PROJECT_ENABLE_SQLITE) diff --git a/support/src/utils/db/sqlite/db_select.cpp b/support/src/utils/db/sqlite/db_select.cpp new file mode 100644 index 0000000..c57efc9 --- /dev/null +++ b/support/src/utils/db/sqlite/db_select.cpp @@ -0,0 +1,221 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#if defined(PROJECT_ENABLE_SQLITE) + +#include "utils/db/sqlite/db_select.hpp" + +namespace monitarr::utils::db::sqlite { +auto db_select::context::db_select_op_t::dump() const -> std::string { + return db_select{ctx}.dump(); +} + +auto db_select::context::db_select_op_t::go() const -> db_result { + return db_select{ctx}.go(); +} + +auto db_select::context::db_select_op_t::group_by(std::string column_name) + -> db_select::context::db_select_op_t { + db_select{ctx}.group_by(column_name); + return *this; +} + +auto db_select::context::db_select_op_t::limit(std::int32_t value) + -> db_select::context::db_select_op_t { + db_select{ctx}.limit(value); + return *this; +} + +auto db_select::context::db_select_op_t::offset(std::int32_t value) + -> db_select::context::db_select_op_t { + db_select{ctx}.offset(value); + return *this; +} + +auto db_select::context::db_select_op_t::order_by(std::string column_name, + bool ascending) + -> db_select::context::db_select_op_t { + db_select{ctx}.order_by(column_name, ascending); + return *this; +} + +auto db_select::column(std::string column_name) -> db_select { + ctx_->columns.push_back(column_name); + return *this; +} + +auto db_select::count(std::string column_name, std::string as_column_name) + -> db_select { + ctx_->count_columns[column_name] = as_column_name; + return *this; +} + +auto db_select::dump() const -> std::string { + std::stringstream query; + query << "SELECT "; + bool has_column{false}; + if (ctx_->columns.empty()) { + if (ctx_->count_columns.empty()) { + query << "*"; + has_column = true; + } + } else { + has_column = not ctx_->columns.empty(); + for (std::size_t idx = 0U; idx < ctx_->columns.size(); idx++) { + if (idx > 0U) { + query << ", "; + } + query << ctx_->columns.at(idx); + } + } + + for (std::int32_t idx = 0U; + idx < static_cast(ctx_->count_columns.size()); idx++) { + if (has_column || idx > 0) { + query << ", "; + } + query << "COUNT(\""; + auto &count_column = *std::next(ctx_->count_columns.begin(), idx); + query << count_column.first << "\") AS \"" << count_column.second << '"'; + } + query << " FROM \"" << ctx_->table_name << "\""; + + if (ctx_->where_data) { + std::int32_t idx{}; + query << " WHERE " << ctx_->where_data->base.dump(idx); + } + + if (not ctx_->group_by.empty()) { + query << " GROUP BY "; + for (std::size_t idx = 0U; idx < ctx_->group_by.size(); idx++) { + if (idx > 0U) { + query << ", "; + } + + query << "\"" << ctx_->group_by.at(idx) << "\""; + } + } + + if (ctx_->order_by.has_value()) { + query << " ORDER BY \"" << ctx_->order_by.value().first << "\" "; + query << (ctx_->order_by.value().second ? "ASC" : "DESC"); + } + + if (ctx_->limit.has_value()) { + query << " LIMIT " << ctx_->limit.value(); + } + + if (ctx_->offset.has_value()) { + query << " OFFSET " << ctx_->offset.value(); + } + + query << ';'; + + return query.str(); +} + +auto db_select::go() const -> db_result { + sqlite3_stmt *stmt_ptr{nullptr}; + auto query_str = dump(); + auto res = + sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr); + + auto stmt = db3_stmt_t{ + stmt_ptr, + sqlite3_statement_deleter(), + }; + + if (res != SQLITE_OK) { + return {std::move(stmt), res}; + } + + if (not ctx_->where_data) { + return {std::move(stmt), res}; + } + + for (std::int32_t idx = 0; + idx < static_cast(ctx_->where_data->values.size()); + idx++) { + res = + std::visit(overloaded{ + [&idx, &stmt](std::int64_t data) -> std::int32_t { + return sqlite3_bind_int64(stmt.get(), idx + 1, data); + }, + [&idx, &stmt](const std::string &data) -> std::int32_t { + return sqlite3_bind_text(stmt.get(), idx + 1, + data.c_str(), -1, nullptr); + }, + }, + ctx_->where_data->values.at(static_cast(idx))); + if (res != SQLITE_OK) { + return {std::move(stmt), res}; + } + } + + return {std::move(stmt), res}; +} + +auto db_select::group(context::w_t::group_func_t func) -> context::w_t::wn_t { + if (not ctx_->where_data) { + ctx_->where_data = std::make_unique(context::wd_t{ + context::w_t{0U, ctx_}, + {}, + {}, + }); + } + + return ctx_->where_data->base.group(std::move(func)); +} + +auto db_select::group_by(std::string column_name) -> db_select { + ctx_->group_by.emplace_back(std::move(column_name)); + return *this; +} + +auto db_select::limit(std::int32_t value) -> db_select { + ctx_->limit = value; + return *this; +} + +auto db_select::offset(std::int32_t value) -> db_select { + ctx_->offset = value; + return *this; +} + +auto db_select::order_by(std::string column_name, bool ascending) -> db_select { + ctx_->order_by = {column_name, ascending}; + return *this; +} + +auto db_select::where(std::string column_name) const -> context::w_t::cn_t { + if (not ctx_->where_data) { + ctx_->where_data = std::make_unique(context::wd_t{ + context::w_t{0U, ctx_}, + {}, + {}, + }); + } + + return ctx_->where_data->base.where(column_name); +} +} // namespace monitarr::utils::db::sqlite + +#endif // defined(PROJECT_ENABLE_SQLITE) diff --git a/support/src/utils/db/sqlite/db_update.cpp b/support/src/utils/db/sqlite/db_update.cpp new file mode 100644 index 0000000..339e4c9 --- /dev/null +++ b/support/src/utils/db/sqlite/db_update.cpp @@ -0,0 +1,189 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#if defined(PROJECT_ENABLE_SQLITE) + +#include "utils/db/sqlite/db_update.hpp" + +namespace monitarr::utils::db::sqlite { +auto db_update::context::db_update_op_t::dump() const -> std::string { + return db_update{ctx}.dump(); +} + +auto db_update::context::db_update_op_t::go() const -> db_result { + return db_update{ctx}.go(); +} + +auto db_update::context::db_update_op_t::limit(std::int32_t value) + -> db_update::context::db_update_op_t { + db_update{ctx}.limit(value); + return *this; +} + +auto db_update::context::db_update_op_t::order_by(std::string column_name, + bool ascending) + -> db_update::context::db_update_op_t { + db_update{ctx}.order_by(column_name, ascending); + return *this; +} + +auto db_update::column_value(std::string column_name, db_types_t value) + -> db_update { + ctx_->column_values[column_name] = value; + return *this; +} + +auto db_update::dump() const -> std::string { + std::stringstream query; + query << "UPDATE \"" << ctx_->table_name << "\" SET "; + + for (std::int32_t idx = 0; + idx < static_cast(ctx_->column_values.size()); idx++) { + if (idx > 0) { + query << ", "; + } + + auto column = std::next(ctx_->column_values.begin(), idx); + query << '"' << column->first << "\"=?" + std::to_string(idx + 1); + } + + if (ctx_->where_data) { + auto idx{static_cast(ctx_->column_values.size())}; + query << " WHERE " << ctx_->where_data->base.dump(idx); + } + + if (ctx_->order_by.has_value()) { + query << " ORDER BY \"" << ctx_->order_by.value().first << "\" "; + query << (ctx_->order_by.value().second ? "ASC" : "DESC"); + } + + if (ctx_->limit.has_value()) { + query << " LIMIT " << ctx_->limit.value(); + } + + query << ';'; + + return query.str(); +} + +auto db_update::go() const -> db_result { + sqlite3_stmt *stmt_ptr{nullptr}; + + auto query_str = dump(); + auto res = + sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr); + + auto stmt = db3_stmt_t{ + stmt_ptr, + sqlite3_statement_deleter(), + }; + + if (res != SQLITE_OK) { + return {std::move(stmt), res}; + } + + for (std::int32_t idx = 0; + idx < static_cast(ctx_->column_values.size()); idx++) { + res = + std::visit(overloaded{ + [&idx, &stmt](std::int64_t data) -> std::int32_t { + return sqlite3_bind_int64(stmt.get(), idx + 1, data); + }, + [&idx, &stmt](const std::string &data) -> std::int32_t { + return sqlite3_bind_text(stmt.get(), idx + 1, + data.c_str(), -1, nullptr); + }, + }, + std::next(ctx_->column_values.begin(), idx)->second); + if (res != SQLITE_OK) { + return {std::move(stmt), res}; + } + } + + if (not ctx_->where_data) { + return {std::move(stmt), res}; + } + + for (std::int32_t idx = 0; + idx < static_cast(ctx_->where_data->values.size()); + idx++) { + res = std::visit( + overloaded{ + [this, &idx, &stmt](std::int64_t data) -> std::int32_t { + return sqlite3_bind_int64( + stmt.get(), + idx + static_cast(ctx_->column_values.size()) + + 1, + data); + }, + [this, &idx, &stmt](const std::string &data) -> std::int32_t { + return sqlite3_bind_text( + stmt.get(), + idx + static_cast(ctx_->column_values.size()) + + 1, + data.c_str(), -1, nullptr); + }, + }, + ctx_->where_data->values.at(static_cast(idx))); + if (res != SQLITE_OK) { + return {std::move(stmt), res}; + } + } + + return {std::move(stmt), res}; +} + +auto db_update::group(context::w_t::group_func_t func) -> context::w_t::wn_t { + if (not ctx_->where_data) { + ctx_->where_data = std::make_unique(context::wd_t{ + context::w_t{0U, ctx_}, + {}, + {}, + }); + } + + return ctx_->where_data->base.group(std::move(func)); +} + +auto db_update::limit(std::int32_t value) -> db_update { + ctx_->limit = value; + return *this; +} + +auto db_update::order_by(std::string column_name, bool ascending) -> db_update { + ctx_->order_by = {column_name, ascending}; + return *this; +} + +auto db_update::where(std::string column_name) const -> context::w_t::cn_t { + if (not ctx_->where_data) { + ctx_->where_data = std::make_unique(context::wd_t{ + context::w_t{0U, ctx_}, + {}, + {}, + }); + } + + return ctx_->where_data->base.where(column_name); +} +} // namespace monitarr::utils::db::sqlite + +#endif // defined(PROJECT_ENABLE_SQLITE) diff --git a/support/src/utils/encrypting_reader.cpp b/support/src/utils/encrypting_reader.cpp new file mode 100644 index 0000000..b9b70b3 --- /dev/null +++ b/support/src/utils/encrypting_reader.cpp @@ -0,0 +1,451 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + +#include "utils/encrypting_reader.hpp" + +#include "utils/collection.hpp" +#include "utils/common.hpp" +#include "utils/encryption.hpp" +#include "utils/error.hpp" +#include "utils/file.hpp" +#include "utils/unix.hpp" +#include "utils/windows.hpp" + +#if !defined(CURL_READFUNC_ABORT) +#define CURL_READFUNC_ABORT (-1) +#endif // !defined(CURL_READFUNC_ABORT) + +namespace monitarr::utils::encryption { +class encrypting_streambuf final : public encrypting_reader::streambuf { +public: + encrypting_streambuf(const encrypting_streambuf &) = default; + encrypting_streambuf(encrypting_streambuf &&) = delete; + auto operator=(const encrypting_streambuf &) + -> encrypting_streambuf & = delete; + auto operator=(encrypting_streambuf &&) -> encrypting_streambuf & = delete; + + explicit encrypting_streambuf(const encrypting_reader &reader) + : reader_(reader) { + setg(reinterpret_cast(0), reinterpret_cast(0), + reinterpret_cast(reader_.get_total_size())); + } + + ~encrypting_streambuf() override = default; + +private: + encrypting_reader reader_; + +protected: + auto seekoff(off_type off, std::ios_base::seekdir dir, + std::ios_base::openmode which = std::ios_base::out | + std::ios_base::in) + -> pos_type override { + MONITARR_USES_FUNCTION_NAME(); + + if ((which & std::ios_base::in) != std::ios_base::in) { + throw utils::error::create_exception(function_name, + { + "output is not supported", + }); + } + + const auto set_position = [this](char *next) -> pos_type { + if ((next <= egptr()) && (next >= eback())) { + setg(eback(), next, reinterpret_cast(reader_.get_total_size())); + return static_cast( + reinterpret_cast(gptr())); + } + + return {traits_type::eof()}; + }; + + switch (dir) { + case std::ios_base::beg: + return set_position(eback() + off); + + case std::ios_base::cur: + return set_position(gptr() + off); + + case std::ios_base::end: + return set_position(egptr() + off); + } + + return encrypting_reader::streambuf::seekoff(off, dir, which); + } + + auto seekpos(pos_type pos, + std::ios_base::openmode which = std::ios_base::out | + std::ios_base::in) + -> pos_type override { + return seekoff(pos, std::ios_base::beg, which); + } + + auto uflow() -> int_type override { + auto ret = underflow(); + if (ret == traits_type::eof()) { + return ret; + } + + gbump(1); + + return ret; + } + + auto underflow() -> int_type override { + if (gptr() == egptr()) { + return traits_type::eof(); + } + + reader_.set_read_position(reinterpret_cast(gptr())); + + char c{}; + const auto res = encrypting_reader::reader_function(&c, 1U, 1U, &reader_); + if (res != 1) { + return traits_type::eof(); + } + + return c; + } + + auto xsgetn(char *ptr, std::streamsize count) -> std::streamsize override { + if (gptr() == egptr()) { + return traits_type::eof(); + } + + reader_.set_read_position(reinterpret_cast(gptr())); + + auto res = encrypting_reader::reader_function( + ptr, 1U, static_cast(count), &reader_); + if ((res == reader_.get_error_return()) || + (reader_.get_stop_requested() && + (res == static_cast(CURL_READFUNC_ABORT)))) { + return traits_type::eof(); + } + + setg(eback(), gptr() + res, + reinterpret_cast(reader_.get_total_size())); + return static_cast(res); + } +}; + +class encrypting_reader_iostream final : public encrypting_reader::iostream { +public: + encrypting_reader_iostream(const encrypting_reader_iostream &) = delete; + encrypting_reader_iostream(encrypting_reader_iostream &&) = delete; + + auto operator=(const encrypting_reader_iostream &) + -> encrypting_reader_iostream & = delete; + auto operator=(encrypting_reader_iostream &&) + -> encrypting_reader_iostream & = delete; + + explicit encrypting_reader_iostream( + std::unique_ptr buffer) + : encrypting_reader::iostream(buffer.get()), buffer_(std::move(buffer)) {} + + ~encrypting_reader_iostream() override = default; + +private: + std::unique_ptr buffer_; +}; + +const std::size_t encrypting_reader::header_size_ = ([]() { + return crypto_aead_xchacha20poly1305_IETF_NPUBBYTES + + crypto_aead_xchacha20poly1305_IETF_ABYTES; +})(); +const std::size_t encrypting_reader::data_chunk_size_ = (8UL * 1024UL * 1024UL); +const std::size_t encrypting_reader::encrypted_chunk_size_ = + data_chunk_size_ + header_size_; + +encrypting_reader::encrypting_reader( + std::string_view file_name, std::string_view source_path, + stop_type_callback stop_requested_cb, std::string_view token, + std::optional relative_parent_path, std::size_t error_return) + : key_(utils::encryption::generate_key( + token)), + stop_requested_cb_(std::move(stop_requested_cb)), + error_return_(error_return), + source_file_(utils::file::file::open_or_create_file(source_path, true)) { + MONITARR_USES_FUNCTION_NAME(); + + if (not*source_file_) { + throw utils::error::create_exception(function_name, { + "file open failed", + source_path, + }); + } + + data_buffer result; + utils::encryption::encrypt_data( + key_, reinterpret_cast(file_name.data()), + file_name.size(), result); + encrypted_file_name_ = utils::collection::to_hex_string(result); + + if (relative_parent_path.has_value()) { + for (auto &&part : + utils::string::split(relative_parent_path.value(), + utils::path::directory_seperator, false)) { + utils::encryption::encrypt_data( + key_, reinterpret_cast(part.c_str()), + strnlen(part.c_str(), part.size()), result); + encrypted_file_path_ += '/' + utils::collection::to_hex_string(result); + } + encrypted_file_path_ += '/' + encrypted_file_name_; + } + + auto opt_size = source_file_->size(); + if (not opt_size.has_value()) { + throw utils::error::create_exception(function_name, + { + "failed to get file size", + source_file_->get_path(), + }); + } + auto file_size = opt_size.value(); + + const auto total_chunks = utils::divide_with_ceiling( + file_size, static_cast(data_chunk_size_)); + total_size_ = file_size + (total_chunks * encryption_header_size); + last_data_chunk_ = total_chunks - 1U; + last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size + : (file_size % data_chunk_size_) == 0U + ? data_chunk_size_ + : file_size % data_chunk_size_; + iv_list_.resize(total_chunks); + for (auto &iv : iv_list_) { + randombytes_buf(iv.data(), iv.size()); + } +} + +encrypting_reader::encrypting_reader(std::string_view encrypted_file_path, + std::string_view source_path, + stop_type_callback stop_requested_cb, + std::string_view token, + std::size_t error_return) + : key_(utils::encryption::generate_key( + token)), + stop_requested_cb_(std::move(stop_requested_cb)), + error_return_(error_return), + source_file_(utils::file::file::open_or_create_file(source_path, true)) { + MONITARR_USES_FUNCTION_NAME(); + + if (not*source_file_) { + throw utils::error::create_exception(function_name, { + "file open failed", + source_path, + }); + } + + encrypted_file_path_ = encrypted_file_path; + encrypted_file_name_ = utils::path::strip_to_file_name(encrypted_file_path_); + + auto opt_size = source_file_->size(); + if (not opt_size.has_value()) { + throw utils::error::create_exception(function_name, + { + "failed to get file size", + source_file_->get_path(), + }); + } + auto file_size = opt_size.value(); + + const auto total_chunks = utils::divide_with_ceiling( + file_size, static_cast(data_chunk_size_)); + total_size_ = file_size + (total_chunks * encryption_header_size); + last_data_chunk_ = total_chunks - 1U; + last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size + : (file_size % data_chunk_size_) == 0U + ? data_chunk_size_ + : file_size % data_chunk_size_; + iv_list_.resize(total_chunks); + for (auto &iv : iv_list_) { + randombytes_buf(iv.data(), iv.size()); + } +} + +encrypting_reader::encrypting_reader( + std::string_view encrypted_file_path, std::string_view source_path, + stop_type_callback stop_requested_cb, std::string_view token, + std::vector< + std::array> + iv_list, + std::size_t error_return) + : key_(utils::encryption::generate_key( + token)), + stop_requested_cb_(std::move(stop_requested_cb)), + error_return_(error_return), + source_file_(utils::file::file::open_or_create_file(source_path, true)) { + MONITARR_USES_FUNCTION_NAME(); + + if (not*source_file_) { + throw utils::error::create_exception(function_name, { + "file open failed", + source_path, + }); + } + + encrypted_file_path_ = encrypted_file_path; + encrypted_file_name_ = utils::path::strip_to_file_name(encrypted_file_path_); + + auto opt_size = source_file_->size(); + if (not opt_size.has_value()) { + throw utils::error::create_exception( + function_name, { + "get file size failed", + std::to_string(utils::get_last_error_code()), + source_file_->get_path(), + }); + } + auto file_size{opt_size.value()}; + + const auto total_chunks = utils::divide_with_ceiling( + file_size, static_cast(data_chunk_size_)); + total_size_ = file_size + (total_chunks * encryption_header_size); + last_data_chunk_ = total_chunks - 1U; + last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size + : (file_size % data_chunk_size_) == 0U + ? data_chunk_size_ + : file_size % data_chunk_size_; + iv_list_ = std::move(iv_list); +} + +encrypting_reader::encrypting_reader(const encrypting_reader &reader) + : key_(reader.key_), + stop_requested_cb_(reader.stop_requested_cb_), + error_return_(reader.error_return_), + source_file_( + utils::file::file::open_file(reader.source_file_->get_path(), true)), + chunk_buffers_(reader.chunk_buffers_), + encrypted_file_name_(reader.encrypted_file_name_), + encrypted_file_path_(reader.encrypted_file_path_), + iv_list_(reader.iv_list_), + last_data_chunk_(reader.last_data_chunk_), + last_data_chunk_size_(reader.last_data_chunk_size_), + read_offset_(reader.read_offset_), + total_size_(reader.total_size_) { + MONITARR_USES_FUNCTION_NAME(); + + if (not*source_file_) { + throw utils::error::create_exception( + function_name, { + "file open failed", + std::to_string(utils::get_last_error_code()), + source_file_->get_path(), + }); + } +} + +auto encrypting_reader::calculate_decrypted_size(std::uint64_t total_size) + -> std::uint64_t { + return total_size - (utils::divide_with_ceiling( + total_size, static_cast( + get_encrypted_chunk_size())) * + encryption_header_size); +} + +auto encrypting_reader::calculate_encrypted_size(std::string_view source_path) + -> std::uint64_t { + MONITARR_USES_FUNCTION_NAME(); + + auto opt_size = utils::file::file{source_path}.size(); + if (not opt_size.has_value()) { + throw utils::error::create_exception( + function_name, { + "get file size failed", + std::to_string(utils::get_last_error_code()), + source_path, + }); + } + auto file_size{opt_size.value()}; + + const auto total_chunks = utils::divide_with_ceiling( + file_size, static_cast(data_chunk_size_)); + return file_size + (total_chunks * encryption_header_size); +} + +auto encrypting_reader::create_iostream() const + -> std::shared_ptr { + return std::make_shared( + std::make_unique(*this)); +} + +auto encrypting_reader::reader_function(char *buffer, size_t size, + size_t nitems) -> size_t { + MONITARR_USES_FUNCTION_NAME(); + + const auto read_size = static_cast(std::min( + static_cast(size * nitems), total_size_ - read_offset_)); + + auto chunk = read_offset_ / encrypted_chunk_size_; + auto chunk_offset = read_offset_ % encrypted_chunk_size_; + std::size_t total_read{}; + + auto ret = false; + if (read_offset_ < total_size_) { + try { + ret = true; + auto remain = read_size; + while (not get_stop_requested() && ret && (remain != 0U)) { + if (chunk_buffers_.find(chunk) == chunk_buffers_.end()) { + auto &chunk_buffer = chunk_buffers_[chunk]; + data_buffer file_data(chunk == last_data_chunk_ + ? last_data_chunk_size_ + : data_chunk_size_); + chunk_buffer.resize(file_data.size() + encryption_header_size); + + std::size_t bytes_read{}; + if ((ret = source_file_->read(file_data, chunk * data_chunk_size_, + &bytes_read))) { + utils::encryption::encrypt_data(iv_list_.at(chunk), key_, file_data, + chunk_buffer); + } + } else if (chunk) { + chunk_buffers_.erase(chunk - 1u); + } + + auto &chunk_buffer = chunk_buffers_[chunk]; + const auto to_read = std::min( + static_cast(chunk_buffer.size() - chunk_offset), + remain); + std::memcpy(buffer + total_read, &chunk_buffer[chunk_offset], to_read); + total_read += to_read; + remain -= to_read; + chunk_offset = 0u; + chunk++; + read_offset_ += to_read; + } + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + ret = false; + } catch (...) { + utils::error::handle_exception(function_name); + ret = false; + } + } + + return get_stop_requested() ? static_cast(CURL_READFUNC_ABORT) + : ret ? total_read + : error_return_; +} +} // namespace monitarr::utils::encryption + +#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) diff --git a/support/src/utils/encryption.cpp b/support/src/utils/encryption.cpp new file mode 100644 index 0000000..bb5f7e4 --- /dev/null +++ b/support/src/utils/encryption.cpp @@ -0,0 +1,162 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + +#include "utils/encryption.hpp" + +#include "utils/collection.hpp" +#include "utils/encrypting_reader.hpp" +#include "utils/path.hpp" + +namespace monitarr::utils::encryption { +auto decrypt_file_path(std::string_view encryption_token, + std::string &file_path) -> bool { + std::vector decrypted_parts; + for (const auto &part : std::filesystem::path(file_path)) { + auto file_name = part.string(); + if (file_name == "/") { + continue; + } + + if (not decrypt_file_name(encryption_token, file_name)) { + return false; + } + + decrypted_parts.push_back(file_name); + } + + file_path = + utils::path::create_api_path(utils::string::join(decrypted_parts, '/')); + return true; +} + +auto decrypt_file_name(std::string_view encryption_token, + std::string &file_name) -> bool { + data_buffer buffer; + if (not utils::collection::from_hex_string(file_name, buffer)) { + return false; + } + + file_name.clear(); + return utils::encryption::decrypt_data(encryption_token, buffer, file_name); +} + +auto read_encrypted_range(const http_range &range, + const utils::encryption::hash_256_t &key, + reader_func_t reader_func, std::uint64_t total_size, + data_buffer &data) -> bool { + auto encrypted_chunk_size = + utils::encryption::encrypting_reader::get_encrypted_chunk_size(); + auto data_chunk_size = + utils::encryption::encrypting_reader::get_data_chunk_size(); + + auto start_chunk = static_cast(range.begin / data_chunk_size); + auto end_chunk = static_cast(range.end / data_chunk_size); + auto remain = range.end - range.begin + 1U; + auto source_offset = static_cast(range.begin % data_chunk_size); + + for (std::size_t chunk = start_chunk; chunk <= end_chunk; chunk++) { + data_buffer cypher; + auto start_offset = chunk * encrypted_chunk_size; + auto end_offset = std::min( + start_offset + (total_size - (chunk * data_chunk_size)) + + encryption_header_size - 1U, + static_cast(start_offset + encrypted_chunk_size - 1U)); + + if (not reader_func(cypher, start_offset, end_offset)) { + return false; + } + + data_buffer source_buffer; + if (not utils::encryption::decrypt_data(key, cypher, source_buffer)) { + return false; + } + cypher.clear(); + + auto data_size = static_cast(std::min( + remain, static_cast(data_chunk_size - source_offset))); + std::copy(std::next(source_buffer.begin(), + static_cast(source_offset)), + std::next(source_buffer.begin(), + static_cast(source_offset + data_size)), + std::back_inserter(data)); + remain -= data_size; + source_offset = 0U; + } + + return true; +} + +auto read_encrypted_range(const http_range &range, + const utils::encryption::hash_256_t &key, + reader_func_t reader_func, std::uint64_t total_size, + unsigned char *data, std::size_t size, + std::size_t &bytes_read) -> bool { + bytes_read = 0U; + + auto encrypted_chunk_size = + utils::encryption::encrypting_reader::get_encrypted_chunk_size(); + auto data_chunk_size = + utils::encryption::encrypting_reader::get_data_chunk_size(); + + auto start_chunk = static_cast(range.begin / data_chunk_size); + auto end_chunk = static_cast(range.end / data_chunk_size); + auto remain = range.end - range.begin + 1U; + auto source_offset = static_cast(range.begin % data_chunk_size); + + std::span dest_buffer(data, size); + for (std::size_t chunk = start_chunk; chunk <= end_chunk; chunk++) { + data_buffer cypher; + auto start_offset = chunk * encrypted_chunk_size; + auto end_offset = std::min( + start_offset + (total_size - (chunk * data_chunk_size)) + + encryption_header_size - 1U, + static_cast(start_offset + encrypted_chunk_size - 1U)); + + if (not reader_func(cypher, start_offset, end_offset)) { + return false; + } + + data_buffer source_buffer; + if (not utils::encryption::decrypt_data(key, cypher, source_buffer)) { + return false; + } + cypher.clear(); + + auto data_size = static_cast(std::min( + remain, static_cast(data_chunk_size - source_offset))); + std::copy( + std::next(source_buffer.begin(), + static_cast(source_offset)), + std::next(source_buffer.begin(), + static_cast(source_offset + data_size)), + std::next(dest_buffer.begin(), static_cast(bytes_read))); + remain -= data_size; + bytes_read += data_size; + source_offset = 0U; + } + + return true; +} +} // namespace monitarr::utils::encryption + +#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined (PROJECT_ENABLE_BOOST) diff --git a/support/src/utils/error.cpp b/support/src/utils/error.cpp new file mode 100644 index 0000000..fe9ddb1 --- /dev/null +++ b/support/src/utils/error.cpp @@ -0,0 +1,82 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "utils/error.hpp" + +namespace monitarr::utils::error { +std::atomic exception_handler{ + &default_exception_handler}; + +auto create_error_message(std::vector items) -> std::string { + std::stringstream stream{}; + for (std::size_t idx = 0U; idx < items.size(); ++idx) { + if (idx > 0) { + stream << '|'; + } + + stream << items.at(idx); + } + + return stream.str(); +} + +auto create_exception(std::string_view function_name, + std::vector items) + -> std::runtime_error { + items.insert(items.begin(), function_name); + return std::runtime_error(create_error_message(items)); +} + +void handle_error(std::string_view function_name, std::string_view msg) { + const i_exception_handler *handler{exception_handler}; + if (handler != nullptr) { + handler->handle_error(function_name, msg); + return; + } + + default_exception_handler.handle_error(function_name, msg); +} + +void handle_exception(std::string_view function_name) { + const i_exception_handler *handler{exception_handler}; + if (handler != nullptr) { + handler->handle_exception(function_name); + return; + } + + default_exception_handler.handle_exception(function_name); +} + +void handle_exception(std::string_view function_name, + const std::exception &ex) { + const i_exception_handler *handler{exception_handler}; + if (handler != nullptr) { + handler->handle_exception(function_name, ex); + return; + } + + default_exception_handler.handle_exception(function_name, ex); +} + +void set_exception_handler(const i_exception_handler *handler) { + exception_handler = handler; +} +} // namespace monitarr::utils::error diff --git a/support/src/utils/file.cpp b/support/src/utils/file.cpp new file mode 100644 index 0000000..6f3b6e6 --- /dev/null +++ b/support/src/utils/file.cpp @@ -0,0 +1,713 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "utils/file.hpp" + +#include "utils/common.hpp" +#include "utils/encryption.hpp" +#include "utils/error.hpp" +#include "utils/path.hpp" +#include "utils/string.hpp" +#include "utils/time.hpp" +#include "utils/unix.hpp" +#include "utils/windows.hpp" + +namespace monitarr::utils::file { +auto change_to_process_directory() -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { +#if defined(_WIN32) + std::string file_name; + file_name.resize(monitarr::max_path_length + 1U); + + ::GetModuleFileNameA(nullptr, file_name.data(), + static_cast(file_name.size() - 1U)); + auto path = utils::path::strip_to_file_name(file_name.c_str()); + ::SetCurrentDirectoryA(path.c_str()); +#else // !defined(_WIN32) + std::string path; + path.resize(PATH_MAX + 1); +#if defined(__APPLE__) + proc_pidpath(getpid(), path.c_str(), path.size()); +#else // !defined(__APPLE__) + auto res = readlink("/proc/self/exe", path.data(), path.size()); + if (res == -1) { + throw utils::error::create_exception( + function_name, { + "failed to readlink", + std::to_string(utils::get_last_error_code()), + path, + }); + } +#endif // defined(__APPLE__) + path = utils::path::get_parent_path(path); + res = chdir(path.c_str()); + if (res != 0) { + throw utils::error::create_exception( + function_name, { + "failed to chdir", + std::to_string(utils::get_last_error_code()), + path, + }); + } +#endif // defined(_WIN32) + + return true; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto create_temp_name(std::string_view file_part) -> std::string { + std::array data{ + utils::generate_random_between(0U, 9U), + utils::generate_random_between(0U, 9U), + utils::generate_random_between(0U, 9U), + utils::generate_random_between(0U, 9U), + utils::generate_random_between(0U, 9U), + utils::generate_random_between(0U, 9U), + utils::generate_random_between(0U, 9U), + utils::generate_random_between(0U, 9U), + }; + + return std::accumulate(data.begin(), data.end(), std::string{file_part} + '_', + [](auto &&name, auto &&val) -> auto { + return name + std::to_string(val); + }); +} + +auto create_temp_name(std::wstring_view file_part) -> std::wstring { + return utils::string::from_utf8( + create_temp_name(utils::string::to_utf8(file_part))); +} + +auto get_free_drive_space(std::string_view path) + -> std::optional { + MONITARR_USES_FUNCTION_NAME(); + + try { +#if defined(_WIN32) + ULARGE_INTEGER li{}; + if (not::GetDiskFreeSpaceEx(std::string{path}.c_str(), &li, nullptr, + nullptr)) { + throw utils::error::create_exception( + function_name, { + "failed to get free disk space", + std::to_string(utils::get_last_error_code()), + path, + }); + } + + return li.QuadPart; +#endif // defined(_WIN32) + +#if defined(__linux__) + struct statfs64 st{}; + if (statfs64(std::string{path}.c_str(), &st) != 0) { + throw utils::error::create_exception( + function_name, { + "failed to get free disk space", + std::to_string(utils::get_last_error_code()), + path, + }); + } + + return st.f_bfree * static_cast(st.f_bsize); +#endif // defined(__linux__) + +#if defined(__APPLE__) + struct statvfs st{}; + if (statvfs(path.c_str(), &st) != 0) { + throw utils::error::create_exception( + function_name, { + "failed to get free disk space", + std::to_string(utils::get_last_error_code()), + path, + }); + } + + return st.f_bfree * static_cast(st.f_frsize); +#endif // defined(__APPLE__) + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return std::nullopt; +} + +auto get_free_drive_space(std::wstring_view path) + -> std::optional { + return get_free_drive_space(utils::string::to_utf8(path)); +} + +auto get_time(std::string_view path, time_type type) + -> std::optional { + auto times = get_times(path); + if (times.has_value()) { + return times->get(type); + } + + return std::nullopt; +} + +auto get_time(std::wstring_view path, time_type type) + -> std::optional { + return get_time(utils::string::to_utf8(path), type); +} + +auto get_times(std::string_view path) -> std::optional { + MONITARR_USES_FUNCTION_NAME(); + + try { + file_times ret{}; + +#if defined(_WIN32) + auto file_handle = ::CreateFileA( + std::string{path}.c_str(), GENERIC_READ, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (file_handle != INVALID_HANDLE_VALUE) { + std::array times{}; + auto res = ::GetFileTime(file_handle, ×.at(0U), ×.at(1U), + ×.at(2U)); + ::CloseHandle(file_handle); + if (res) { + ret.accessed = + utils::time::windows_file_time_to_unix_time(times.at(1U)); + ret.created = utils::time::windows_file_time_to_unix_time(times.at(0U)); + ret.modified = + utils::time::windows_file_time_to_unix_time(times.at(2U)); + ret.written = utils::time::windows_file_time_to_unix_time(times.at(2U)); + return ret; + } + } + + struct _stat64 st{}; + if (_stat64(std::string{path}.c_str(), &st) != 0) { + throw utils::error::create_exception( + function_name, { + "failed to get file times", + std::to_string(utils::get_last_error_code()), + path, + }); + } + + ret.accessed = utils::time::windows_time_t_to_unix_time(st.st_atime); + ret.created = utils::time::windows_time_t_to_unix_time(st.st_ctime); + ret.modified = utils::time::windows_time_t_to_unix_time(st.st_mtime); + ret.written = utils::time::windows_time_t_to_unix_time(st.st_mtime); +#else // !defined(_WIN32) + struct stat64 st{}; + if (stat64(std::string{path}.c_str(), &st) != 0) { + throw utils::error::create_exception( + function_name, { + "failed to get file times", + std::to_string(utils::get_last_error_code()), + path, + }); + } + + ret.accessed = static_cast(st.st_atim.tv_nsec) + + static_cast(st.st_atim.tv_sec) * + utils::time::NANOS_PER_SECOND; + ret.created = static_cast(st.st_ctim.tv_nsec) + + static_cast(st.st_ctim.tv_sec) * + utils::time::NANOS_PER_SECOND; + ret.modified = static_cast(st.st_mtim.tv_nsec) + + static_cast(st.st_mtim.tv_sec) * + utils::time::NANOS_PER_SECOND; + ret.written = static_cast(st.st_mtim.tv_nsec) + + static_cast(st.st_mtim.tv_sec) * + utils::time::NANOS_PER_SECOND; + +#endif // defined(_WIN32) + + return ret; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return std::nullopt; +} + +auto get_times(std::wstring_view path) -> std::optional { + return get_times(utils::string::to_utf8(path)); +} + +auto get_total_drive_space(std::string_view path) + -> std::optional { + MONITARR_USES_FUNCTION_NAME(); + + try { +#if defined(_WIN32) + ULARGE_INTEGER li{}; + if (not::GetDiskFreeSpaceEx(std::string{path}.c_str(), nullptr, &li, + nullptr)) { + throw utils::error::create_exception( + function_name, { + "failed to get total disk space", + std::to_string(utils::get_last_error_code()), + path, + }); + } + + return li.QuadPart; +#endif // defined(_WIN32) + +#if defined(__linux__) + struct statfs64 st{}; + if (statfs64(std::string{path}.c_str(), &st) != 0) { + throw utils::error::create_exception( + function_name, { + "failed to get total disk space", + std::to_string(utils::get_last_error_code()), + path, + }); + } + + return st.f_blocks * static_cast(st.f_bsize); +#endif // defined(__linux__) + +#if defined(__APPLE__) + struct statvfs st{}; + if (statvfs(path.c_str(), &st) != 0) { + throw utils::error::create_exception( + function_name, { + "failed to get total disk space", + std::to_string(utils::get_last_error_code()), + path, + }); + } + + return st.f_blocks * static_cast(st.f_frsize); +#endif // defined(__APPLE__) + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return std::nullopt; +} + +auto get_total_drive_space(std::wstring_view path) + -> std::optional { + return get_total_drive_space(utils::string::to_utf8(path)); +} + +auto i_fs_item::get_time(time_type type) const -> std::optional { + return utils::file::get_time(get_path(), type); +} + +auto i_file::read_all(data_buffer &data, std::uint64_t offset, + std::size_t *total_read) -> bool { + data_buffer buffer; + buffer.resize(get_read_buffer_size()); + + std::size_t current_read{}; + while (read(reinterpret_cast(buffer.data()), + buffer.size() * sizeof(data_buffer::value_type), offset, + ¤t_read)) { + if (total_read != nullptr) { + *total_read += current_read; + } + + if (current_read != 0U) { + offset += current_read; + + data.insert( + data.end(), buffer.begin(), + std::next(buffer.begin(), + static_cast( + current_read / sizeof(data_buffer::value_type)))); + continue; + } + + return true; + } + + return false; +} + +#if defined(PROJECT_ENABLE_JSON) +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +auto read_json_file(std::string_view path, nlohmann::json &data, + std::optional password) -> bool { +#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +auto read_json_file(std::string_view path, nlohmann::json &data) -> bool { +#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + MONITARR_USES_FUNCTION_NAME(); + + try { + auto abs_path = utils::path::absolute(path); + auto file = file::open_file(abs_path); + if (not*file) { + return false; + } + + try { + data_buffer buffer{}; + if (not file->read_all(buffer, 0U)) { + return false; + } + +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + if (password.has_value()) { + data_buffer decrypted_data{}; + if (not utils::encryption::decrypt_data(*password, buffer, + decrypted_data)) { + return false; + } + + buffer = decrypted_data; + } +#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + + std::string json_str(buffer.begin(), buffer.end()); + if (not json_str.empty()) { + data = nlohmann::json::parse(json_str); + } + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + return false; + } catch (...) { + utils::error::handle_exception(function_name); + return false; + } + + return true; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +auto write_json_file(std::string_view path, const nlohmann::json &data, + std::optional password) -> bool { +#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +auto write_json_file(std::string_view path, const nlohmann::json &data) + -> bool { +#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + MONITARR_USES_FUNCTION_NAME(); + + try { + auto file = file::open_or_create_file(path); + if (not file->truncate()) { + throw utils::error::create_exception( + function_name, { + "failed to truncate file", + std::to_string(utils::get_last_error_code()), + path, + }); + } + +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + if (password.has_value()) { + const auto str_data = data.dump(2); + + data_buffer encrypted_data{}; + utils::encryption::encrypt_data( + *password, reinterpret_cast(str_data.c_str()), + str_data.size(), encrypted_data); + return file->write(encrypted_data, 0U); + } +#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + + auto json_str = data.dump(2); + return file->write( + reinterpret_cast(json_str.c_str()), + json_str.size(), 0U); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +auto read_json_file(std::wstring_view path, nlohmann::json &data, + std::optional password) -> bool { + if (password.has_value()) { + auto password_a = utils::string::to_utf8(*password); + return read_json_file(utils::string::to_utf8(path), data, password_a); + } + + return read_json_file(utils::string::to_utf8(path), data, std::nullopt); +} + +auto write_json_file(std::wstring_view path, const nlohmann::json &data, + std::optional password) -> bool { + if (password.has_value()) { + auto password_a = utils::string::to_utf8(*password); + return write_json_file(utils::string::to_utf8(path), data, password_a); + } + + return write_json_file(utils::string::to_utf8(path), data, std::nullopt); +} +#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +auto read_json_file(std::wstring_view path, nlohmann::json &data) -> bool { + return read_json_file(utils::string::to_utf8(path), data); +} + +auto write_json_file(std::wstring_view path, const nlohmann::json &data) + -> bool { + return write_json_file(utils::string::to_utf8(path), data); +} +#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +#endif // defined(PROJECT_ENABLE_JSON) + +#if defined(PROJECT_ENABLE_LIBDSM) +static constexpr const auto validate_smb_path = + [](std::string_view path) -> bool { + return (not utils::string::begins_with(path, "///") && + utils::string::begins_with(path, "//") && + // not utils::string::contains(path, " ") && + std::count(path.begin(), path.end(), '/') >= 3U); +}; + +auto smb_create_smb_path(std::string_view smb_path, std::string_view rel_path) + -> std::string { + MONITARR_USES_FUNCTION_NAME(); + + if (not validate_smb_path(smb_path)) { + throw utils::error::create_exception(function_name, { + "invalid smb path", + smb_path, + }); + } + + std::string path{rel_path}; + utils::path::format_path(path, "/", "\\"); + utils::string::left_trim(path, '/'); + + auto old_parts = + monitarr::utils::string::split(smb_path.substr(2U), '/', false); + + auto new_parts = monitarr::utils::string::split(path, '/', false); + old_parts.insert(old_parts.end(), new_parts.begin(), new_parts.end()); + + path = utils::string::join(old_parts, '/'); + path = "//" + utils::path::format_path(path, "/", "\\"); + + if (not validate_smb_path(path)) { + throw utils::error::create_exception(function_name, { + "invalid smb path", + path, + }); + } + + return path; +} + +auto smb_create_and_validate_relative_path(std::string_view smb_path, + std::string_view path) + -> std::string { + MONITARR_USES_FUNCTION_NAME(); + + if (not validate_smb_path(smb_path)) { + throw utils::error::create_exception(function_name, { + "invalid smb path", + smb_path, + }); + } + + std::string dir_path; + if (utils::string::begins_with(path, "//")) { + if (not utils::file::smb_parent_is_same(smb_path, path)) { + throw utils::error::create_exception(function_name, + { + "failed to validate path", + "parent paths are not the same", + smb_path, + path, + }); + } + + return utils::file::smb_create_relative_path(path); + } + + return utils::file::smb_create_relative_path(std::string{smb_path} + '/' + + std::string{path}); +} + +auto smb_create_relative_path(std::string_view smb_path) -> std::string { + MONITARR_USES_FUNCTION_NAME(); + + if (not validate_smb_path(smb_path)) { + throw utils::error::create_exception(function_name, { + "invalid smb path", + smb_path, + }); + } + + std::string path{smb_path}; + utils::path::format_path(path, "\\", "/"); + utils::string::left_trim(path, '\\'); + + auto parts = monitarr::utils::string::split(path, '\\', false); + parts.erase(parts.begin(), std::next(parts.begin(), 2U)); + return "\\" + utils::string::join(parts, '\\'); +} + +auto smb_create_search_path(std::string_view smb_path) -> std::string { + MONITARR_USES_FUNCTION_NAME(); + + if (not validate_smb_path(smb_path)) { + throw utils::error::create_exception(function_name, { + "invalid smb path", + smb_path, + }); + } + + std::string path{smb_path}; + utils::string::left_trim(path, '/'); + + auto parts = monitarr::utils::string::split(path, '/', false); + parts.erase(parts.begin(), std::next(parts.begin(), 2U)); + + auto search_path = monitarr::utils::string::join(parts, '\\'); + return search_path.empty() ? "\\*" : "\\" + search_path + "\\*"; +} + +auto smb_get_parent_path(std::string_view smb_path) -> std::string { + MONITARR_USES_FUNCTION_NAME(); + + if (not validate_smb_path(smb_path)) { + throw utils::error::create_exception(function_name, { + "invalid smb path", + smb_path, + }); + } + + auto parts = monitarr::utils::string::split(smb_path.substr(2U), '/', false); + if (parts.size() > 2U) { + parts.erase(std::prev(parts.end()), parts.end()); + } + + auto parent_smb_path = "//" + utils::string::join(parts, '/'); + if (not validate_smb_path(parent_smb_path)) { + throw utils::error::create_exception(function_name, + { + "invalid parent smb path", + parent_smb_path, + }); + } + + return parent_smb_path; +} + +auto smb_get_root_path(std::string_view smb_path) -> std::string { + MONITARR_USES_FUNCTION_NAME(); + + if (not validate_smb_path(smb_path)) { + throw utils::error::create_exception(function_name, { + "invalid smb path", + smb_path, + }); + } + + auto parts = monitarr::utils::string::split(smb_path.substr(2U), '/', false); + if (parts.size() > 2U) { + parts.erase(std::next(parts.begin(), 2U), parts.end()); + } + + return "//" + utils::string::join(parts, '/'); +} + +auto smb_get_unc_path(std::string_view smb_path) -> std::string { + MONITARR_USES_FUNCTION_NAME(); + + if (not validate_smb_path(smb_path)) { + throw utils::error::create_exception(function_name, { + "invalid smb path", + smb_path, + }); + } + + std::string unc_path{smb_path}; + utils::path::format_path(unc_path, "\\", "/"); + return '\\' + unc_path; +} + +auto smb_get_uri_path(std::string_view smb_path) -> std::string { + MONITARR_USES_FUNCTION_NAME(); + + if (not validate_smb_path(smb_path)) { + throw utils::error::create_exception(function_name, { + "invalid smb path", + smb_path, + }); + } + + return "smb:" + std::string{smb_path}; +} + +auto smb_get_uri_path(std::string_view smb_path, std::string_view user, + std::string_view password) -> std::string { + MONITARR_USES_FUNCTION_NAME(); + + if (not validate_smb_path(smb_path)) { + throw utils::error::create_exception(function_name, { + "invalid smb path", + smb_path, + }); + } + + return "smb://" + std::string{user} + ':' + std::string{password} + '@' + + std::string{smb_path.substr(2U)}; +} + +auto smb_parent_is_same(std::string_view smb_path1, std::string_view smb_path2) + -> bool { + if (not(validate_smb_path(smb_path1) && validate_smb_path(smb_path2))) { + return false; + } + + auto parts1 = utils::string::split(smb_path1.substr(2U), "/", false); + auto parts2 = utils::string::split(smb_path2.substr(2U), "/", false); + if (parts1.size() < 2U || parts2.size() < 2U) { + return false; + } + + if (parts2.at(1U).empty() || parts1.at(1U).empty()) { + return false; + } + + return std::equal(parts1.begin(), std::next(parts1.begin(), 2U), + parts2.begin()); +} +#endif // defined(PROJECT_ENABLE_LIBDSM) +} // namespace monitarr::utils::file diff --git a/support/src/utils/file_directory.cpp b/support/src/utils/file_directory.cpp new file mode 100644 index 0000000..99821db --- /dev/null +++ b/support/src/utils/file_directory.cpp @@ -0,0 +1,486 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "utils/file_directory.hpp" + +#include "utils/common.hpp" +#include "utils/error.hpp" +#include "utils/string.hpp" +#include "utils/unix.hpp" +#include "utils/windows.hpp" + +namespace { +auto traverse_directory( + std::string_view path, + std::function directory_action, + std::function file_action, + monitarr::stop_type *stop_requested) -> bool { + MONITARR_USES_FUNCTION_NAME(); + + auto res{true}; + + const auto is_stop_requested = [&stop_requested]() -> bool { + return (stop_requested != nullptr && *stop_requested); + }; + +#if defined(_WIN32) + WIN32_FIND_DATAA fd{}; + auto search = monitarr::utils::path::combine(path, {"*.*"}); + auto find = ::FindFirstFileA(search.c_str(), &fd); + if (find == INVALID_HANDLE_VALUE) { + throw monitarr::utils::error::create_exception( + function_name, + { + "failed to open directory", + std::to_string(monitarr::utils::get_last_error_code()), + path, + }); + } + + do { + if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if ((std::string_view(fd.cFileName) != ".") && + (std::string_view(fd.cFileName) != "..")) { + res = directory_action(monitarr::utils::file::directory{ + monitarr::utils::path::combine(path, {fd.cFileName}), + stop_requested, + }); + } + } else { + res = file_action(monitarr::utils::file::file( + monitarr::utils::path::combine(path, {fd.cFileName}))); + } + } while (res && (::FindNextFileA(find, &fd) != 0) && !is_stop_requested()); + + ::FindClose(find); +#else // !defined(_WIN32) + auto *root = opendir(std::string{path}.c_str()); + if (root == nullptr) { + throw monitarr::utils::error::create_exception( + function_name, + { + "failed to open directory", + std::to_string(monitarr::utils::get_last_error_code()), + path, + }); + } + + struct dirent *de{nullptr}; + while (res && (de = readdir(root)) && !is_stop_requested()) { + if (de->d_type == DT_DIR) { + if ((std::string_view(de->d_name) != ".") && + (std::string_view(de->d_name) != "..")) { + res = directory_action(monitarr::utils::file::directory( + monitarr::utils::path::combine(path, {de->d_name}))); + } + } else { + res = file_action(monitarr::utils::file::file( + monitarr::utils::path::combine(path, {de->d_name}))); + } + } + + closedir(root); +#endif // defined(_WIN32) + + return res; +} +} // namespace + +namespace monitarr::utils::file { +auto directory::copy_to(std::string_view new_path, + bool overwrite) const -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + throw utils::error::create_exception( + function_name, { + "failed to copy directory", + "not implemented", + utils::string::from_bool(overwrite), + new_path, + path_, + }); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto directory::count(bool recursive) const -> std::uint64_t { + MONITARR_USES_FUNCTION_NAME(); + + try { + std::uint64_t ret{0U}; + + traverse_directory( + path_, + [&ret, &recursive](auto dir_item) -> bool { + if (recursive) { + ret += dir_item.count(true); + } + + ++ret; + return true; + }, + [&ret](auto /* file_item */) -> bool { + ++ret; + return true; + }, + stop_requested_); + + return ret; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return 0U; +} + +auto directory::create_directory(std::string_view path) const + -> fs_directory_t { + MONITARR_USES_FUNCTION_NAME(); + + try { + auto abs_path = utils::path::combine(path_, {path}); + if (directory{abs_path, stop_requested_}.exists()) { + return std::make_unique(abs_path); + } + +#if defined(_WIN32) + auto res = ::SHCreateDirectory(nullptr, + utils::string::from_utf8(abs_path).c_str()); + if (res != ERROR_SUCCESS) { + throw utils::error::create_exception(function_name, + { + "failed to create directory", + std::to_string(res), + abs_path, + }); + } +#else // !defined(_WIN32) + auto ret{true}; + auto paths = + utils::string::split(abs_path, utils::path::directory_seperator, false); + + std::string current_path; + for (std::size_t idx = 0U; + !is_stop_requested() && ret && (idx < paths.size()); ++idx) { + if (paths.at(idx).empty()) { + current_path = utils::path::directory_seperator; + continue; + } + + current_path = utils::path::combine(current_path, {paths.at(idx)}); + auto status = mkdir(current_path.c_str(), S_IRWXU); + ret = ((status == 0) || (errno == EEXIST)); + } +#endif // defined(_WIN32) + + return std::make_unique(abs_path); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return nullptr; +} + +auto directory::exists() const -> bool { +#if defined(_WIN32) + return ::PathIsDirectoryA(path_.c_str()) != 0; +#else // !defined(_WIN32) + struct stat64 st {}; + return (stat64(path_.c_str(), &st) == 0 && S_ISDIR(st.st_mode)); +#endif // defined(_WIN32) + + return false; +} + +auto directory::get_directory(std::string_view path) const -> fs_directory_t { + MONITARR_USES_FUNCTION_NAME(); + + try { + auto dir_path = utils::path::combine(path_, {path}); + return fs_directory_t{ + directory{dir_path, stop_requested_}.exists() + ? new directory(dir_path, stop_requested_) + : nullptr, + }; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return nullptr; +} + +auto directory::get_directories() const -> std::vector { + MONITARR_USES_FUNCTION_NAME(); + + try { + std::vector ret{}; + + traverse_directory( + path_, + [this, &ret](auto dir_item) -> bool { + ret.emplace_back(fs_directory_t{ + new directory(dir_item.get_path(), stop_requested_), + }); + + return true; + }, + [](auto /* file_item */) -> bool { return true; }, stop_requested_); + + return ret; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return {}; +} + +auto directory::create_file(std::string_view file_name, + bool read_only) const -> fs_file_t { + MONITARR_USES_FUNCTION_NAME(); + + try { + auto file_path = utils::path::combine(path_, {file_name}); + return file::open_or_create_file(file_path, read_only); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return nullptr; +} + +auto directory::get_file(std::string_view path) const -> fs_file_t { + MONITARR_USES_FUNCTION_NAME(); + + try { + auto file_path = utils::path::combine(path_, {path}); + return fs_file_t{ + file{file_path}.exists() ? new file(file_path) : nullptr, + }; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return nullptr; +} + +auto directory::get_files() const -> std::vector { + MONITARR_USES_FUNCTION_NAME(); + + try { + std::vector ret{}; + + traverse_directory( + path_, [](auto /* dir_item */) -> bool { return true; }, + [&ret](auto file_item) -> bool { + ret.emplace_back(fs_file_t{ + new file(file_item.get_path()), + }); + return true; + }, + stop_requested_); + + return ret; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return {}; +} + +auto directory::get_items() const -> std::vector { + MONITARR_USES_FUNCTION_NAME(); + + try { + std::vector ret{}; + + traverse_directory( + path_, + [this, &ret](auto dir_item) -> bool { + ret.emplace_back(fs_item_t{ + new directory(dir_item.get_path(), stop_requested_), + }); + return true; + }, + [&ret](auto file_item) -> bool { + ret.emplace_back(fs_item_t{ + new file(file_item.get_path()), + }); + return true; + }, + stop_requested_); + + return ret; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return {}; +} + +auto directory::is_stop_requested() const -> bool { + return (stop_requested_ != nullptr) && *stop_requested_; +} + +auto directory::is_symlink() const -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + return std::filesystem::is_symlink(path_); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto directory::move_to(std::string_view new_path) -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + throw utils::error::create_exception(function_name, + { + "failed to move directory", + "not implemented", + new_path, + path_, + }); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto directory::remove() -> bool { + MONITARR_USES_FUNCTION_NAME(); + + return utils::retry_action([this]() -> bool { + try { +#if defined(_WIN32) + auto ret = not exists() || (::RemoveDirectoryA(path_.c_str()) != 0); +#else // !defined(_WIN32) + auto ret = not exists() || (rmdir(path_.c_str()) == 0); +#endif // defined(_WIN32) + if (not ret) { + utils::error::handle_error(function_name, + utils::error::create_error_message({ + "failed to remove directory", + path_, + })); + } + + return ret; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; + }); +} + +auto directory::remove_recursively() -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not exists()) { + return true; + } + + if (not traverse_directory( + path_, + [](auto dir_item) -> bool { return dir_item.remove_recursively(); }, + [](auto file_item) -> bool { return file_item.remove(); }, + stop_requested_)) { + return false; + } + + return remove(); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto directory::size(bool recursive) const -> std::uint64_t { + MONITARR_USES_FUNCTION_NAME(); + + try { + std::uint64_t ret{0U}; + + traverse_directory( + path_, + [&ret, &recursive](auto dir_item) -> bool { + if (recursive) { + ret += dir_item.size(true); + } + + return true; + }, + [&ret](auto file_item) -> bool { + auto cur_size = file_item.size(); + if (cur_size.has_value()) { + ret += cur_size.value(); + } + return true; + }, + stop_requested_); + + return ret; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return 0U; +} +} // namespace monitarr::utils::file diff --git a/support/src/utils/file_enc_file.cpp b/support/src/utils/file_enc_file.cpp new file mode 100644 index 0000000..47e8bc1 --- /dev/null +++ b/support/src/utils/file_enc_file.cpp @@ -0,0 +1,188 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + +#include "utils/file_enc_file.hpp" + +#include "utils/common.hpp" +#include "utils/encrypting_reader.hpp" +#include "utils/encryption.hpp" + +namespace monitarr::utils::file { +auto enc_file::attach_file(fs_file_t file) -> fs_file_t { + return fs_file_t{ + new enc_file(std::move(file)), + }; +} + +enc_file::enc_file(fs_file_t file) : file_(std::move(file)) {} + +void enc_file::close() { file_->close(); } + +auto enc_file::copy_to(std::string_view new_path, bool overwrite) const + -> bool { + return file_->copy_to(new_path, overwrite); +} + +void enc_file::flush() const { return file_->flush(); } + +auto enc_file::move_to(std::string_view path) -> bool { + return file_->move_to(path); +} + +auto enc_file::read(unsigned char *data, std::size_t to_read, + std::uint64_t offset, std::size_t *total_read) -> bool { + if (total_read != nullptr) { + *total_read = 0U; + } + + auto file_size{size()}; + if (not file_size.has_value()) { + return false; + } + + to_read = utils::calculate_read_size(file_size.value(), to_read, offset); + if (to_read == 0U) { + return true; + } + + std::size_t bytes_read{}; + auto ret{ + utils::encryption::read_encrypted_range( + {offset, offset + to_read - 1U}, + utils::encryption::generate_key( + encryption_token_), + [&](auto &&ct_buffer, auto &&start_offset, + auto &&end_offset) -> bool { + ct_buffer.resize(end_offset - start_offset + 1U); + return file_->read(ct_buffer, start_offset); + }, + file_size.value(), data, to_read, bytes_read), + }; + if (ret && total_read != nullptr) { + *total_read = bytes_read; + } + + return ret; +} + +auto enc_file::remove() -> bool { return file_->remove(); } + +auto enc_file::truncate(std::size_t size) -> bool { + if (size == 0U) { + return file_->truncate(size); + } + + auto file_size{this->size()}; + if (not file_size.has_value()) { + return false; + } + + if (size == file_size.value()) { + return true; + } + + auto chunks{ + size / utils::encryption::encrypting_reader::get_data_chunk_size(), + }; + auto real_size{ + (chunks * utils::encryption::encrypting_reader::get_data_chunk_size()) + + (chunks * utils::encryption::encrypting_reader::get_header_size()), + }; + auto remain{ + size % utils::encryption::encrypting_reader::get_data_chunk_size(), + }; + if (remain > 0U) { + real_size += + (remain + utils::encryption::encrypting_reader::get_header_size()); + } + + if (size < file_size.value()) { + if (remain == 0U) { + return file_->truncate(real_size); + } + + auto begin_chunk{ + size / utils::encryption::encrypting_reader::get_data_chunk_size(), + }; + + auto offset{ + begin_chunk * + utils::encryption::encrypting_reader::get_data_chunk_size(), + }; + + std::size_t total_read{}; + data_buffer data( + utils::encryption::encrypting_reader::get_data_chunk_size()); + if (not i_file::read(data, offset, &total_read)) { + return false; + } + data.resize(remain); + + if (not file_->truncate(real_size)) { + return false; + } + + return i_file::write(data, offset); + } + + auto begin_chunk{ + file_size.value() / + utils::encryption::encrypting_reader::get_data_chunk_size(), + }; + auto end_chunk{ + utils::divide_with_ceiling( + file_size.value(), + utils::encryption::encrypting_reader::get_data_chunk_size()), + }; + + return false; +} + +auto enc_file::write(const unsigned char *data, std::size_t to_write, + std::size_t offset, std::size_t *total_written) -> bool { + auto file_size{size()}; + if (not file_size.has_value()) { + return false; + } + + if ((offset + to_write) > file_size.value()) { + if (not truncate((offset + to_write) - file_size.value())) { + return false; + } + } + + return false; +} + +auto enc_file::size() const -> std::optional { + auto file_size = file_->size(); + if (not file_size.has_value()) { + return std::nullopt; + } + + return utils::encryption::encrypting_reader::calculate_decrypted_size( + file_size.value()); +} +} // namespace monitarr::utils::file + +#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) diff --git a/support/src/utils/file_file.cpp b/support/src/utils/file_file.cpp new file mode 100644 index 0000000..5038cca --- /dev/null +++ b/support/src/utils/file_file.cpp @@ -0,0 +1,594 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "utils/file_file.hpp" + +#include "utils/collection.hpp" +#include "utils/common.hpp" +#include "utils/error.hpp" +#include "utils/path.hpp" + +namespace { +[[nodiscard]] auto get_file_size(std::string_view path, + std::uint64_t &file_size) -> bool { + auto abs_path = monitarr::utils::path::absolute(path); + file_size = 0U; + +#if defined(_WIN32) + struct _stat64 st{}; + auto res = _stat64(std::string{path}.c_str(), &st); + if (res != 0) { + return false; + } + + file_size = static_cast(st.st_size); + return true; +#else // !defined(_WIN32) + std::error_code ec{}; + file_size = std::filesystem::file_size(abs_path, ec); + return (ec.value() == 0); +#endif // defined(_WIN32) +} + +[[nodiscard]] auto is_file(std::string_view path) -> bool { + auto abs_path = monitarr::utils::path::absolute(path); + +#if defined(_WIN32) + return ((::PathFileExistsA(abs_path.c_str()) != 0) && + (::PathIsDirectoryA(abs_path.c_str()) == 0)); +#else // !defined(_WIN32) + struct stat64 st{}; + return (stat64(abs_path.c_str(), &st) == 0 && not S_ISDIR(st.st_mode)); +#endif // defined(_WIN32) +} +} // namespace + +namespace monitarr::utils::file { +// auto file::attach_file(native_handle handle, +// bool read_only) -> fs_file_t { +// MONITARR_USES_FUNCTION_NAME(); +// +// try { +// std::string path; +// +// #if defined(_WIN32) +// path.resize(monitarr::max_path_length + 1U); +// ::GetFinalPathNameByHandleA(handle, path.data(), +// static_cast(path.size()), +// FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); +// #else // !defined(_WIN32) +// path.resize(monitarr::max_path_length + 1U); +// +// #if defined(__APPLE__) +// fcntl(handle, F_GETPATH, source_path.data()); +// #else // !defined(__APPLE__) +// readlink(("/proc/self/fd/" + std::to_string(handle)).c_str(), +// path.data(), +// path.size()); +// #endif // defined(__APPLE__) +// #endif // defined(_WIN32) +// +// path = path.c_str(); +// +// #if defined(_WIN32) +// auto *ptr = _fdopen( +// static_cast(_open_osfhandle(reinterpret_cast(handle), +// read_only ? _O_RDONLY : _O_RDWR)), +// read_only ? "rb" : "rb+"); +// #else // !defined(_WIN32) +// auto *ptr = fdopen(handle, read_only ? "rb" : "rb+"); +// #endif // defined(_WIN32) +// +// return fs_file_t(new file{ +// file_t{ptr}, +// utils::path::absolute(path), +// read_only, +// }); +// } catch (const std::exception &e) { +// utils::error::handle_exception(function_name, e); +// } catch (...) { +// utils::error::handle_exception(function_name); +// } +// +// return nullptr; +// } + +void file::open() { + MONITARR_USES_FUNCTION_NAME(); + + if (not is_file(path_)) { + throw utils::error::create_exception(function_name, { + "file not found", + path_, + }); + } + +#if defined(_WIN32) + file_ = file_t{ + _fsopen(path_.c_str(), read_only_ ? "rb" : "rb+", _SH_DENYNO), + file_deleter(), + }; +#else // !defined(_WIN32) + file_ = file_t{ + fopen(path_.c_str(), read_only_ ? "rb" : "rb+"), + file_deleter(), + }; +#endif // defined(_WIN32) +} + +auto file::open_file(std::string_view path, bool read_only) -> fs_file_t { + MONITARR_USES_FUNCTION_NAME(); + + auto *ptr = new file{ + nullptr, + utils::path::absolute(path), + read_only, + }; + auto new_file = fs_file_t(ptr); + + try { + ptr->open(); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return new_file; +} + +auto file::open_or_create_file(std::string_view path, bool read_only) + -> fs_file_t { + auto abs_path = utils::path::absolute(path); + if (not is_file(abs_path)) { +#if defined(_WIN32) + int old_mode{}; + _umask_s(077, &old_mode); +#else // !defined(_WIN32) + auto old_mode = umask(077); +#endif // defined(_WIN32) + +#if defined(_WIN32) + auto *ptr = _fsopen(abs_path.c_str(), "ab+", _SH_DENYNO); +#else // !defined(_WIN32) + auto *ptr = fopen(abs_path.c_str(), "ab+"); +#endif // defined(_WIN32) + + if (ptr != nullptr) { + fclose(ptr); + } + +#if defined(_WIN32) + _umask_s(old_mode, nullptr); +#else // !defined(_WIN32) + umask(old_mode); +#endif // defined(_WIN32) + } + + return open_file(abs_path, read_only); +} + +void file::close() { file_.reset(); } + +auto file::copy_to(std::string_view new_path, bool overwrite) const -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + auto to_path = utils::path::absolute(new_path); + if (directory(to_path).exists()) { + return false; + } + +#if defined(_WIN32) + return ::CopyFileA(path_.c_str(), to_path.c_str(), + overwrite ? TRUE : FALSE) != 0; +#else // !defined(_WIN32) + return std::filesystem::copy_file( + path_, to_path, + overwrite ? std::filesystem::copy_options::overwrite_existing + : std::filesystem::copy_options::skip_existing); +#endif // defined(_WIN32) + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto file::exists() const -> bool { return is_file(path_); } + +void file::flush() const { + if (file_) { + fflush(file_.get()); + } +} + +auto file::get_handle() const -> native_handle { + if (file_) { +#if defined(_WIN32) + return reinterpret_cast( + _get_osfhandle(_fileno(file_.get()))); +#else // !defined(_WIN32) + return fileno(file_.get()); +#endif // defined(_WIN32) + } + + return INVALID_HANDLE_VALUE; +} + +auto file::is_symlink() const -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + return std::filesystem::is_symlink(path_); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto file::move_to(std::string_view path) -> bool { + MONITARR_USES_FUNCTION_NAME(); + + auto abs_path = utils::path::absolute(path); + + auto reopen{false}; + if (file_) { + reopen = true; + close(); + } + + auto success{false}; +#if defined(_WIN32) + success = ::MoveFileExA(path_.c_str(), abs_path.c_str(), + MOVEFILE_REPLACE_EXISTING) != 0; +#else // !// defined(_WIN32) + std::error_code ec{}; + std::filesystem::rename(path_, abs_path, ec); + success = ec.value() == 0; +#endif // defined(_WIN32) + + if (success) { + path_ = abs_path; + } + + if (reopen) { + try { + open(); + return success; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + } + + return false; +} + +auto file::read(unsigned char *data, std::size_t to_read, std::uint64_t offset, + std::size_t *total_read) -> bool { + MONITARR_USES_FUNCTION_NAME(); + + if (total_read != nullptr) { + (*total_read) = 0U; + } + + try { + if (not file_) { + throw utils::error::create_exception(function_name, + { + "file is not open for reading", + path_, + }); + } + + if (fseeko(file_.get(), static_cast(offset), SEEK_SET) == + -1) { + throw utils::error::create_exception(function_name, + { + "failed to seek before read", + path_, + }); + } + + std::size_t bytes_read{0U}; + while (bytes_read != to_read) { + auto res = + fread(&data[bytes_read], 1U, to_read - bytes_read, file_.get()); + if (not feof(file_.get()) && ferror(file_.get())) { + throw utils::error::create_exception(function_name, + { + "failed to read file bytes", + path_, + }); + } + + if (res == 0) { + break; + } + + bytes_read += static_cast(res); + } + + if (total_read != nullptr) { + (*total_read) = bytes_read; + } + + return true; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +#if defined(PROJECT_ENABLE_LIBSODIUM) +auto file::sha256() -> std::optional { + MONITARR_USES_FUNCTION_NAME(); + + auto should_close{false}; + auto read_only{read_only_}; + std::optional ret; + + try { + if (file_ == nullptr) { + should_close = true; + read_only_ = true; + this->open(); + } + + crypto_hash_sha256_state state{}; + auto res = crypto_hash_sha256_init(&state); + if (res != 0) { + throw utils::error::create_exception(function_name, + { + "failed to initialize sha256", + std::to_string(res), + path_, + }); + } + + { + data_buffer buffer(get_read_buffer_size()); + std::uint64_t read_offset{0U}; + std::size_t bytes_read{0U}; + while (i_file::read(buffer, read_offset, &bytes_read)) { + if (not bytes_read) { + break; + } + + read_offset += bytes_read; + res = crypto_hash_sha256_update( + &state, reinterpret_cast(buffer.data()), + bytes_read); + if (res != 0) { + throw utils::error::create_exception(function_name, + { + "failed to update sha256", + std::to_string(res), + path_, + }); + } + } + } + + std::array out{}; + res = crypto_hash_sha256_final(&state, out.data()); + if (res != 0) { + throw utils::error::create_exception(function_name, + { + "failed to finalize sha256", + std::to_string(res), + path_, + }); + } + + ret = utils::collection::to_hex_string(out); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + if (should_close) { + read_only_ = read_only; + close(); + } + + return ret; +} +#endif // defined(PROJECT_ENABLE_LIBSODIUM) + +auto file::remove() -> bool { + MONITARR_USES_FUNCTION_NAME(); + + close(); + + return utils::retry_action([this]() -> bool { + try { +#if defined(_WIN32) + auto ret = not exists() || (::DeleteFileA(path_.c_str()) != 0); +#else // !defined(_WIN32) + std::error_code ec{}; + auto ret = not exists() || std::filesystem::remove(path_, ec); +#endif // defined(_WIN32) + if (not ret) { + utils::error::handle_error(function_name, + utils::error::create_error_message({ + "failed to remove file", + path_, + })); + } + + return ret; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; + }); +} + +auto file::truncate(std::size_t size) -> bool { + MONITARR_USES_FUNCTION_NAME(); + + auto reopen{false}; + if (file_) { + reopen = true; + close(); + } + + std::error_code ec{}; + std::filesystem::resize_file(path_, size, ec); + + auto success{ec.value() == 0}; + + if (reopen) { + try { + open(); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + success = false; + } catch (...) { + utils::error::handle_exception(function_name); + success = false; + } + } + + return success; +} + +auto file::write(const unsigned char *data, std::size_t to_write, + std::size_t offset, std::size_t *total_written) -> bool { + MONITARR_USES_FUNCTION_NAME(); + + if (total_written != nullptr) { + (*total_written) = 0U; + } + + try { + if (not file_) { + throw utils::error::create_exception(function_name, + { + "file is not open for writing", + path_, + }); + } + + auto res = fseeko(file_.get(), static_cast(offset), SEEK_SET); + if (res == -1) { + throw utils::error::create_exception(function_name, + { + "failed to seek before write", + path_, + }); + } + + std::size_t bytes_written{0U}; + while (bytes_written != to_write) { + auto written = + fwrite(reinterpret_cast(&data[bytes_written]), 1U, + to_write - bytes_written, file_.get()); + if (not feof(file_.get()) && ferror(file_.get())) { + throw utils::error::create_exception(function_name, + { + "failed to write file bytes", + path_, + }); + } + + if (written == 0U) { + break; + } + + bytes_written += written; + } + + flush(); + + if (total_written != nullptr) { + (*total_written) = bytes_written; + } + + return true; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto file::size() const -> std::optional { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (file_) { + if (fseeko(file_.get(), 0, SEEK_END) == -1) { + throw utils::error::create_exception(function_name, + { + "failed to seek", + path_, + }); + } + + auto size = ftello(file_.get()); + if (size == -1) { + throw utils::error::create_exception(function_name, + { + "failed to get position", + path_, + }); + } + + return static_cast(size); + } + + std::uint64_t size{}; + if (not get_file_size(path_, size)) { + throw utils::error::create_exception(function_name, + { + "failed to get file size", + path_, + }); + } + + return size; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return std::nullopt; +} +} // namespace monitarr::utils::file diff --git a/support/src/utils/file_smb_directory.cpp b/support/src/utils/file_smb_directory.cpp new file mode 100644 index 0000000..41af706 --- /dev/null +++ b/support/src/utils/file_smb_directory.cpp @@ -0,0 +1,789 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#if defined(PROJECT_ENABLE_LIBDSM) + +#include "utils/file_smb_directory.hpp" + +#include "utils/common.hpp" +#include "utils/error.hpp" +#include "utils/unix.hpp" +#include "utils/windows.hpp" + +namespace monitarr::utils::file { +auto smb_directory::open(std::string_view host, std::string_view user, + std::string_view password, std::string_view path, + stop_type *stop_requested) -> smb_directory_t { + MONITARR_USES_FUNCTION_NAME(); + + try { + smb_session_t session{ + smb_session_new(), + smb_session_deleter, + }; + netbios_ns_t ns{ + netbios_ns_new(), + netbios_ns_deleter(), + }; + + sockaddr_in addr{}; + + auto res = netbios_ns_resolve( + ns.get(), std::string{host}.c_str(), NETBIOS_FILESERVER, + reinterpret_cast(&addr.sin_addr.s_addr)); + if (res != DSM_SUCCESS) { + res = inet_pton(AF_INET, std::string{host}.c_str(), &addr.sin_addr); + if (res != 1) { + throw utils::error::create_exception( + function_name, { + "failed to resolve host", + std::to_string(utils::get_last_error_code()), + host, + }); + } + } + + res = smb_session_connect(session.get(), std::string{host}.c_str(), + static_cast(addr.sin_addr.s_addr), + SMB_TRANSPORT_TCP); + if (res != DSM_SUCCESS) { + throw utils::error::create_exception(function_name, + { + "failed to connect to host", + std::to_string(res), + host, + }); + } + + smb_session_set_creds(session.get(), std::string{host}.c_str(), + std::string{user}.c_str(), + std::string{password}.c_str()); + res = smb_session_login(session.get()); + if (res != DSM_SUCCESS) { + throw utils::error::create_exception(function_name, + { + "failed to logon to host", + std::to_string(res), + host, + user, + }); + } + + auto share_name = utils::string::split(path, '/', false).at(0U); + + smb_tid tid{}; + res = smb_tree_connect(session.get(), share_name.c_str(), &tid); + if (res != DSM_SUCCESS) { + throw utils::error::create_exception(function_name, + { + "failed to connect to share", + std::to_string(res), + share_name, + }); + } + + return smb_directory_t{ + new smb_directory{ + "//" + std::string{host} + "/" + std::string{path}, + session, + share_name, + tid, + stop_requested, + }, + }; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return nullptr; +} + +auto smb_directory::open(std::wstring_view host, std::wstring_view user, + std::wstring_view password, std::wstring_view path, + stop_type *stop_requested) -> smb_directory_t { + return open(utils::string::to_utf8(host), utils::string::to_utf8(user), + utils::string::to_utf8(password), utils::string::to_utf8(path), + stop_requested); +} + +auto smb_directory::copy_to(std::string_view new_path, + bool /* overwrite */) const -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + // auto to_path = utils::path::absolute(new_path); + + throw utils::error::create_exception(function_name, + { + "failed to copy directory", + "not implemented", + new_path, + path_, + }); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto smb_directory::count(bool recursive) const -> std::uint64_t { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + smb_stat_list_t list{ + smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()), + smb_stat_list_deleter(), + }; + auto count = smb_stat_list_count(list.get()); + + if (not recursive) { + return count; + } + + throw utils::error::create_exception( + function_name, { + "failed to get directory count recursively", + "not implemented", + utils::string::from_bool(recursive), + path_, + }); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return 0U; +} + +auto smb_directory::create_directory(std::string_view path) const + -> fs_directory_t { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + auto dir = get_directory(path); + if (dir) { + return dir; + } + + auto res = smb_directory_create( + session_.get(), tid_, + smb_create_and_validate_relative_path(path_, path).c_str()); + if (res != DSM_SUCCESS) { + throw utils::error::create_exception(function_name, + { + "failed to create directory", + std::to_string(res), + path_, + }); + } + + return get_directory(path); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return nullptr; +} + +auto smb_directory::create_file(std::string_view file_name, + bool read_only) const -> fs_file_t { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + auto fs_file = get_file(file_name); + if (fs_file) { + if (not dynamic_cast(fs_file.get())->open(read_only)) { + throw utils::error::create_exception(function_name, + { + "failed to open existing file", + file_name, + }); + } + + return fs_file; + } + + auto rel_path = smb_create_and_validate_relative_path(path_, file_name); + + smb_fd fd{}; + auto res = + smb_fopen(session_.get(), tid_, rel_path.c_str(), SMB_MOD_RW, &fd); + if (res != DSM_SUCCESS) { + return nullptr; + } + smb_fclose(session_.get(), fd); + + res = smb_fopen(session_.get(), tid_, rel_path.c_str(), + read_only ? SMB_MOD_RO : SMB_MOD_RW2, &fd); + if (res != DSM_SUCCESS) { + return nullptr; + } + + return std::make_unique( + fd, smb_create_smb_path(path_, std::string{rel_path}), session_, + share_name_, tid_); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return nullptr; +} +auto smb_directory::exists() const -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + smb_stat_t st{ + smb_fstat(session_.get(), tid_, + smb_create_relative_path(path_).c_str()), + smb_stat_deleter(), + }; + if (not st) { + return false; + } + + return smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto smb_directory::get_directory(std::string_view path) const + -> fs_directory_t { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + auto rel_path = smb_create_and_validate_relative_path(path_, path); + smb_stat_t st{ + smb_fstat(session_.get(), tid_, rel_path.c_str()), + smb_stat_deleter(), + }; + if (not st) { + throw utils::error::create_exception(function_name, + { + "failed to stat directory", + rel_path, + path_, + }); + } + + bool is_dir{smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U}; + if (not is_dir) { + throw utils::error::create_exception(function_name, + { + "path is not a directory", + rel_path, + path_, + }); + } + + return smb_directory_t{ + new smb_directory{ + smb_create_smb_path(path_, rel_path), + session_, + share_name_, + tid_, + stop_requested_, + }, + }; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return nullptr; +} + +auto smb_directory::get_directories() const -> std::vector { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + smb_stat_list_t list{ + smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()), + + smb_stat_list_deleter(), + }; + if (not list) { + throw utils::error::create_exception(function_name, + { + "failed to get directory list", + path_, + }); + } + + std::vector ret{}; + + auto count = smb_stat_list_count(list.get()); + for (std::size_t idx = 0U; !is_stop_requested() && idx < count; ++idx) { + auto *item_st = smb_stat_list_at(list.get(), idx); + + bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U}; + if (not is_dir) { + continue; + } + + std::string name{smb_stat_name(item_st)}; + if (name == "." || name == "..") { + continue; + } + + try { + ret.emplace_back(smb_directory_t{ + new smb_directory{ + smb_create_smb_path(path_, name), + session_, + share_name_, + tid_, + stop_requested_, + }, + }); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + } + + return ret; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return {}; +} + +auto smb_directory::get_file(std::string_view path) const -> fs_file_t { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + auto rel_path = smb_create_and_validate_relative_path(path_, path); + smb_stat_t st{ + smb_fstat(session_.get(), tid_, rel_path.c_str()), + smb_stat_deleter(), + }; + if (not st) { + throw utils::error::create_exception(function_name, + { + "failed to stat file", + rel_path, + path_, + }); + } + + bool is_dir{smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U}; + if (is_dir) { + throw utils::error::create_exception(function_name, + { + "path is not a file", + rel_path, + path_, + }); + } + + return std::make_unique( + std::nullopt, smb_create_smb_path(path_, std::string{rel_path}), + session_, share_name_, tid_); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return nullptr; +} + +auto smb_directory::get_files() const -> std::vector { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + smb_stat_list_t list{ + smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()), + smb_stat_list_deleter(), + }; + if (not list) { + throw utils::error::create_exception(function_name, + { + "failed to get file list", + path_, + }); + } + + std::vector ret{}; + + auto count = smb_stat_list_count(list.get()); + for (std::size_t idx = 0U; !is_stop_requested() && idx < count; ++idx) { + auto *item_st = smb_stat_list_at(list.get(), idx); + + bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U}; + if (is_dir) { + continue; + } + + try { + std::string name{smb_stat_name(item_st)}; + ret.emplace_back(std::make_unique( + std::nullopt, smb_create_smb_path(path_, name), session_, + share_name_, tid_)); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + } + + return ret; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return {}; +} + +auto smb_directory::get_items() const -> std::vector { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + smb_stat_list_t list{ + smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()), + smb_stat_list_deleter(), + }; + if (not list) { + throw utils::error::create_exception(function_name, + { + "failed to get item list", + path_, + }); + } + std::vector ret{}; + + auto count = smb_stat_list_count(list.get()); + for (std::size_t idx = 0U; !is_stop_requested() && idx < count; ++idx) { + auto *item_st = smb_stat_list_at(list.get(), idx); + + bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U}; + std::string name{smb_stat_name(item_st)}; + + if (is_dir) { + if (name == "." || name == "..") { + continue; + } + + try { + ret.emplace_back(smb_directory_t{ + new smb_directory{ + path_ + '/' + name, + session_, + share_name_, + tid_, + stop_requested_, + }, + }); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + continue; + } + + try { + ret.emplace_back(std::make_unique( + std::nullopt, smb_create_smb_path(path_, name), session_, + share_name_, tid_)); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + } + + return ret; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return {}; +} + +auto smb_directory::get_time(time_type type) const + -> std::optional { + return smb_file::get_time(session_.get(), tid_, path_, type); +} + +auto smb_directory::is_stop_requested() const -> bool { + return (stop_requested_ != nullptr) && *stop_requested_; +} + +auto smb_directory::is_symlink() const -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto smb_directory::move_to(std::string_view new_path) -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + throw utils::error::create_exception(function_name, + { + "failed to move directory", + "not implemented", + new_path, + path_, + }); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto smb_directory::remove() -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + return utils::retry_action([this]() -> bool { + if (not exists()) { + return true; + } + + try { + auto res = smb_directory_rm(session_.get(), tid_, + smb_create_relative_path(path_).c_str()); + if (res != DSM_SUCCESS) { + throw utils::error::create_exception(function_name, + { + "failed to remove directory", + std::to_string(res), + path_, + }); + } + + return true; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; + }); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto smb_directory::remove_recursively() -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + if (not exists()) { + return true; + } + + throw utils::error::create_exception( + function_name, { + "failed to remove directory recursively", + "not implemented", + path_, + }); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto smb_directory::size(bool /* recursive */) const -> std::uint64_t { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + throw utils::error::create_exception(function_name, + { + "failed to get directory size", + "not implemented", + path_, + }); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} +} // namespace monitarr::utils::file + +#endif // defined(PROJECT_ENABLE_LIBDSM) diff --git a/support/src/utils/file_smb_file.cpp b/support/src/utils/file_smb_file.cpp new file mode 100644 index 0000000..138aa10 --- /dev/null +++ b/support/src/utils/file_smb_file.cpp @@ -0,0 +1,549 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#if defined(PROJECT_ENABLE_LIBDSM) + +#include "utils/file_smb_file.hpp" + +#include "utils/common.hpp" +#include "utils/error.hpp" +#include "utils/string.hpp" + +namespace monitarr::utils::file { +void smb_file::close() { + if (fd_.has_value()) { + smb_fclose(session_.get(), *fd_); + fd_.reset(); + } +} + +auto smb_file::copy_to(std::string_view new_path, + bool overwrite) const -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + // auto to_path = utils::path::absolute(new_path); + + throw utils::error::create_exception(function_name, + { + "failed to copy file", + "not implemented", + std::to_string(overwrite), + new_path, + path_, + }); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto smb_file::exists() const -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + smb_stat_t st{ + smb_fstat(session_.get(), tid_, + smb_create_relative_path(path_).c_str()), + smb_stat_deleter(), + }; + if (not st) { + return false; + } + + return smb_stat_get(st.get(), SMB_STAT_ISDIR) == 0U; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +void smb_file::flush() const { + MONITARR_USES_FUNCTION_NAME(); + + try { + throw utils::error::create_exception(function_name, + { + "failed to flush file", + "not implemented", + path_, + }); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } +} + +auto smb_file::get_time(smb_session *session, smb_tid tid, std::string path, + time_type type) -> std::optional { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (session == nullptr) { + throw utils::error::create_exception(function_name, + { + "session not found", + path, + }); + } + + auto rel_path = smb_create_relative_path(path); + smb_stat_t st{ + smb_fstat(session, tid, rel_path.c_str()), + smb_stat_deleter(), + }; + if (not st) { + throw utils::error::create_exception(function_name, + { + "failed to stat file", + "not implemented", + rel_path, + path, + }); + } + + switch (type) { + case time_type::accessed: + return smb_stat_get(st.get(), SMB_STAT_ATIME); + + case time_type::created: + return smb_stat_get(st.get(), SMB_STAT_CTIME); + + case time_type::modified: + return smb_stat_get(st.get(), SMB_STAT_MTIME); + + case time_type::written: + return smb_stat_get(st.get(), SMB_STAT_WTIME); + } + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return std::nullopt; +} + +auto smb_file::is_symlink() const -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto smb_file::move_to(std::string_view new_path) -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (utils::string::begins_with(new_path, "//")) { + throw utils::error::create_exception(function_name, + { + "failed to move file", + "new path must be in same share", + new_path, + path_, + }); + } + + auto from_path = smb_create_relative_path(path_); + auto to_path = smb_create_and_validate_relative_path( + utils::string::begins_with(new_path, "/") ? smb_get_root_path(path_) + : smb_get_parent_path(path_), + new_path); + + auto was_open{false}; + if (fd_.has_value()) { + close(); + was_open = true; + } + + auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_); + if (res != DSM_SUCCESS) { + throw utils::error::create_exception(function_name, + { + "failed to connect to share", + std::to_string(res), + share_name_, + path_, + }); + } + + res = smb_file_mv(session_.get(), tid_, from_path.c_str(), to_path.c_str()); + if (res != DSM_SUCCESS) { + throw utils::error::create_exception(function_name, + { + "failed to move file", + std::to_string(res), + from_path, + to_path, + }); + } + + path_ = smb_create_smb_path(path_, to_path); + if (was_open) { + return open(read_only_); + } + + return true; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto smb_file::open(bool read_only) -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (fd_.has_value()) { + if (read_only == read_only_) { + return true; + } + + close(); + } + + auto rel_path = smb_create_relative_path(path_); + + auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_); + if (res != DSM_SUCCESS) { + throw utils::error::create_exception(function_name, + { + "failed to connect to share", + std::to_string(res), + share_name_, + path_, + }); + } + + smb_fd fd{}; + res = smb_fopen(session_.get(), tid_, rel_path.c_str(), + read_only ? SMB_MOD_RO : SMB_MOD_RW2, &fd); + if (res != DSM_SUCCESS) { + throw utils::error::create_exception( + function_name, { + "failed to open file", + std::to_string(res), + utils::string::from_bool(read_only), + rel_path, + path_, + }); + } + + fd_ = fd; + read_only_ = read_only; + + return true; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto smb_file::read(unsigned char *data, std::size_t to_read, + std::uint64_t offset, std::size_t *total_read) -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (total_read != nullptr) { + (*total_read) = 0U; + } + + if (not fd_.has_value()) { + throw utils::error::create_exception(function_name, + { + "failed to read file", + "file not open", + path_, + }); + } + + auto res = smb_fseek(session_.get(), *fd_, static_cast(offset), + SMB_SEEK_SET); + if (res == -1) { + throw utils::error::create_exception(function_name, + { + "failed to seek file", + std::to_string(res), + std::to_string(offset), + path_, + }); + } + + std::size_t bytes_read{0U}; + while (bytes_read != to_read) { + res = smb_fread(session_.get(), *fd_, &data[bytes_read], + to_read - bytes_read); + if (res == -1) { + throw utils::error::create_exception(function_name, + { + "failed to read file", + std::to_string(res), + std::to_string(offset), + std::to_string(to_read), + path_, + }); + } + + if (res == 0) { + break; + } + + bytes_read += static_cast(res); + } + + if (total_read != nullptr) { + (*total_read) = bytes_read; + } + + return true; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto smb_file::remove() -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + close(); + + return utils::retry_action([this]() -> bool { + if (not exists()) { + return true; + } + + try { + auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_); + if (res != DSM_SUCCESS) { + throw utils::error::create_exception(function_name, + { + "failed to connect to share", + std::to_string(res), + share_name_, + path_, + }); + } + + auto rel_path = smb_create_relative_path(path_); + res = smb_file_rm(session_.get(), tid_, rel_path.c_str()); + if (res != DSM_SUCCESS) { + throw utils::error::create_exception( + function_name, + { + "failed to remove file", + std::to_string(res), + std::to_string(smb_session_get_nt_status(session_.get())), + rel_path, + path_, + }); + } + + return true; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; + }); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto smb_file::size() const -> std::optional { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (not session_) { + throw utils::error::create_exception(function_name, + { + "session not found", + path_, + }); + } + + auto rel_path = smb_create_relative_path(path_); + smb_stat_t st{ + smb_fstat(session_.get(), tid_, rel_path.c_str()), + smb_stat_deleter(), + }; + if (not st) { + throw utils::error::create_exception(function_name, + { + "failed to stat directory", + rel_path, + path_, + }); + } + + return smb_stat_get(st.get(), SMB_STAT_SIZE); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return std::nullopt; +} + +auto smb_file::truncate(std::size_t size) -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + throw utils::error::create_exception(function_name, + { + "failed to truncate file", + "not implemented", + std::to_string(size), + path_, + }); + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} + +auto smb_file::write(const unsigned char *data, std::size_t to_write, + std::size_t offset, std::size_t *total_written) -> bool { + MONITARR_USES_FUNCTION_NAME(); + + try { + if (total_written != nullptr) { + (*total_written) = 0U; + } + + if (not fd_.has_value()) { + throw utils::error::create_exception(function_name, + { + "failed to write file", + "file not open", + path_, + }); + } + + auto res = smb_fseek(session_.get(), *fd_, static_cast(offset), + SMB_SEEK_SET); + if (res == -1) { + throw utils::error::create_exception(function_name, + { + "failed to seek file", + std::to_string(res), + std::to_string(offset), + path_, + }); + } + + std::size_t bytes_written{0U}; + while (bytes_written != to_write) { + res = smb_fwrite(session_.get(), *fd_, + const_cast(&data[bytes_written]), + to_write - bytes_written); + if (res == -1) { + throw utils::error::create_exception(function_name, + { + "failed to write file", + std::to_string(res), + std::to_string(offset), + std::to_string(to_write), + path_, + }); + } + + if (res == 0) { + break; + } + + bytes_written += static_cast(res); + } + + if (total_written != nullptr) { + (*total_written) = bytes_written; + } + + return true; + } catch (const std::exception &e) { + utils::error::handle_exception(function_name, e); + } catch (...) { + utils::error::handle_exception(function_name); + } + + return false; +} +} // namespace monitarr::utils::file + +#endif // defined(PROJECT_ENABLE_LIBDSM) diff --git a/support/src/utils/file_thread_file.cpp b/support/src/utils/file_thread_file.cpp new file mode 100644 index 0000000..446b78b --- /dev/null +++ b/support/src/utils/file_thread_file.cpp @@ -0,0 +1,198 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "utils/file_thread_file.hpp" + +namespace monitarr::utils::file { +// auto thread_file::attach_file(native_handle handle, +// bool read_only) -> fs_file_t {} + +auto thread_file::attach_file(fs_file_t file) -> fs_file_t { + return fs_file_t{ + new thread_file(std::move(file)), + }; +} + +auto thread_file::open_file(std::string_view path, + bool read_only) -> fs_file_t { + return fs_file_t{ + new thread_file(file::open_file(path, read_only)), + }; +} + +auto thread_file::open_or_create_file(std::string_view path, + bool read_only) -> fs_file_t { + return fs_file_t{ + new thread_file(file::open_or_create_file(path, read_only)), + }; +} + +void thread_file::io_item::done(bool result) { + unique_mutex_lock lock(*mtx); + complete = true; + success = result; + notify->notify_all(); +} + +void thread_file::io_item::wait() const { + if (complete) { + return; + } + + unique_mutex_lock lock(*mtx); + while (not complete) { + notify->wait(lock); + } + notify->notify_all(); +} + +thread_file::thread_file(std::string_view path) : file_(new file(path)) {} + +thread_file::thread_file(std::wstring_view path) + : file_(new file(utils::string::to_utf8(path))) {} + +thread_file::thread_file(fs_file_t file) : file_(std::move(file)) {} + +thread_file::~thread_file() { + close(); + + if (io_thread_) { + io_thread_->join(); + } +} + +void thread_file::close() { + do_io([this]() -> bool { + file_->close(); + stop_requested_ = true; + return true; + }); +} + +auto thread_file::copy_to(std::string_view new_path, + bool overwrite) const -> bool { + return do_io([this, &new_path, &overwrite]() -> bool { + return file_->copy_to(new_path, overwrite); + }); +} + +auto thread_file::do_io(action_t action) const -> bool { + unique_mutex_lock lock(*mtx_); + if (stop_requested_) { + return false; + } + + if (not io_thread_) { + io_thread_ = std::make_unique([this]() { thread_func(); }); + } + + auto item = std::make_shared(action); + actions_.emplace_back(item); + notify_->notify_all(); + lock.unlock(); + + item->wait(); + + return item->success; +} + +void thread_file::flush() const { + do_io([this]() -> bool { + file_->flush(); + return true; + }); +} + +auto thread_file::move_to(std::string_view path) -> bool { + return do_io([this, &path]() -> bool { return file_->move_to(path); }); +} + +auto thread_file::read(unsigned char *data, std::size_t to_read, + std::uint64_t offset, std::size_t *total_read) -> bool { + return do_io([this, &data, &to_read, &offset, &total_read]() -> bool { + return file_->read(data, to_read, offset, total_read); + }); +} + +auto thread_file::remove() -> bool { + return do_io([this]() -> bool { return file_->remove(); }); +} + +void thread_file::thread_func() const { + unique_mutex_lock lock(*mtx_); + notify_->notify_all(); + lock.unlock(); + + const auto run_actions = [this, &lock]() { + auto actions = actions_; + actions_.clear(); + notify_->notify_all(); + lock.unlock(); + + for (auto &&action : actions) { + action->done(action->action()); + } + }; + + while (not stop_requested_) { + lock.lock(); + if (stop_requested_) { + lock.unlock(); + break; + } + + while (not stop_requested_ && actions_.empty()) { + notify_->wait(lock); + } + + if (stop_requested_) { + lock.unlock(); + break; + } + + run_actions(); + } + + lock.lock(); + run_actions(); +} + +auto thread_file::truncate(std::size_t size) -> bool { + return do_io([this, &size]() -> bool { return file_->truncate(size); }); +} + +auto thread_file::write(const unsigned char *data, std::size_t to_write, + std::size_t offset, + std::size_t *total_written) -> bool { + return do_io([this, &data, &to_write, &offset, &total_written]() -> bool { + return file_->write(data, to_write, offset, total_written); + }); +} + +auto thread_file::size() const -> std::optional { + std::optional size; + do_io([this, &size]() -> bool { + size = file_->size(); + return size.has_value(); + }); + return size; +} +} // namespace monitarr::utils::file diff --git a/support/src/utils/hash.cpp b/support/src/utils/hash.cpp new file mode 100644 index 0000000..f1c7e3e --- /dev/null +++ b/support/src/utils/hash.cpp @@ -0,0 +1,189 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#if defined(PROJECT_ENABLE_LIBSODIUM) + +#include "utils/hash.hpp" + +#include "utils/error.hpp" + +namespace monitarr::utils::encryption { +auto create_hash_blake2b_256(std::string_view data) -> hash_256_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), data.size()); +} + +auto create_hash_blake2b_256(std::wstring_view data) -> hash_256_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), + data.size() * sizeof(wchar_t)); +} + +auto create_hash_blake2b_256(const data_buffer &data) -> hash_256_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), + data.size() * sizeof(data_buffer::value_type)); +} + +auto create_hash_blake2b_384(std::string_view data) -> hash_384_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), data.size()); +} + +auto create_hash_blake2b_384(std::wstring_view data) -> hash_384_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), + data.size() * sizeof(wchar_t)); +} + +auto create_hash_blake2b_384(const data_buffer &data) -> hash_384_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), + data.size() * sizeof(data_buffer::value_type)); +} + +auto create_hash_blake2b_512(std::string_view data) -> hash_512_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), data.size()); +} + +auto create_hash_blake2b_512(std::wstring_view data) -> hash_512_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), + data.size() * sizeof(wchar_t)); +} + +auto create_hash_blake2b_512(const data_buffer &data) -> hash_512_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), + data.size() * sizeof(data_buffer::value_type)); +} + +auto create_hash_sha256(std::string_view data) -> hash_256_t { + return create_hash_sha256( + reinterpret_cast(data.data()), data.size()); +} + +auto create_hash_sha256(std::wstring_view data) -> hash_256_t { + return create_hash_sha256( + reinterpret_cast(data.data()), + data.size() * sizeof(wchar_t)); +} + +auto create_hash_sha256(const data_buffer &data) -> hash_256_t { + return create_hash_sha256( + reinterpret_cast(data.data()), + data.size() * sizeof(data_buffer::value_type)); +} + +auto create_hash_sha512(std::string_view data) -> hash_512_t { + return create_hash_sha512( + reinterpret_cast(data.data()), data.size()); +} + +auto create_hash_sha512(std::wstring_view data) -> hash_512_t { + return create_hash_sha512( + reinterpret_cast(data.data()), + data.size() * sizeof(wchar_t)); +} + +auto create_hash_sha512(const data_buffer &data) -> hash_512_t { + return create_hash_sha512( + reinterpret_cast(data.data()), + data.size() * sizeof(data_buffer::value_type)); +} + +auto create_hash_sha512(const unsigned char *data, + std::size_t data_size) -> hash_512_t { + MONITARR_USES_FUNCTION_NAME(); + + hash_512_t hash{}; + + crypto_hash_sha512_state state{}; + auto res = crypto_hash_sha512_init(&state); + if (res != 0) { + throw utils::error::create_exception(function_name, + { + "failed to initialize sha-512", + std::to_string(res), + }); + } + + res = crypto_hash_sha512_update(&state, data, data_size); + if (res != 0) { + throw utils::error::create_exception(function_name, + { + "failed to update sha-512", + std::to_string(res), + }); + } + + res = crypto_hash_sha512_final(&state, hash.data()); + if (res != 0) { + throw utils::error::create_exception(function_name, + { + "failed to finalize sha-512", + std::to_string(res), + }); + } + + return hash; +} + +auto create_hash_sha256(const unsigned char *data, + std::size_t data_size) -> hash_256_t { + MONITARR_USES_FUNCTION_NAME(); + + hash_256_t hash{}; + + crypto_hash_sha256_state state{}; + auto res = crypto_hash_sha256_init(&state); + if (res != 0) { + throw utils::error::create_exception(function_name, + { + "failed to initialize sha-256", + std::to_string(res), + }); + } + + res = crypto_hash_sha256_update(&state, data, data_size); + if (res != 0) { + throw utils::error::create_exception(function_name, + { + "failed to update sha-256", + std::to_string(res), + }); + } + + res = crypto_hash_sha256_final(&state, hash.data()); + if (res != 0) { + throw utils::error::create_exception(function_name, + { + "failed to finalize sha-256", + std::to_string(res), + }); + } + + return hash; +} +} // namespace monitarr::utils::encryption + +#endif // defined(PROJECT_ENABLE_LIBSODIUM) diff --git a/support/src/utils/path.cpp b/support/src/utils/path.cpp new file mode 100644 index 0000000..100925f --- /dev/null +++ b/support/src/utils/path.cpp @@ -0,0 +1,315 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "utils/path.hpp" + +#include "utils/common.hpp" +#include "utils/error.hpp" +#include "utils/file.hpp" +#include "utils/string.hpp" +#include "utils/unix.hpp" + +namespace { +[[nodiscard]] auto resolve(std::string path) -> std::string { + MONITARR_USES_FUNCTION_NAME(); + +#if defined(_WIN32) + if (monitarr::utils::string::contains(path, "~\\")) { + monitarr::utils::string::replace(path, "~\\", "%USERPROFILE%\\"); + } + + if (monitarr::utils::string::contains(path, "~/")) { + monitarr::utils::string::replace(path, "~/", "%USERPROFILE%\\"); + } + + if (monitarr::utils::string::contains(path, "%")) { + auto size = ::ExpandEnvironmentStringsA(path.c_str(), nullptr, 0); + + std::string dest; + dest.resize(size); + ::ExpandEnvironmentStringsA(path.c_str(), dest.data(), + static_cast(dest.size())); + path = dest.c_str(); + } +#else // !defined (_WIN32) + if (monitarr::utils::string::contains(path, "~\\")) { + monitarr::utils::string::replace(path, "~\\", "~/"); + } + + if (monitarr::utils::string::contains(path, "~/")) { + std::string home{}; + auto res = + monitarr::utils::use_getpwuid(getuid(), [&home](struct passwd *pw) { + home = (pw->pw_dir ? pw->pw_dir : ""); + if (home.empty() || + ((home == monitarr::utils::path::slash) && (getuid() != 0))) { + home = monitarr::utils::path::combine("/home", {pw->pw_name}); + } + }); + if (not res) { + throw monitarr::utils::error::create_exception(function_name, + { + "failed to getpwuid", + res.reason, + }); + } + + path = monitarr::utils::string::replace(path, "~/", home + "/"); + } +#endif // defined (_WIN32) + + return monitarr::utils::path::finalize(path); +} +} // namespace + +namespace monitarr::utils::path { +auto absolute(std::string_view path) -> std::string { + std::string abs_path{path}; + if (abs_path.empty()) { + return abs_path; + } + + abs_path = resolve(abs_path); +#if defined(_WIN32) + if (not utils::string::contains(abs_path, dot)) { + return abs_path; + } + + std::string temp; + temp.resize(monitarr::max_path_length + 1U); + ::GetFullPathNameA(abs_path.c_str(), static_cast(temp.size()), + temp.data(), nullptr); +#else // !defined(_WIN32) + if (not utils::string::contains(abs_path, dot) || + utils::string::begins_with(abs_path, slash)) { + return abs_path; + } + + auto found{false}; + std::string tmp{abs_path}; + do { + auto *res = realpath(tmp.c_str(), nullptr); + if (res != nullptr) { + abs_path = + res + std::string{directory_seperator} + abs_path.substr(tmp.size()); + free(res); + found = true; + } else if (tmp == dot) { + found = true; + } else { + tmp = dirname(tmp.data()); + } + } while (not found); +#endif // defined(_WIN32) + + return finalize(abs_path); +} + +auto absolute(std::wstring_view path) -> std::wstring { + return utils::string::from_utf8(absolute(utils::string::to_utf8(path))); +} + +auto exists(std::string_view path) -> bool { + return utils::file::file{path}.exists() || + utils::file::directory{path}.exists(); +} + +auto exists(std::wstring_view path) -> bool { + return exists(utils::string::to_utf8(path)); +} + +auto find_program_in_path(const std::string &name_without_extension) + -> std::string { + static std::mutex mtx{}; + static std::unordered_map found_items{}; + + mutex_lock lock(mtx); + if (found_items.contains(name_without_extension)) { + return found_items.at(name_without_extension); + } + + auto path = utils::get_environment_variable("PATH"); + if (path.empty()) { + return path; + } + +#if defined(_WIN32) + static constexpr const std::array extension_list{ + ".bat", + ".cmd", + ".exe", + ".ps1", + }; + static constexpr const auto split_char = ';'; +#else // !defined(_WIN32) + static constexpr const std::array extension_list{ + "", + ".sh", + }; + static constexpr const auto split_char = ':'; +#endif // defined(_WIN32) + + auto search_path_list = utils::string::split(path, split_char, false); + for (auto &&search_path : search_path_list) { + for (auto &&extension : extension_list) { + auto exec_path = combine( + search_path, {name_without_extension + std::string{extension}}); + if (utils::file::file(exec_path).exists()) { + found_items[name_without_extension] = exec_path; + return exec_path; + } + } + } + + return ""; +} + +[[nodiscard]] auto +find_program_in_path(std::wstring_view name_without_extension) -> std::wstring { + return utils::string::from_utf8( + find_program_in_path(utils::string::to_utf8(name_without_extension))); +} + +auto get_parent_path(std::string_view path) -> std::string { + auto abs_path = absolute(path); + +#if defined(_WIN32) + ::PathRemoveFileSpecA(abs_path.data()); + abs_path = abs_path.c_str(); +#else // !defined(_WIN32) + abs_path = std::filesystem::path{abs_path}.parent_path().string(); +#endif // defined(_WIN32) + + return finalize(abs_path); +} + +auto get_parent_path(std::wstring_view path) -> std::wstring { + return utils::string::from_utf8( + get_parent_path(utils::string::to_utf8(path))); +} + +auto get_relative_path(std::string_view path, std::string_view root_path) + -> std::string { + auto abs_path = absolute(path); + auto abs_root_path = + absolute(root_path) + std::string{get_directory_seperator()}; +#if defined(_WIN32) + if (utils::string::to_lower(abs_path).starts_with( + utils::string::to_lower(abs_root_path))) { +#else // !defined(_WIN32) + if (abs_path.starts_with(abs_root_path)) { +#endif // defined(_WIN32) + return abs_path.substr(abs_root_path.size()); + } + + return abs_path; +} + +auto get_relative_path(std::wstring_view path, std::wstring_view root_path) + -> std::wstring { + return utils::string::from_utf8(get_relative_path( + utils::string::to_utf8(path), utils::string::to_utf8(root_path))); +} + +auto contains_trash_directory(std::string_view path) -> bool { + auto parts = utils::string::split(utils::string::to_lower(absolute(path)), + get_directory_seperator(), false); + + return std::find_if(parts.begin(), parts.end(), [](auto &&part) -> bool { + return utils::string::begins_with(part, ".trash-") || + part == ".trashes" || part == "$recycle.bin"; + }) != parts.end(); +} + +auto contains_trash_directory(std::wstring_view path) -> bool { + return contains_trash_directory(utils::string::to_utf8(path)); +} + +auto make_file_uri(std::string_view path) -> std::string { + auto abs_path = absolute(path); +#if defined(_WIN32) + utils::string::replace(abs_path, backslash, slash); + abs_path = std::string{slash} + abs_path; +#endif // defined(_WIN32) + return "file://" + abs_path; +} + +auto make_file_uri(std::wstring_view path) -> std::wstring { + return utils::string::from_utf8(make_file_uri(utils::string::to_utf8(path))); +} + +auto strip_to_file_name(std::string path) -> std::string { + if (path == "." || path == "..") { + return path; + } + +#if defined(_WIN32) + return ::PathFindFileNameA(path.c_str()); +#else // !defined(_WIN32) + return utils::string::contains(path, slash) ? basename(path.data()) : path; +#endif // defined(_WIN32) +} + +auto strip_to_file_name(std::wstring path) -> std::wstring { + return utils::string::from_utf8( + strip_to_file_name(utils::string::to_utf8(path))); +} + +auto unmake_file_uri(std::string_view uri) -> std::string { + static constexpr const std::array escape_characters = { + { + " ", "<", ">", "#", "%", "+", "{", "}", "|", "\\", "^", "~", + "[", "]", "`", ";", "/", "?", ":", "@", "=", "&", "$", + }}; + static constexpr const std::array + escape_sequences_lower = {{ + "%20", "%3c", "%3e", "%23", "%25", "%2b", "%7b", "%7d", + "%7c", "%5c", "%5e", "%7e", "%5b", "%5d", "%60", "%3b", + "%2f", "%3f", "%3a", "%40", "%3d", "%26", "%24", + }}; + static constexpr const std::array + escape_sequences_upper = {{ + "%20", "%3C", "%3E", "%23", "%25", "%2B", "%7B", "%7D", + "%7C", "%5C", "%5E", "%7E", "%5B", "%5D", "%60", "%3B", + "%2F", "%3F", "%3A", "%40", "%3D", "%26", "%24", + }}; + + std::string ret_uri{uri}; +#if defined(_WIN32) + ret_uri = ret_uri.substr(8U); +#else + ret_uri = ret_uri.substr(7U); +#endif + + for (std::size_t idx = 0U; idx < escape_characters.size(); idx++) { + utils::string::replace(ret_uri, escape_sequences_lower.at(idx), + escape_characters.at(idx)); + utils::string::replace(ret_uri, escape_sequences_upper.at(idx), + escape_characters.at(idx)); + } + + return absolute(ret_uri); +} + +auto unmake_file_uri(std::wstring_view uri) -> std::wstring { + return utils::string::from_utf8(unmake_file_uri(utils::string::to_utf8(uri))); +} +} // namespace monitarr::utils::path diff --git a/support/src/utils/string.cpp b/support/src/utils/string.cpp new file mode 100644 index 0000000..0d376ab --- /dev/null +++ b/support/src/utils/string.cpp @@ -0,0 +1,138 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "utils/string.hpp" + +namespace monitarr::utils::string { +auto from_bool(bool val) -> std::string { + return std::to_string(static_cast(val)); +} + +#if defined(PROJECT_ENABLE_BOOST) +auto from_dynamic_bitset(const boost::dynamic_bitset<> &bitset) -> std::string { + std::stringstream stream; + boost::archive::text_oarchive archive(stream); + archive << bitset; + return stream.str(); +} +#endif // defined(PROJECT_ENABLE_BOOST) + +auto from_utf8(std::string_view str) -> std::wstring { + return str.empty() + ? L"" + : std::wstring_convert, wchar_t>() + .from_bytes(std::string{str}); +} + +#if defined(PROJECT_ENABLE_SFML) +auto replace_sf(sf::String &src, const sf::String &find, const sf::String &with, + std::size_t start_pos) -> sf::String & { + if (not src.isEmpty() && (start_pos < src.getSize())) { + while ((start_pos = src.find(find, start_pos)) != std::string::npos) { + src.replace(start_pos, find.getSize(), with); + start_pos += with.getSize(); + } + } + + return src; +} + +auto split_sf(sf::String str, wchar_t delim, + bool should_trim) -> std::vector { + auto result = std::views::split(str.toWideString(), delim); + + std::vector ret{}; + for (auto &&word : result) { + auto val = std::wstring{word.begin(), word.end()}; + if (should_trim) { + trim(val); + } + ret.emplace_back(val); + } + + return ret; +} +#endif // defined(PROJECT_ENABLE_SFML) + +auto to_bool(std::string val) -> bool { + auto ret = false; + + trim(val); + if (is_numeric(val)) { + if (contains(val, ".")) { + ret = (to_double(val) != 0.0); + } else { + std::istringstream(val) >> ret; + } + } else { + std::istringstream(to_lower(val)) >> std::boolalpha >> ret; + } + + return ret; +} + +auto to_double(const std::string &str) -> double { return std::stod(str); } + +#if defined(PROJECT_ENABLE_BOOST) +auto to_dynamic_bitset(const std::string &val) -> boost::dynamic_bitset<> { + std::stringstream stream(val); + boost::dynamic_bitset<> bitset; + boost::archive::text_iarchive archive(stream); + archive >> bitset; + return bitset; +} +#endif // defined(PROJECT_ENABLE_BOOST) + +auto to_int32(const std::string &val) -> std::int32_t { return std::stoi(val); } + +auto to_int64(const std::string &val) -> std::int64_t { + return std::stoll(val); +} + +auto to_size_t(const std::string &val) -> std::size_t { + return static_cast(std::stoull(val)); +} + +auto to_uint8(const std::string &val) -> std::uint8_t { + return static_cast(std::stoul(val)); +} + +auto to_uint16(const std::string &val) -> std::uint16_t { + return static_cast(std::stoul(val)); +} + +auto to_uint32(const std::string &val) -> std::uint32_t { + return static_cast(std::stoul(val)); +} + +auto to_uint64(const std::string &val) -> std::uint64_t { + return std::stoull(val); +} + +auto to_utf8(std::string_view str) -> std::string { return std::string{str}; } + +auto to_utf8(std::wstring_view str) -> std::string { + return str.empty() + ? "" + : std::wstring_convert, wchar_t>() + .to_bytes(std::wstring{str}); +} +} // namespace monitarr::utils::string diff --git a/support/src/utils/time.cpp b/support/src/utils/time.cpp new file mode 100644 index 0000000..2fb967e --- /dev/null +++ b/support/src/utils/time.cpp @@ -0,0 +1,88 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "utils/time.hpp" + +namespace monitarr::utils::time { +void get_local_time_now(struct tm &local_time) { + std::memset(&local_time, 0, sizeof(local_time)); + + const auto now = + std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); +#if defined(_WIN32) + localtime_s(&local_time, &now); +#else // !defined(_WIN32) + localtime_r(&now, &local_time); +#endif // defined(_WIN32) +} + +auto get_time_now() -> std::uint64_t { + return static_cast( + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count()); +} + +#if defined(_WIN32) +// https://stackoverflow.com/questions/321849/strptime-equivalent-on-windows +auto strptime(const char *s, const char *f, struct tm *tm) -> const char * { + std::istringstream input{s}; + input.imbue(std::locale(setlocale(LC_ALL, nullptr))); + input >> std::get_time(tm, f); + if (input.fail()) { + return nullptr; + } + + return reinterpret_cast(s + input.tellg()); +} + +// https://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/ +auto unix_time_to_filetime(std::uint64_t unix_time) -> FILETIME { + auto win_time = unix_time_to_windows_time(unix_time); + + FILETIME file_time{}; + file_time.dwHighDateTime = static_cast(win_time >> 32U); + file_time.dwLowDateTime = win_time & 0xFFFFFFFF; + return file_time; +} + +auto windows_file_time_to_unix_time(FILETIME win_time) -> std::uint64_t { + return windows_time_to_unix_time( + (static_cast(win_time.dwHighDateTime) << 32ULL) | + static_cast(win_time.dwLowDateTime)); +} + +auto windows_time_t_to_unix_time(__time64_t win_time) -> std::uint64_t { + return static_cast( + std::chrono::duration_cast( + std::chrono::system_clock::from_time_t(win_time).time_since_epoch()) + .count()); +} +#endif // defined(_WIN32) + +auto unix_time_to_windows_time(std::uint64_t unix_time) -> std::uint64_t { + return (unix_time / WIN32_TIME_NANOS_PER_TICK) + WIN32_TIME_CONVERSION; +} + +auto windows_time_to_unix_time(std::uint64_t win_time) -> std::uint64_t { + return (win_time - WIN32_TIME_CONVERSION) * WIN32_TIME_NANOS_PER_TICK; +} +} // namespace monitarr::utils::time diff --git a/support/src/utils/unix.cpp b/support/src/utils/unix.cpp new file mode 100644 index 0000000..30ff5b3 --- /dev/null +++ b/support/src/utils/unix.cpp @@ -0,0 +1,83 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#if !defined(_WIN32) + +#include "utils/unix.hpp" +#include "utils/collection.hpp" + +namespace monitarr::utils { +#if !defined(__APPLE__) +auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t { + return static_cast(thread); +} +#endif // !defined(__APPLE__) + +auto get_last_error_code() -> int { return errno; } + +auto get_thread_id() -> std::uint64_t { + return convert_to_uint64(pthread_self()); +} + +auto is_uid_member_of_group(uid_t uid, gid_t gid) -> bool { + std::vector groups{}; + auto res = use_getpwuid(uid, [&groups](struct passwd *pass) { + int group_count{}; + if (getgrouplist(pass->pw_name, pass->pw_gid, nullptr, &group_count) < 0) { + groups.resize(static_cast(group_count)); +#if defined(__APPLE__) + getgrouplist(pass->pw_name, pass->pw_gid, + reinterpret_cast(groups.data()), &group_count); +#else // !defined(__APPLE__) + getgrouplist(pass->pw_name, pass->pw_gid, groups.data(), &group_count); +#endif // defined(__APPLE__) + } + }); + + if (not res) { + throw utils::error::create_exception(res.function_name, + {"use_getpwuid failed", res.reason}); + } + + return collection::includes(groups, gid); +} + +auto use_getpwuid(uid_t uid, passwd_callback_t callback) -> result { + MONITARR_USES_FUNCTION_NAME(); + + static std::mutex mtx{}; + mutex_lock lock{mtx}; + + auto *temp_pw = getpwuid(uid); + if (temp_pw == nullptr) { + return { + std::string{function_name}, + false, + "'getpwuid' returned nullptr", + }; + } + + callback(temp_pw); + return {std::string{function_name}}; +} +} // namespace monitarr::utils + +#endif // !defined(_WIN32) diff --git a/support/src/utils/windows.cpp b/support/src/utils/windows.cpp new file mode 100644 index 0000000..6d001a0 --- /dev/null +++ b/support/src/utils/windows.cpp @@ -0,0 +1,144 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#if defined(_WIN32) + +#include "utils/windows.hpp" + +#include "utils/com_init_wrapper.hpp" +#include "utils/error.hpp" +#include "utils/string.hpp" + +namespace monitarr::utils { +void create_console() { + if (::AllocConsole() == 0) { + return; + } + + FILE *dummy{nullptr}; + freopen_s(&dummy, "CONOUT$", "w", stdout); + freopen_s(&dummy, "CONOUT$", "w", stderr); + freopen_s(&dummy, "CONIN$", "r", stdin); + std::cout.clear(); + std::clog.clear(); + std::cerr.clear(); + std::cin.clear(); + + auto *out_w = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + auto *in_w = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + SetStdHandle(STD_OUTPUT_HANDLE, out_w); + SetStdHandle(STD_ERROR_HANDLE, out_w); + SetStdHandle(STD_INPUT_HANDLE, in_w); + std::wcout.clear(); + std::wclog.clear(); + std::wcerr.clear(); + std::wcin.clear(); +} + +void free_console() { ::FreeConsole(); } + +auto get_last_error_code() -> DWORD { return ::GetLastError(); } + +auto get_local_app_data_directory() -> const std::string & { + MONITARR_USES_FUNCTION_NAME(); + + static std::string app_data = ([]() -> std::string { + com_init_wrapper cw; + PWSTR local_app_data{}; + if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, + &local_app_data))) { + auto ret = utils::string::to_utf8(local_app_data); + ::CoTaskMemFree(local_app_data); + return ret; + } + + throw utils::error::create_exception( + function_name, { + "unable to detect local application data folder", + }); + })(); + + return app_data; +} + +auto get_thread_id() -> std::uint64_t { + return static_cast(::GetCurrentThreadId()); +} + +auto is_process_elevated() -> bool { + auto ret{false}; + HANDLE token{INVALID_HANDLE_VALUE}; + if (::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { + TOKEN_ELEVATION te{}; + DWORD sz = sizeof(te); + if (::GetTokenInformation(token, TokenElevation, &te, sizeof(te), &sz)) { + ret = (te.TokenIsElevated != 0); + } + } + + if (token != INVALID_HANDLE_VALUE) { + ::CloseHandle(token); + } + + return ret; +} + +auto run_process_elevated(std::vector args) -> int { + std::cout << "Elevating Process" << std::endl; + std::string parameters{"-hidden"}; + for (std::size_t idx = 1U; idx < args.size(); idx++) { + parameters += + (parameters.empty() ? args.at(idx) : " " + std::string(args.at(idx))); + } + + std::string full_path; + full_path.resize(monitarr::max_path_length + 1); + + if (::GetModuleFileNameA(nullptr, full_path.data(), + monitarr::max_path_length)) { + SHELLEXECUTEINFOA sei{}; + sei.fMask = SEE_MASK_NOCLOSEPROCESS; + sei.cbSize = sizeof(sei); + sei.lpVerb = "runas"; + sei.lpFile = full_path.c_str(); + sei.lpParameters = parameters.c_str(); + sei.hwnd = nullptr; + sei.nShow = SW_NORMAL; + if (::ShellExecuteExA(&sei)) { + ::WaitForSingleObject(sei.hProcess, INFINITE); + DWORD exit_code{}; + ::GetExitCodeProcess(sei.hProcess, &exit_code); + ::CloseHandle(sei.hProcess); + return static_cast(exit_code); + } + } + + return static_cast(::GetLastError()); +} + +void set_last_error_code(DWORD error_code) { ::SetLastError(error_code); } +} // namespace monitarr::utils + +#endif // defined(_WIN32) diff --git a/support/test/include/test.hpp b/support/test/include/test.hpp new file mode 100644 index 0000000..acf57a0 --- /dev/null +++ b/support/test/include/test.hpp @@ -0,0 +1,63 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef MONITARR_TEST_INCLUDE_TEST_HPP_ +#define MONITARR_TEST_INCLUDE_TEST_HPP_ + +#if defined(U) +#undef U +#endif // defined(U) + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::testing::_; +using namespace ::testing; + +#define COMMA , + +#include "utils/all.hpp" + +namespace monitarr::test { +[[nodiscard]] auto create_random_file(std::size_t size) + -> utils::file::i_file &; + +[[nodiscard]] auto +generate_test_file_name(std::string_view file_name_no_extension) -> std::string; + +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +template +static void decrypt_and_verify(const buffer_t &buffer, std::string_view token, + result_t &result) { + EXPECT_TRUE(utils::encryption::decrypt_data(token, buffer, result)); +} +#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + +auto generate_test_directory() -> utils::file::i_directory &; + +[[nodiscard]] auto get_test_config_dir() -> std::string; + +[[nodiscard]] auto get_test_input_dir() -> std::string; + +[[nodiscard]] auto get_test_output_dir() -> std::string; +} // namespace monitarr::test + +#endif // MONITARR_TEST_INCLUDE_TEST_HPP_ diff --git a/support/test/src/test.cpp b/support/test/src/test.cpp new file mode 100644 index 0000000..32a203a --- /dev/null +++ b/support/test/src/test.cpp @@ -0,0 +1,148 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "test.hpp" + +extern int PROJECT_TEST_RESULT; + +namespace { +static std::recursive_mutex file_mtx{}; + +static std::vector> + generated_files{}; + +struct file_deleter final { + std::string test_output_dir; + + ~file_deleter() { + generated_files.clear(); + + if (PROJECT_TEST_RESULT == 0) { + EXPECT_TRUE(monitarr::utils::file::directory(test_output_dir) + .remove_recursively()); + } + } +}; + +const auto deleter{ + std::make_unique(monitarr::test::get_test_output_dir()), +}; +} // namespace + +namespace monitarr::test { +auto create_random_file(std::size_t size) -> utils::file::i_file & { + auto path = generate_test_file_name("random"); + auto file = utils::file::file::open_or_create_file(path); + EXPECT_TRUE(*file); + if (*file) { + data_buffer buf(size); +#if defined(PROJECT_ENABLE_LIBSODIUM) + randombytes_buf(buf.data(), buf.size()); +#else // !defined(PROJECT_ENABLE_LIBSODIUM) + thread_local std::mt19937 gen{ + static_cast(std::time(nullptr)) ^ + static_cast(std::random_device{}()), + }; + std::uniform_int_distribution dis(0U, 255U); + std::generate(buf.begin(), buf.end(), [&]() -> auto { return dis(gen); }); +#endif // defined(PROJECT_ENABLE_LIBSODIUM) + + std::size_t bytes_written{}; + EXPECT_TRUE(file->write(buf, 0U, &bytes_written)); + EXPECT_EQ(size, bytes_written); + + EXPECT_EQ(size, file->size()); + } + + recur_mutex_lock lock{file_mtx}; + generated_files.emplace_back(std::move(file)); + return *dynamic_cast(generated_files.back().get()); +} + +auto generate_test_directory() -> utils::file::i_directory & { + auto path = utils::path::combine( + get_test_output_dir(), + { + std::string{"test_dir"} + std::to_string(generated_files.size()), + }); + + recur_mutex_lock lock{file_mtx}; + generated_files.emplace_back(std::unique_ptr( + new utils::file::directory{path})); + + auto &ret = + *dynamic_cast(generated_files.back().get()); + EXPECT_TRUE(ret.create_directory()); + return ret; +} + +auto generate_test_file_name(std::string_view file_name_no_extension) + -> std::string { + auto path = utils::path::combine( + get_test_output_dir(), { + std::string{file_name_no_extension} + + std::to_string(generated_files.size()), + }); + + recur_mutex_lock lock{file_mtx}; + generated_files.emplace_back( + std::unique_ptr(new utils::file::file{path})); + return generated_files.back()->get_path(); +} + +auto get_test_config_dir() -> std::string { + static auto test_path = ([]() -> std::string { + auto dir = utils::get_environment_variable("PROJECT_TEST_CONFIG_DIR"); + return utils::path::combine(dir.empty() ? "." : dir, {"test_config"}); + })(); + + return test_path; +} + +auto get_test_input_dir() -> std::string { + static auto test_path = ([]() -> std::string { + auto dir = utils::get_environment_variable("PROJECT_TEST_INPUT_DIR"); + return utils::path::combine(dir.empty() ? "." : dir, {"test_input"}); + })(); + + return test_path; +} + +auto get_test_output_dir() -> std::string { + static auto test_path = ([]() -> std::string { + auto temp = utils::file::create_temp_name("project_test"); + +#if defined(_WIN32) + auto path = utils::path::combine("%TEMP%", {temp}); +#else // !defined(_WIN32) + auto path = utils::path::combine("/tmp", {temp}); +#endif // defined(_WIN32) + + if (not utils::file::directory(path).exists()) { + EXPECT_TRUE(utils::file::directory{path}.create_directory()); + } + + return path; + })(); + + return test_path; +} +} // namespace monitarr::test diff --git a/support/test/src/utils/collection_test.cpp b/support/test/src/utils/collection_test.cpp new file mode 100644 index 0000000..24f1c5a --- /dev/null +++ b/support/test/src/utils/collection_test.cpp @@ -0,0 +1,250 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "test.hpp" + +namespace monitarr { +TEST(utils_collection, excludes) { + auto data = {"cow", "moose", "dog", "chicken"}; + EXPECT_FALSE(utils::collection::excludes(data, "chicken")); + EXPECT_FALSE(utils::collection::excludes(data, "cow")); + EXPECT_FALSE(utils::collection::excludes(data, "dog")); + EXPECT_FALSE(utils::collection::excludes(data, "moose")); + EXPECT_TRUE(utils::collection::excludes(data, "mouse")); +} + +TEST(utils_collection, includes) { + auto data = {"cow", "moose", "dog", "chicken"}; + EXPECT_FALSE(utils::collection::includes(data, "mice")); + EXPECT_TRUE(utils::collection::includes(data, "chicken")); + EXPECT_TRUE(utils::collection::includes(data, "cow")); + EXPECT_TRUE(utils::collection::includes(data, "dog")); + EXPECT_TRUE(utils::collection::includes(data, "moose")); +} + +TEST(utils_collection, from_hex_string) { + { + auto data = "0xABCDEF10"; + std::vector val{}; + EXPECT_TRUE(utils::collection::from_hex_string(data, val)); + EXPECT_EQ(4U, val.size()); + } + + { + auto data = " 0xABCDEF10 "; + std::vector val{}; + EXPECT_TRUE(utils::collection::from_hex_string(data, val)); + EXPECT_EQ(4U, val.size()); + } + + { + auto data = "ABCDEF10"; + std::vector val{}; + EXPECT_TRUE(utils::collection::from_hex_string(data, val)); + EXPECT_EQ(4U, val.size()); + } + + { + auto data = "ACDEF"; + std::vector val{}; + EXPECT_TRUE(utils::collection::from_hex_string(data, val)); + EXPECT_EQ(3U, val.size()); + } + + { + auto data = " ACDEF "; + std::vector val{}; + EXPECT_TRUE(utils::collection::from_hex_string(data, val)); + EXPECT_EQ(3U, val.size()); + } + + { + auto data = ""; + std::vector val{}; + EXPECT_TRUE(utils::collection::from_hex_string(data, val)); + EXPECT_TRUE(val.empty()); + } + + { + auto data = L"0xABCDEF10"; + std::vector val{}; + EXPECT_TRUE(utils::collection::from_hex_string(data, val)); + EXPECT_EQ(4U, val.size()); + } + + { + auto data = L" 0xABCDEF10 "; + std::vector val{}; + EXPECT_TRUE(utils::collection::from_hex_string(data, val)); + EXPECT_EQ(4U, val.size()); + } + + { + auto data = L"ABCDEF10"; + std::vector val{}; + EXPECT_TRUE(utils::collection::from_hex_string(data, val)); + EXPECT_EQ(4U, val.size()); + } + + { + auto data = L"ACDEF"; + std::vector val{}; + EXPECT_TRUE(utils::collection::from_hex_string(data, val)); + EXPECT_EQ(3U, val.size()); + } + + { + auto data = L" ACDEF "; + std::vector val{}; + EXPECT_TRUE(utils::collection::from_hex_string(data, val)); + EXPECT_EQ(3U, val.size()); + } + + { + auto data = L""; + std::vector val{}; + EXPECT_TRUE(utils::collection::from_hex_string(data, val)); + EXPECT_TRUE(val.empty()); + } +} + +TEST(utils_collection, from_hex_string_fails) { + { + auto data = "ABCDEF1Z"; + std::vector val{}; + EXPECT_FALSE(utils::collection::from_hex_string(data, val)); + EXPECT_TRUE(val.empty()); + } + + { + auto data = "ABC DEF1"; + std::vector val{}; + EXPECT_FALSE(utils::collection::from_hex_string(data, val)); + EXPECT_TRUE(val.empty()); + } + + { + auto data = "0x"; + std::vector val{}; + EXPECT_FALSE(utils::collection::from_hex_string(data, val)); + EXPECT_TRUE(val.empty()); + } + + { + auto data = " 0x "; + std::vector val{}; + EXPECT_FALSE(utils::collection::from_hex_string(data, val)); + EXPECT_TRUE(val.empty()); + } + + { + auto data = L"ABCDEF1Z"; + std::vector val{}; + EXPECT_FALSE(utils::collection::from_hex_string(data, val)); + EXPECT_TRUE(val.empty()); + } + + { + auto data = L"ABC DEF1"; + std::vector val{}; + EXPECT_FALSE(utils::collection::from_hex_string(data, val)); + EXPECT_TRUE(val.empty()); + } + + { + auto data = L"0x"; + std::vector val{}; + EXPECT_FALSE(utils::collection::from_hex_string(data, val)); + EXPECT_TRUE(val.empty()); + } + + { + auto data = L" 0x"; + std::vector val{}; + EXPECT_FALSE(utils::collection::from_hex_string(data, val)); + EXPECT_TRUE(val.empty()); + } +} + +TEST(utils_collection, to_hex_string) { + { + std::array col{ + static_cast(0xFF), + static_cast(0xEE), + }; + + auto str = utils::collection::to_hex_string(col); + EXPECT_STREQ("ffee", str.c_str()); + + auto w_str = utils::collection::to_hex_wstring(col); + EXPECT_STREQ(L"ffee", w_str.c_str()); + } + + { + std::array col{ + static_cast(0xFF), + static_cast(0xEE), + }; + + auto str = utils::collection::to_hex_string(col); + EXPECT_STREQ("ffee", str.c_str()); + + auto w_str = utils::collection::to_hex_wstring(col); + EXPECT_STREQ(L"ffee", w_str.c_str()); + } +} + +TEST(utils_collection, remove_element) { + { + std::vector col{ + static_cast(0xFF), + static_cast(0xEE), + }; + + utils::collection::remove_element(col, 0xFF); + EXPECT_EQ(1U, col.size()); + EXPECT_EQ(static_cast(0xEE), col.at(0U)); + } + + { + std::vector col{ + static_cast(0xFF), + static_cast(0xEE), + }; + + utils::collection::remove_element(col, 0xEE); + EXPECT_EQ(1U, col.size()); + EXPECT_EQ(static_cast(0xFF), col.at(0U)); + } + + { + std::vector col{ + static_cast(0xFF), + static_cast(0xEE), + }; + + utils::collection::remove_element(col, 0xEF); + EXPECT_EQ(2U, col.size()); + EXPECT_EQ(static_cast(0xFF), col.at(0U)); + EXPECT_EQ(static_cast(0xEE), col.at(1U)); + } +} +} // namespace monitarr diff --git a/support/test/src/utils/common_test.cpp b/support/test/src/utils/common_test.cpp new file mode 100644 index 0000000..6b0296b --- /dev/null +++ b/support/test/src/utils/common_test.cpp @@ -0,0 +1,306 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "test.hpp" + +namespace monitarr { +TEST(utils_common, calculate_read_size) { + auto read_size = utils::calculate_read_size(0U, 0U, 0U); + EXPECT_EQ(0U, read_size); + + read_size = utils::calculate_read_size(5U, 0U, 0U); + EXPECT_EQ(0U, read_size); + + read_size = utils::calculate_read_size(0U, 6U, 7U); + EXPECT_EQ(0U, read_size); + + read_size = utils::calculate_read_size(7U, 1U, 7U); + EXPECT_EQ(0U, read_size); + + read_size = utils::calculate_read_size(5U, 5U, 0U); + EXPECT_EQ(5U, read_size); + + read_size = utils::calculate_read_size(5U, 5U, 1U); + EXPECT_EQ(4U, read_size); +} + +TEST(utils_common, version_equal) { + EXPECT_EQ(0, utils::compare_version_strings("", "")); + EXPECT_EQ(0, utils::compare_version_strings("1.0", "1.0")); + EXPECT_EQ(0, utils::compare_version_strings("1.0.0", "1.0")); + EXPECT_EQ(0, utils::compare_version_strings("1.0.0.0", "1.0")); + EXPECT_EQ(0, utils::compare_version_strings("1.0.0.0", "1.0.0")); + + EXPECT_EQ(0, utils::compare_version_strings(L"", L"")); + EXPECT_EQ(0, utils::compare_version_strings(L"1.0", L"1.0")); + EXPECT_EQ(0, utils::compare_version_strings(L"1.0.0", L"1.0")); + EXPECT_EQ(0, utils::compare_version_strings(L"1.0.0.0", L"1.0")); + EXPECT_EQ(0, utils::compare_version_strings(L"1.0.0.0", L"1.0.0")); +} + +TEST(utils_common, version_greater) { + EXPECT_EQ(1, utils::compare_version_strings("1.0.1", "")); + EXPECT_EQ(1, utils::compare_version_strings("1.0.1", "1.0")); + EXPECT_EQ(1, utils::compare_version_strings("1.0.1", "1.0.0")); + EXPECT_EQ(1, utils::compare_version_strings("1.0.1", "1.0.0.0")); + EXPECT_EQ(1, utils::compare_version_strings("1.0.1.0", "1.0")); + EXPECT_EQ(1, utils::compare_version_strings("1.0.1.0", "1.0.0")); + EXPECT_EQ(1, utils::compare_version_strings("1.0.1.0", "1.0.0.0")); + EXPECT_EQ(1, utils::compare_version_strings("1.0", "0.9.9")); + EXPECT_EQ(1, utils::compare_version_strings("1.0.1", "0.9.9")); + EXPECT_EQ(1, utils::compare_version_strings("1.0.1.0", "0.9.9")); + + EXPECT_EQ(1, utils::compare_version_strings("1.0.1", "")); + EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1", L"1.0")); + EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1", L"1.0.0")); + EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1", L"1.0.0.0")); + EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1.0", L"1.0")); + EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1.0", L"1.0.0")); + EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1.0", L"1.0.0.0")); + EXPECT_EQ(1, utils::compare_version_strings(L"1.0", L"0.9.9")); + EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1", L"0.9.9")); + EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1.0", L"0.9.9")); +} + +TEST(utils_common, version_less) { + EXPECT_EQ(-1, utils::compare_version_strings("", "1.0")); + EXPECT_EQ(-1, utils::compare_version_strings("0.9.9", "1.0")); + EXPECT_EQ(-1, utils::compare_version_strings("0.9.9", "1.0.1")); + EXPECT_EQ(-1, utils::compare_version_strings("0.9.9", "1.0.1.0")); + EXPECT_EQ(-1, utils::compare_version_strings("1.0", "1.0.1")); + EXPECT_EQ(-1, utils::compare_version_strings("1.0", "1.0.1.0")); + EXPECT_EQ(-1, utils::compare_version_strings("1.0.0", "1.0.1")); + EXPECT_EQ(-1, utils::compare_version_strings("1.0.0", "1.0.1.0")); + EXPECT_EQ(-1, utils::compare_version_strings("1.0.0.0", "1.0.1")); + EXPECT_EQ(-1, utils::compare_version_strings("1.0.0.0", "1.0.1.0")); + + EXPECT_EQ(-1, utils::compare_version_strings(L"", L"1.0")); + EXPECT_EQ(-1, utils::compare_version_strings(L"0.9.9", L"1.0")); + EXPECT_EQ(-1, utils::compare_version_strings(L"0.9.9", L"1.0.1")); + EXPECT_EQ(-1, utils::compare_version_strings(L"0.9.9", L"1.0.1.0")); + EXPECT_EQ(-1, utils::compare_version_strings(L"1.0", L"1.0.1")); + EXPECT_EQ(-1, utils::compare_version_strings(L"1.0", L"1.0.1.0")); + EXPECT_EQ(-1, utils::compare_version_strings(L"1.0.0", L"1.0.1")); + EXPECT_EQ(-1, utils::compare_version_strings(L"1.0.0", L"1.0.1.0")); + EXPECT_EQ(-1, utils::compare_version_strings(L"1.0.0.0", L"1.0.1")); + EXPECT_EQ(-1, utils::compare_version_strings(L"1.0.0.0", L"1.0.1.0")); +} + +#if defined(PROJECT_ENABLE_STDUUID) +TEST(utils_common, create_uuid_string) { + { + const auto uuid1 = utils::create_uuid_string(); + const auto uuid2 = utils::create_uuid_string(); + ASSERT_EQ(36U, uuid1.size()); + ASSERT_EQ(36U, uuid2.size()); + ASSERT_STRNE(uuid1.c_str(), uuid2.c_str()); + } + { + const auto uuid1 = utils::create_uuid_wstring(); + const auto uuid2 = utils::create_uuid_wstring(); + ASSERT_EQ(36U, uuid1.size()); + ASSERT_EQ(36U, uuid2.size()); + ASSERT_STRNE(uuid1.c_str(), uuid2.c_str()); + } +} +#endif // defined(PROJECT_ENABLE_STDUUID) + +#if defined(PROJECT_ENABLE_LIBSODIUM) +TEST(utils_common, generate_secure_random) { + { + auto r1 = utils::generate_secure_random(); + auto r2 = utils::generate_secure_random(); + EXPECT_NE(r1, r2); + } + + { + auto r1 = utils::generate_secure_random>(6U); + auto r2 = utils::generate_secure_random>(6U); + EXPECT_EQ(6U, r1.size()); + EXPECT_EQ(r1.size(), r2.size()); + EXPECT_NE(r1, r2); + } + + { + auto r1 = utils::generate_secure_random>(); + auto r2 = utils::generate_secure_random>(); + EXPECT_EQ(4U, r1.size()); + EXPECT_EQ(r1.size(), r2.size()); + EXPECT_NE(0, std::memcmp(r1.data(), r2.data(), r1.size())); + } + + { + auto r1 = utils::generate_secure_random(6U); + auto r2 = utils::generate_secure_random(6U); + EXPECT_EQ(6U, r1.size()); + EXPECT_EQ(r1.size(), r2.size()); + EXPECT_NE(0, std::memcmp(r1.data(), r2.data(), r1.size())); + } + + { + auto r1 = utils::generate_secure_random(6U); + auto r2 = utils::generate_secure_random(6U); + EXPECT_EQ(6U, r1.size()); + EXPECT_EQ(r1.size(), r2.size()); + EXPECT_NE(0, std::memcmp(r1.data(), r2.data(), r1.size())); + } +} +#endif // defined(PROJECT_ENABLE_LIBSODIUM) + +TEST(utils_common, divide_with_ceiling) { + auto r = utils::divide_with_ceiling(12, 5); + EXPECT_EQ(3, r); + + r = utils::divide_with_ceiling(12, 4); + EXPECT_EQ(3, r); + + r = utils::divide_with_ceiling(1, 2); + EXPECT_EQ(1, r); + + r = utils::divide_with_ceiling(2, 2); + EXPECT_EQ(1, r); + + r = utils::divide_with_ceiling(0, 2); + EXPECT_EQ(0, r); +} + +TEST(utils_common, generate_random_between_for_signed_integers) { + static constexpr const auto max_iterations{1000000UL}; + + for (std::size_t idx = 0U; idx < max_iterations; ++idx) { + auto res = utils::generate_random_between(5, 12); + EXPECT_GE(res, 5); + EXPECT_LE(res, 12); + } + + for (std::size_t idx = 0U; idx < max_iterations; ++idx) { + auto res = utils::generate_random_between(-5, 12); + EXPECT_GE(res, -5); + EXPECT_LE(res, 12); + } + + for (std::size_t idx = 0U; idx < max_iterations; ++idx) { + auto res = utils::generate_random_between(-5, -1); + EXPECT_GE(res, -5); + EXPECT_LE(res, -1); + } +} + +TEST(utils_common, generate_random_between_for_unsigned_integers) { + static constexpr const auto max_iterations{1000000UL}; + + for (std::size_t idx = 0U; idx < max_iterations; ++idx) { + auto res = utils::generate_random_between(5U, 12U); + EXPECT_GE(res, 5); + EXPECT_LE(res, 12); + } +} + +TEST(utils_common, generate_random_between_throws_error_on_invalid_range) { + EXPECT_THROW( + { + try { + [[maybe_unused]] auto res = utils::generate_random_between(12, 5); + } catch (const std::range_error &e) { + EXPECT_STREQ("end must be greater than begin", e.what()); + throw; + } + }, + std::range_error); + + EXPECT_THROW( + { + try { + [[maybe_unused]] auto res = utils::generate_random_between(12, 12); + } catch (const std::range_error &e) { + EXPECT_STREQ("end must be greater than begin", e.what()); + throw; + } + }, + std::range_error); +} + +TEST(utils_common, generate_random_string) { + static constexpr const auto max_iterations{10000L}; + + const auto test_string = [](auto str) { + static std::vector list{}; + + EXPECT_FALSE(utils::collection::includes(list, str)); + list.push_back(str); + + EXPECT_EQ(16U, str.size()); + for (auto &&ch : str) { + auto ch_int = static_cast(ch); + EXPECT_GE(ch_int, 48U); + EXPECT_LE(ch_int, 73U + 48U); + } + }; + + for (std::size_t idx = 0U; idx < max_iterations; ++idx) { + test_string(utils::generate_random_string(16U)); + test_string(utils::generate_random_wstring(16U)); + } +} + +TEST(utils_common, generate_random_string_for_zero_length) { + EXPECT_TRUE(utils::generate_random_string(0U).empty()); + EXPECT_TRUE(utils::generate_random_wstring(0U).empty()); +} + +TEST(utils_common, get_environment_variable) { + static constexpr const std::string path_env{"PATH"}; + std::string path; + +#if defined(_WIN32) + path.resize(monitarr::max_path_length + 1U); + auto size = ::GetEnvironmentVariableA(path_env.c_str(), path.data(), 0U); + + path.resize(size); + ::GetEnvironmentVariableA(path_env.c_str(), path.data(), + static_cast(path.size())); +#else // !defined(_WIN32) + path = std::getenv(path_env.c_str()); +#endif // defined(_WIN32) + + EXPECT_STREQ(path.c_str(), utils::get_environment_variable(path_env).c_str()); + EXPECT_STREQ( + utils::string::from_utf8(path).c_str(), + utils::get_environment_variable(utils::string::from_utf8(path_env)) + .c_str()); +} + +#if defined(PROJECT_ENABLE_BOOST) +TEST(utils_common, get_next_available_port) { + std::uint16_t available_port{}; + for (std::uint16_t port = 1025U; port < 1030U; ++port) { + EXPECT_TRUE(utils::get_next_available_port(port, available_port)); + EXPECT_GE(available_port, port); + } +} + +TEST(utils_common, get_next_available_port_fails_if_starting_point_is_zero) { + std::uint16_t available_port{}; + EXPECT_FALSE(utils::get_next_available_port(0U, available_port)); + EXPECT_EQ(0U, available_port); +} +#endif // defined(PROJECT_ENABLE_BOOST) +} // namespace monitarr diff --git a/support/test/src/utils/db_sqlite_test.cpp b/support/test/src/utils/db_sqlite_test.cpp new file mode 100644 index 0000000..8022cb9 --- /dev/null +++ b/support/test/src/utils/db_sqlite_test.cpp @@ -0,0 +1,300 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "test.hpp" + +#if defined(PROJECT_ENABLE_SQLITE) + +namespace monitarr { +class utils_db_sqlite : public ::testing::Test { +public: + utils::db::sqlite::db3_t db3; + + void SetUp() override { + { + sqlite3 *db3_ptr{nullptr}; + auto res = sqlite3_open(":memory:", &db3_ptr); + ASSERT_EQ(SQLITE_OK, res); + ASSERT_TRUE(db3_ptr != nullptr); + db3 = utils::db::sqlite::db3_t{ + db3_ptr, + utils::db::sqlite::sqlite3_deleter(), + }; + } + + utils::db::sqlite::db3_stmt_t db3_stmt; + { + std::string sql{ + "CREATE TABLE [table] (column1 TEXT PRIMARY KEY UNIQUE " + "NOT NULL, column2 TEXT NOT NULL);", + }; + + sqlite3_stmt *stmt_ptr{nullptr}; + auto res = + sqlite3_prepare_v2(db3.get(), sql.c_str(), -1, &stmt_ptr, nullptr); + db3_stmt = utils::db::sqlite::db3_stmt_t{ + stmt_ptr, + utils::db::sqlite::sqlite3_statement_deleter(), + }; + ASSERT_EQ(SQLITE_OK, res); + } + + auto res = sqlite3_step(db3_stmt.get()); + ASSERT_EQ(SQLITE_DONE, res); + } + + void TearDown() override { db3.reset(); } +}; + +static void common_insert(sqlite3 &db3, bool dump = false) { + auto query = utils::db::sqlite::db_insert{db3, "table"} + .column_value("column1", "test0") + .column_value("column2", "test1"); + if (dump) { + std::cout << query.dump() << std::endl; + } + + auto res = query.go(); + EXPECT_TRUE(res.ok()); +} + +static void common_select(sqlite3 &db3, std::string value1, std::string value2, + bool dump = false) { + auto query = utils::db::sqlite::db_select{db3, "table"}; + if (dump) { + std::cout << query.dump() << std::endl; + } + + auto res = query.go(); + EXPECT_TRUE(res.ok()); + EXPECT_TRUE(res.has_row()); + + std::size_t row_count{}; + while (res.has_row()) { + std::optional row; + EXPECT_TRUE(res.get_row(row)); + EXPECT_TRUE(row.has_value()); + if (row.has_value()) { + auto columns = row.value().get_columns(); + EXPECT_EQ(std::size_t(2U), columns.size()); + EXPECT_STREQ("column1", columns[0U].get_name().c_str()); + EXPECT_STREQ(value1.c_str(), + columns[0U].get_value().c_str()); + EXPECT_STREQ("column2", columns[1U].get_name().c_str()); + EXPECT_STREQ(value2.c_str(), + columns[1U].get_value().c_str()); + for (auto &&column : columns) { + std::cout << column.get_index() << ':'; + std::cout << column.get_name() << ':'; + std::cout << column.get_value() << std::endl; + } + } + ++row_count; + } + + EXPECT_EQ(std::size_t(1U), row_count); +} + +static void common_delete(sqlite3 &db3, bool dump = false) { + { + auto query = utils::db::sqlite::db_delete{db3, "table"}; + if (dump) { + std::cout << query.dump() << std::endl; + } + + auto res = query.go(); + EXPECT_TRUE(res.ok()); + } + + { + auto query = utils::db::sqlite::db_select{db3, "table"}; + auto res = query.go(); + EXPECT_TRUE(res.ok()); + + std::size_t row_count{}; + while (res.has_row()) { + ++row_count; + } + + EXPECT_EQ(std::size_t(0U), row_count); + } +} + +TEST_F(utils_db_sqlite, db_delete_query) { + auto query = utils::db::sqlite::db_delete{*db3.get(), "table"}; + auto query_str = query.dump(); + std::cout << query_str << std::endl; + EXPECT_STREQ(R"(DELETE FROM "table";)", query_str.c_str()); +} + +TEST_F(utils_db_sqlite, db_delete_where_query) { + auto query = utils::db::sqlite::db_delete{*db3.get(), "table"} + .where("column1") + .equals("test1") + .and_() + .where("column2") + .equals("test2"); + auto query_str = query.dump(); + std::cout << query_str << std::endl; + EXPECT_STREQ(R"(DELETE FROM "table" WHERE "column1"=?1 AND "column2"=?2;)", + query_str.c_str()); +} + +TEST_F(utils_db_sqlite, db_insert_query) { + auto query = utils::db::sqlite::db_insert{*db3.get(), "table"} + .column_value("column1", "test9") + .column_value("column2", "test9"); + auto query_str = query.dump(); + std::cout << query_str << std::endl; + EXPECT_STREQ(R"(INSERT INTO "table" ("column1", "column2") VALUES (?1, ?2);)", + query_str.c_str()); +} + +TEST_F(utils_db_sqlite, db_insert_or_replace_query) { + auto query = utils::db::sqlite::db_insert{*db3.get(), "table"} + .or_replace() + .column_value("column1", "test1") + .column_value("column2", "test2"); + auto query_str = query.dump(); + std::cout << query_str << std::endl; + EXPECT_STREQ( + R"(INSERT OR REPLACE INTO "table" ("column1", "column2") VALUES (?1, ?2);)", + query_str.c_str()); +} + +TEST_F(utils_db_sqlite, db_select_query) { + auto query = utils::db::sqlite::db_select{*db3.get(), "table"}; + auto query_str = query.dump(); + std::cout << query_str << std::endl; + EXPECT_STREQ(R"(SELECT * FROM "table";)", query_str.c_str()); +} + +TEST_F(utils_db_sqlite, db_select_where_query) { + auto query = utils::db::sqlite::db_select{*db3.get(), "table"} + .where("column1") + .equals("test1") + .and_() + .where("column2") + .equals("test2"); + auto query_str = query.dump(); + std::cout << query_str << std::endl; + EXPECT_STREQ(R"(SELECT * FROM "table" WHERE "column1"=?1 AND "column2"=?2;)", + query_str.c_str()); +} + +TEST_F(utils_db_sqlite, db_select_where_with_group_query) { + auto query = + utils::db::sqlite::db_select{*db3.get(), "table"} + .group([](auto &grp) { + grp.where("column1").equals("a").or_().where("column1").equals("b"); + }) + .and_() + .group([](auto &grp) { + grp.where("column2").equals("c").or_().where("column2").equals("d"); + }) + .or_() + .group([](auto &grp) { + grp.where("column1").equals("e").or_().where("column2").equals("f"); + }); + auto query_str = query.dump(); + std::cout << query_str << std::endl; + EXPECT_STREQ( + R"(SELECT * FROM "table" WHERE ("column1"=?1 OR "column1"=?2) AND ("column2"=?3 OR "column2"=?4) OR ("column1"=?5 OR "column2"=?6);)", + query_str.c_str()); +} + +TEST_F(utils_db_sqlite, db_select_columns_query) { + auto query = utils::db::sqlite::db_select{*db3.get(), "table"} + .column("column1") + .column("column2") + .where("column1") + .equals("test1") + .and_() + .where("column2") + .equals("test2"); + auto query_str = query.dump(); + std::cout << query_str << std::endl; + EXPECT_STREQ( + R"(SELECT column1, column2 FROM "table" WHERE "column1"=?1 AND "column2"=?2;)", + query_str.c_str()); +} + +TEST_F(utils_db_sqlite, db_update_query) { + auto query = utils::db::sqlite::db_update{*db3.get(), "table"} + .column_value("column1", "moose") + .where("column1") + .equals("test1") + .and_() + .where("column2") + .equals("test2"); + auto query_str = query.dump(); + std::cout << query_str << std::endl; + EXPECT_STREQ( + R"(UPDATE "table" SET "column1"=?1 WHERE "column1"=?2 AND "column2"=?3;)", + query_str.c_str()); +} + +TEST_F(utils_db_sqlite, insert_select_delete) { + common_insert(*db3.get(), true); + + common_select(*db3.get(), "test0", "test1", true); + + common_delete(*db3.get(), true); +} + +TEST_F(utils_db_sqlite, insert_update_delete) { + common_insert(*db3.get()); + + { + auto query = utils::db::sqlite::db_update{*db3.get(), "table"} + .column_value("column1", "moose") + .where("column1") + .equals("test0"); + std::cout << query.dump() << std::endl; + auto res = query.go(); + EXPECT_TRUE(res.ok()); + } + + common_select(*db3.get(), "moose", "test1"); + + common_delete(*db3.get()); +} + +TEST_F(utils_db_sqlite, insert_or_replace_and_delete) { + common_insert(*db3.get()); + + { + auto query = utils::db::sqlite::db_insert{*db3.get(), "table"} + .or_replace() + .column_value("column1", "test0") + .column_value("column2", "moose"); + std::cout << query.dump() << std::endl; + auto res = query.go(); + EXPECT_TRUE(res.ok()); + } + + common_select(*db3.get(), "test0", "moose"); + + common_delete(*db3.get()); +} +} // namespace monitarr + +#endif // defined(PROJECT_ENABLE_SQLITE) diff --git a/support/test/src/utils/encrypting_reader_test.cpp b/support/test/src/utils/encrypting_reader_test.cpp new file mode 100644 index 0000000..d77ad9c --- /dev/null +++ b/support/test/src/utils/encrypting_reader_test.cpp @@ -0,0 +1,225 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "test.hpp" + +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + +namespace { +const auto get_stop_requested = []() -> bool { return false; }; +} // namespace + +namespace monitarr { +TEST(utils_encrypting_reader, read_file_data) { + const auto token = std::string("moose"); + auto &source_file = test::create_random_file( + 8U * utils::encryption::encrypting_reader::get_data_chunk_size()); + EXPECT_TRUE(source_file); + if (source_file) { + utils::encryption::encrypting_reader reader( + "test.dat", source_file.get_path(), get_stop_requested, token); + + for (std::uint8_t i = 0U; i < 8U; i++) { + data_buffer buffer( + utils::encryption::encrypting_reader::get_encrypted_chunk_size()); + for (std::uint8_t j = 0U; j < 2U; j++) { + ASSERT_EQ( + buffer.size() / 2U, + utils::encryption::encrypting_reader::reader_function( + reinterpret_cast(&buffer[(buffer.size() / 2U) * j]), + buffer.size() / 2U, 1U, &reader)); + } + + data_buffer decrypted_data; + EXPECT_TRUE( + utils::encryption::decrypt_data(token, buffer, decrypted_data)); + + EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(), + decrypted_data.size()); + + std::size_t bytes_read{}; + data_buffer file_data(decrypted_data.size()); + EXPECT_TRUE(source_file.read( + file_data, + utils::encryption::encrypting_reader::get_data_chunk_size() * i, + &bytes_read)); + EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(), + file_data.size())); + } + } +} + +TEST(utils_encrypting_reader, read_file_data_in_multiple_chunks) { + const auto token = std::string("moose"); + auto &source_file = test::create_random_file( + 8U * utils::encryption::encrypting_reader::get_data_chunk_size()); + EXPECT_TRUE(source_file); + if (source_file) { + utils::encryption::encrypting_reader reader( + "test.dat", source_file.get_path(), get_stop_requested, token); + + for (std::uint8_t i = 0U; i < 8U; i += 2U) { + data_buffer buffer( + utils::encryption::encrypting_reader::get_encrypted_chunk_size() * + 2U); + EXPECT_EQ(buffer.size(), + utils::encryption::encrypting_reader::reader_function( + reinterpret_cast(buffer.data()), buffer.size(), 1U, + &reader)); + + for (std::uint8_t j = 0U; j < 2U; j++) { + data_buffer decrypted_data; + const auto offset = (j * (buffer.size() / 2U)); + EXPECT_TRUE(utils::encryption::decrypt_data( + token, + data_buffer( + std::next(buffer.begin(), static_cast(offset)), + std::next(buffer.begin(), static_cast( + offset + (buffer.size() / 2U)))), + decrypted_data)); + + EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(), + decrypted_data.size()); + + std::size_t bytes_read{}; + data_buffer file_data(decrypted_data.size()); + EXPECT_TRUE(source_file.read( + file_data, + (utils::encryption::encrypting_reader::get_data_chunk_size() * i) + + (j * + utils::encryption::encrypting_reader::get_data_chunk_size()), + &bytes_read)); + EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(), + file_data.size())); + } + } + } +} + +TEST(utils_encrypting_reader, read_file_data_as_stream) { + const auto token = std::string("moose"); + auto &source_file = test::create_random_file( + 8U * utils::encryption::encrypting_reader::get_data_chunk_size()); + EXPECT_TRUE(source_file); + if (source_file) { + utils::encryption::encrypting_reader reader( + "test.dat", source_file.get_path(), get_stop_requested, token); + auto io_stream = reader.create_iostream(); + EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail()); + EXPECT_TRUE(io_stream->good()); + EXPECT_EQ(reader.get_total_size(), + static_cast(io_stream->tellg())); + EXPECT_FALSE(io_stream->seekg(0, std::ios_base::beg).fail()); + EXPECT_TRUE(io_stream->good()); + + for (std::uint8_t i = 0U; i < 8U; i++) { + data_buffer buffer( + utils::encryption::encrypting_reader::get_encrypted_chunk_size()); + EXPECT_FALSE( + io_stream->seekg(static_cast(i * buffer.size())) + .fail()); + EXPECT_TRUE(io_stream->good()); + for (std::uint8_t j = 0U; j < 2U; j++) { + EXPECT_FALSE( + io_stream + ->read( + reinterpret_cast(&buffer[(buffer.size() / 2U) * j]), + static_cast(buffer.size()) / 2U) + .fail()); + EXPECT_TRUE(io_stream->good()); + } + + data_buffer decrypted_data; + EXPECT_TRUE( + utils::encryption::decrypt_data(token, buffer, decrypted_data)); + + EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(), + decrypted_data.size()); + + std::size_t bytes_read{}; + data_buffer file_data(decrypted_data.size()); + EXPECT_TRUE(source_file.read( + file_data, + utils::encryption::encrypting_reader::get_data_chunk_size() * i, + &bytes_read)); + EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(), + file_data.size())); + } + } +} + +TEST(utils_encrypting_reader, read_file_data_in_multiple_chunks_as_stream) { + const auto token = std::string("moose"); + auto &source_file = test::create_random_file( + 8u * utils::encryption::encrypting_reader::get_data_chunk_size()); + EXPECT_TRUE(source_file); + if (source_file) { + utils::encryption::encrypting_reader reader( + "test.dat", source_file.get_path(), get_stop_requested, token); + auto io_stream = reader.create_iostream(); + EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail()); + EXPECT_TRUE(io_stream->good()); + EXPECT_EQ(reader.get_total_size(), + static_cast(io_stream->tellg())); + EXPECT_FALSE(io_stream->seekg(0, std::ios_base::beg).fail()); + EXPECT_TRUE(io_stream->good()); + + for (std::uint8_t i = 0U; i < 8U; i += 2U) { + data_buffer buffer( + utils::encryption::encrypting_reader::get_encrypted_chunk_size() * + 2U); + EXPECT_FALSE(io_stream + ->read(reinterpret_cast(buffer.data()), + static_cast(buffer.size())) + .fail()); + EXPECT_TRUE(io_stream->good()); + + for (std::uint8_t j = 0U; j < 2U; j++) { + data_buffer decrypted_data; + const auto offset = (j * (buffer.size() / 2U)); + EXPECT_TRUE(utils::encryption::decrypt_data( + token, + data_buffer( + std::next(buffer.begin(), static_cast(offset)), + std::next(buffer.begin(), static_cast( + offset + (buffer.size() / 2U)))), + decrypted_data)); + + EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(), + decrypted_data.size()); + + std::size_t bytes_read{}; + data_buffer file_data(decrypted_data.size()); + EXPECT_TRUE(source_file.read( + file_data, + (utils::encryption::encrypting_reader::get_data_chunk_size() * i) + + (j * + utils::encryption::encrypting_reader::get_data_chunk_size()), + &bytes_read)); + EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(), + file_data.size())); + } + } + } +} +} // namespace monitarr + +#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) diff --git a/support/test/src/utils/encryption_test.cpp b/support/test/src/utils/encryption_test.cpp new file mode 100644 index 0000000..9c599ce --- /dev/null +++ b/support/test/src/utils/encryption_test.cpp @@ -0,0 +1,286 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "test.hpp" + +#if defined(PROJECT_ENABLE_LIBSODIUM) + +namespace { +const auto get_stop_requested = []() -> bool { return false; }; +} // namespace + +namespace monitarr { +static constexpr const std::string_view token{"moose"}; +static constexpr const std::wstring_view token_w{L"moose"}; + +TEST(utils_encryption, generate_key) { + auto key1 = + utils::encryption::generate_key(token); + EXPECT_STREQ( + "ab4a0b004e824962913f7c0f79582b6ec7a3b8726426ca61d1a0a28ce5049e96", + utils::collection::to_hex_string(key1).c_str()); + + auto key2 = + utils::encryption::generate_key("moose"); + auto key3 = + utils::encryption::generate_key("moose"); + EXPECT_EQ(key2, key3); + + auto key4 = + utils::encryption::generate_key("moose2"); + EXPECT_NE(key2, key4); + + auto key1_w = + utils::encryption::generate_key(token_w); + EXPECT_NE(key1, key1_w); +#if defined(_WIN32) + EXPECT_STREQ( + L"4f5eb2a2ab34e3777b230465283923080b9ba59311e74058ccd74185131d11fe", + utils::collection::to_hex_wstring(key1_w).c_str()); +#else // !defined(_WIN32) + EXPECT_STREQ( + L"0392d95ed3eee9772fbb9af68fedf829a8eb0adbe8575d9691cc9a752196766a", + utils::collection::to_hex_wstring(key1_w).c_str()); +#endif + + auto key2_w = + utils::encryption::generate_key(L"moose"); + auto key3_w = + utils::encryption::generate_key(L"moose"); + EXPECT_EQ(key2_w, key3_w); + EXPECT_NE(key2_w, key2); + EXPECT_NE(key3_w, key3); + + auto key4_w = + utils::encryption::generate_key(L"moose2"); + EXPECT_NE(key2_w, key4_w); + EXPECT_NE(key4_w, key4); +} + +TEST(utils_encryption, generate_key_default_hasher_is_blake2b_256) { + auto key1 = + utils::encryption::generate_key(token); + auto key2 = utils::encryption::generate_key( + token, [](auto &&data, auto &&size) -> auto { + return utils::encryption::create_hash_blake2b_256( + std::string_view(reinterpret_cast(data), size)); + }); + EXPECT_EQ(key1, key2); + + auto key1_w = + utils::encryption::generate_key(token_w); + auto key2_w = utils::encryption::generate_key( + token_w, [](auto &&data, auto &&size) -> auto { + return utils::encryption::create_hash_blake2b_256(std::wstring_view( + reinterpret_cast(data), size / sizeof(wchar_t))); + }); + EXPECT_EQ(key1_w, key2_w); + + EXPECT_NE(key1_w, key1); + EXPECT_NE(key2_w, key2); +} + +TEST(utils_encryption, generate_key_with_hasher) { + auto key1 = utils::encryption::generate_key( + token, utils::encryption::blake2b_256_hasher); + EXPECT_STREQ( + "ab4a0b004e824962913f7c0f79582b6ec7a3b8726426ca61d1a0a28ce5049e96", + utils::collection::to_hex_string(key1).c_str()); + + auto key2 = utils::encryption::generate_key( + token, utils::encryption::sha256_hasher); + EXPECT_NE(key1, key2); + + EXPECT_STREQ( + "182072537ada59e4d6b18034a80302ebae935f66adbdf0f271d3d36309c2d481", + utils::collection::to_hex_string(key2).c_str()); + + auto key1_w = utils::encryption::generate_key( + token_w, utils::encryption::blake2b_256_hasher); +#if defined(_WIN32) + EXPECT_STREQ( + L"4f5eb2a2ab34e3777b230465283923080b9ba59311e74058ccd74185131d11fe", + utils::collection::to_hex_wstring(key1_w).c_str()); +#else // !defined(_WIN32) + EXPECT_STREQ( + L"0392d95ed3eee9772fbb9af68fedf829a8eb0adbe8575d9691cc9a752196766a", + utils::collection::to_hex_wstring(key1_w).c_str()); +#endif + + auto key2_w = utils::encryption::generate_key( + token_w, utils::encryption::sha256_hasher); + EXPECT_NE(key1_w, key2_w); + +#if defined(_WIN32) + EXPECT_STREQ( + L"918e4c6d39bb373f139b5fac8ec0548a9770da399b2835608974ffeac7fab6c4", + utils::collection::to_hex_wstring(key2_w).c_str()); +#else // !defined(_WIN32) + EXPECT_STREQ( + L"590ac70125bec4501172937f6a2cbdeb22a87b5e40d5595eccd06b2b20548d8f", + utils::collection::to_hex_wstring(key2_w).c_str()); +#endif + + EXPECT_NE(key1_w, key1); + EXPECT_NE(key2_w, key2); +} + +#if defined(PROJECT_ENABLE_BOOST) +static const std::string buffer = "cow moose dog chicken"; + +static void test_encrypted_result(const data_buffer &result) { + EXPECT_EQ(buffer.size() + utils::encryption::encryption_header_size, + result.size()); + std::string data; + EXPECT_TRUE(utils::encryption::decrypt_data(token, result, data)); + EXPECT_EQ(buffer.size(), data.size()); + EXPECT_STREQ(buffer.c_str(), data.c_str()); +} + +TEST(utils_encryption, encrypt_data_buffer) { + data_buffer result; + utils::encryption::encrypt_data(token, buffer, result); + test_encrypted_result(result); +} + +TEST(utils_encryption, encrypt_data_buffer_with_key) { + const auto key = + utils::encryption::generate_key(token); + data_buffer result; + utils::encryption::encrypt_data(key, buffer, result); + test_encrypted_result(result); +} + +TEST(utils_encryption, encrypt_data_pointer) { + data_buffer result; + utils::encryption::encrypt_data( + token, reinterpret_cast(buffer.data()), + buffer.size(), result); + test_encrypted_result(result); +} + +TEST(utils_encryption, encrypt_data_pointer_with_key) { + const auto key = + utils::encryption::generate_key(token); + data_buffer result; + utils::encryption::encrypt_data( + key, reinterpret_cast(buffer.data()), + buffer.size(), result); + test_encrypted_result(result); +} + +TEST(utils_encryption, decrypt_data_pointer) { + const auto key = + utils::encryption::generate_key(token); + data_buffer result; + utils::encryption::encrypt_data( + key, reinterpret_cast(buffer.data()), + buffer.size(), result); + + std::string data; + EXPECT_TRUE(utils::encryption::decrypt_data(token, result.data(), + result.size(), data)); + + EXPECT_EQ(buffer.size(), data.size()); + EXPECT_STREQ(buffer.c_str(), data.c_str()); +} + +TEST(utils_encryption, decrypt_data_buffer_with_key) { + const auto key = + utils::encryption::generate_key(token); + data_buffer result; + utils::encryption::encrypt_data( + key, reinterpret_cast(buffer.data()), + buffer.size(), result); + + std::string data; + EXPECT_TRUE(utils::encryption::decrypt_data(key, result, data)); + + EXPECT_EQ(buffer.size(), data.size()); + EXPECT_STREQ(buffer.c_str(), data.c_str()); +} + +TEST(utils_encryption, decrypt_data_pointer_with_key) { + const auto key = + utils::encryption::generate_key(token); + data_buffer result; + utils::encryption::encrypt_data( + key, reinterpret_cast(buffer.data()), + buffer.size(), result); + + std::string data; + EXPECT_TRUE( + utils::encryption::decrypt_data(key, result.data(), result.size(), data)); + + EXPECT_EQ(buffer.size(), data.size()); + EXPECT_STREQ(buffer.c_str(), data.c_str()); +} + +TEST(utils_encryption, decryption_failure) { + const auto key = + utils::encryption::generate_key(token); + data_buffer result; + utils::encryption::encrypt_data( + key, reinterpret_cast(buffer.data()), + buffer.size(), result); + result[0U] = 0U; + result[1U] = 1U; + result[2U] = 2U; + + std::string data; + EXPECT_FALSE(utils::encryption::decrypt_data(key, result, data)); +} + +TEST(utils_encryption, decrypt_file_name) { + auto &source_file = test::create_random_file( + 8U * utils::encryption::encrypting_reader::get_data_chunk_size()); + EXPECT_TRUE(source_file); + if (source_file) { + utils::encryption::encrypting_reader reader( + "test.dat", source_file.get_path(), get_stop_requested, token, + std::nullopt); + + auto file_name = reader.get_encrypted_file_name(); + + EXPECT_EQ(true, utils::encryption::decrypt_file_name(token, file_name)); + EXPECT_STREQ("test.dat", file_name.c_str()); + } +} + +TEST(utils_encryption, decrypt_file_path) { + auto &source_file = test::create_random_file( + 8U * utils::encryption::encrypting_reader::get_data_chunk_size()); + EXPECT_TRUE(source_file); + if (source_file) { + utils::encryption::encrypting_reader reader( + "test.dat", source_file.get_path(), get_stop_requested, token, + "moose/cow"); + + auto file_path = reader.get_encrypted_file_path(); + + EXPECT_EQ(true, utils::encryption::decrypt_file_path(token, file_path)); + EXPECT_STREQ("/moose/cow/test.dat", file_path.c_str()); + } +} +#endif // defined(PROJECT_ENABLE_BOOST) +} // namespace monitarr + +#endif // defined(PROJECT_ENABLE_LIBSODIUM) diff --git a/support/test/src/utils/error_test.cpp b/support/test/src/utils/error_test.cpp new file mode 100644 index 0000000..85fdb04 --- /dev/null +++ b/support/test/src/utils/error_test.cpp @@ -0,0 +1,73 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "test.hpp" + +namespace monitarr { +template +constexpr bool is_decay_equ = std::is_same_v, U>; + +TEST(utils_error, check_default_exception_handler) { + EXPECT_TRUE(utils::error::get_exception_handler() != nullptr); + if (&utils::error::default_exception_handler == + utils::error::get_exception_handler()) { + auto default_handler_is_iostream = + is_decay_equ; + EXPECT_TRUE(default_handler_is_iostream); + } +} + +TEST(utils_error, can_override_exception_handler) { + struct my_exc_handler final : public utils::error::i_exception_handler { + MOCK_METHOD(void, handle_error, + (std::string_view function_name, std::string_view msg), + (const, override)); + + MOCK_METHOD(void, handle_exception, (std::string_view function_name), + (const, override)); + + MOCK_METHOD(void, handle_exception, + (std::string_view function_name, const std::exception &ex), + (const, override)); + }; + + my_exc_handler handler{}; + utils::error::set_exception_handler(&handler); + + EXPECT_CALL(handler, handle_error("test_func", "error")).WillOnce(Return()); + utils::error::handle_error("test_func", "error"); + + EXPECT_CALL(handler, handle_exception("test_func")).WillOnce(Return()); + utils::error::handle_exception("test_func"); + + auto ex = std::runtime_error("moose"); + EXPECT_CALL(handler, handle_exception(_, _)) + .WillOnce( + [&ex](std::string_view function_name, const std::exception &ex2) { + EXPECT_EQ("test_func_ex", function_name); + EXPECT_STREQ(ex.what(), ex2.what()); + }); + utils::error::handle_exception("test_func_ex", ex); + + utils::error::set_exception_handler(&utils::error::default_exception_handler); +} +} // namespace monitarr diff --git a/support/test/src/utils/file_test.cpp b/support/test/src/utils/file_test.cpp new file mode 100644 index 0000000..5ba34bf --- /dev/null +++ b/support/test/src/utils/file_test.cpp @@ -0,0 +1,556 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "test.hpp" + +namespace { +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +#include "utils/file_enc_file.hpp" +constexpr const auto file_type_count{3U}; +#else +constexpr const auto file_type_count{2U}; +#endif + +[[nodiscard]] auto create_file(auto idx, auto path, + bool read_only = false) -> auto { + switch (idx) { + case 0U: + return monitarr::utils::file::file::open_or_create_file(path, read_only); + case 1U: + return monitarr::utils::file::thread_file::open_or_create_file(path, + read_only); +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + case 2U: + return monitarr::utils::file::enc_file::attach_file( + monitarr::utils::file::file::open_or_create_file(path, read_only)); +#endif + default: + throw std::runtime_error("not supported"); + } +} + +[[nodiscard]] auto open_file(auto idx, auto path, + bool read_only = false) -> auto { + switch (idx) { + case 0U: + return monitarr::utils::file::file::open_file(path, read_only); + case 1U: + return monitarr::utils::file::thread_file::open_file(path, read_only); +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) + case 2U: + return monitarr::utils::file::enc_file::attach_file( + monitarr::utils::file::file::open_file(path, read_only)); +#endif + default: + throw std::runtime_error("not supported"); + } +} +} // namespace + +namespace monitarr { +TEST(utils_file, can_create_and_remove_file) { + for (auto idx = 0U; idx < file_type_count; ++idx) { + auto path = test::generate_test_file_name("utils_file"); + EXPECT_FALSE(utils::file::file(path).exists() || + utils::file::directory(path).exists()); + + auto file{create_file(idx, path)}; + EXPECT_TRUE(*file); + + EXPECT_TRUE(utils::file::file(path).exists()); + EXPECT_TRUE(file->exists()); + + EXPECT_TRUE(file->remove()); + + EXPECT_FALSE(utils::file::file(path).exists()); + EXPECT_FALSE(file->exists()); + } +} + +TEST(utils_file, can_open_file) { + for (auto idx = 0U; idx < file_type_count; ++idx) { + auto path = test::generate_test_file_name("utils_file"); + + { + auto file{create_file(idx, path)}; + EXPECT_TRUE(*file); + } + + { + auto file{create_file(idx, path)}; + EXPECT_TRUE(*file); + } + } +} + +TEST(utils_file, open_file_fails_if_not_found) { + for (auto idx = 0U; idx < file_type_count; ++idx) { + auto path = test::generate_test_file_name("utils_file"); + + auto file{open_file(idx, path)}; + EXPECT_FALSE(*file); + } +} + +TEST(utils_file, write_fails_for_read_only_file) { + for (auto idx = 0U; idx < file_type_count; ++idx) { + auto path = test::generate_test_file_name("utils_file"); + + auto file{create_file(idx, path, true)}; + EXPECT_TRUE(utils::file::file(path).exists()); + EXPECT_TRUE(*file); + std::size_t bytes_written{}; + EXPECT_FALSE(file->write(reinterpret_cast("0"), 1U, + 0U, &bytes_written)); + EXPECT_EQ(0U, bytes_written); + } +} + +// TEST(utils_file, can_attach_file) { +// for (auto idx = 0U; idx < file_type_count; ++idx) { +// auto path = test::generate_test_file_name("utils_file"); +// auto file = idx == 0U ? utils::file::file::open_or_create_file(path) +// : +// utils::file::thread_file::open_or_create_file(path); +// auto file2 = +// idx == 0U ? utils::file::file::attach_file(file->get_handle()) +// : +// utils::file::thread_file::attach_file(file->get_handle()); +// EXPECT_TRUE(*file); +// EXPECT_TRUE(*file2); +// EXPECT_EQ(file->get_path(), file2->get_path()); +// } +// } + +#if defined(PROJECT_ENABLE_JSON) +TEST(utils_file, read_and_write_json_file) { + auto path = test::generate_test_file_name("utils_file"); + + auto json_data = nlohmann::json({{"moose", "cow"}}); + EXPECT_TRUE(utils::file::write_json_file(path, json_data)); + + { + nlohmann::json result_data{}; + EXPECT_TRUE(utils::file::read_json_file(path, result_data)); + EXPECT_STREQ(json_data.dump().c_str(), result_data.dump().c_str()); + } + + { + nlohmann::json result_data{}; + EXPECT_TRUE(utils::file::read_json_file(utils::string::from_utf8(path), + result_data)); + EXPECT_STREQ(json_data.dump().c_str(), result_data.dump().c_str()); + } +} + +#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +TEST(utils_file, read_and_write_json_file_encrypted) { + { + auto path = test::generate_test_file_name("utils_file"); + + auto json_data = nlohmann::json({{"moose", "cow"}}); + EXPECT_TRUE(utils::file::write_json_file(path, json_data, "moose")); + + nlohmann::json result_data{}; + EXPECT_TRUE(utils::file::read_json_file(path, result_data, "moose")); + EXPECT_STREQ(json_data.dump().c_str(), result_data.dump().c_str()); + + { + auto file = utils::file::file::open_file(path); + data_buffer encrypted_data{}; + EXPECT_TRUE(file->read_all(encrypted_data, 0U)); + + data_buffer decrypted_data{}; + EXPECT_TRUE(utils::encryption::decrypt_data("moose", encrypted_data, + decrypted_data)); + EXPECT_STREQ(json_data.dump().c_str(), + nlohmann::json::parse(std::string(decrypted_data.begin(), + decrypted_data.end())) + .dump() + .c_str()); + } + } + + { + auto path = + utils::string::from_utf8(test::generate_test_file_name("utils_file")); + + auto json_data = nlohmann::json({{"moose", "cow"}}); + EXPECT_TRUE(utils::file::write_json_file(path, json_data, L"moose")); + + nlohmann::json result_data{}; + EXPECT_TRUE(utils::file::read_json_file(path, result_data, L"moose")); + EXPECT_STREQ(json_data.dump().c_str(), result_data.dump().c_str()); + + { + auto file = utils::file::file::open_file(path); + data_buffer encrypted_data{}; + EXPECT_TRUE(file->read_all(encrypted_data, 0U)); + + data_buffer decrypted_data{}; + EXPECT_TRUE(utils::encryption::decrypt_data("moose", encrypted_data, + decrypted_data)); + EXPECT_STREQ(json_data.dump().c_str(), + nlohmann::json::parse(std::string(decrypted_data.begin(), + decrypted_data.end())) + .dump() + .c_str()); + } + } +} +#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) +#endif // defined(PROJECT_ENABLE_JSON) + +#if defined(PROJECT_ENABLE_LIBDSM) +TEST(utils_file, smb_create_smb_path) { + const auto *path = "//server/share"; + const auto *rel_path = "test/test.txt"; + auto smb_path = utils::file::smb_create_smb_path(path, rel_path); + EXPECT_STREQ("//server/share/test/test.txt", smb_path.c_str()); + + rel_path = "/test/test.txt"; + smb_path = utils::file::smb_create_smb_path(path, rel_path); + EXPECT_STREQ("//server/share/test/test.txt", smb_path.c_str()); + + rel_path = "test\\test.txt"; + smb_path = utils::file::smb_create_smb_path(path, rel_path); + EXPECT_STREQ("//server/share/test/test.txt", smb_path.c_str()); + + rel_path = "\\test\\test.txt"; + smb_path = utils::file::smb_create_smb_path(path, rel_path); + EXPECT_STREQ("//server/share/test/test.txt", smb_path.c_str()); +} + +TEST(utils_file, smb_create_relative_path) { + const auto *path = "//server/share/test.txt"; + auto rel_path = utils::file::smb_create_relative_path(path); + EXPECT_STREQ("\\test.txt", rel_path.c_str()); + + path = "//server/share/test"; + rel_path = utils::file::smb_create_relative_path(path); + EXPECT_STREQ("\\test", rel_path.c_str()); + + path = "//server/share/test/"; + rel_path = utils::file::smb_create_relative_path(path); + EXPECT_STREQ("\\test", rel_path.c_str()); + + path = "//server/share/test/"; + rel_path = utils::file::smb_create_relative_path(path); + EXPECT_STREQ("\\test", rel_path.c_str()); +} + +TEST(utils_file, smb_create_search_path) { + const auto *path = "//server/share"; + auto search_path = utils::file::smb_create_search_path(path); + EXPECT_STREQ("\\*", search_path.c_str()); + + path = "//server/share/"; + search_path = utils::file::smb_create_search_path(path); + EXPECT_STREQ("\\*", search_path.c_str()); + + path = "//server/share/folder"; + search_path = utils::file::smb_create_search_path(path); + EXPECT_STREQ("\\folder\\*", search_path.c_str()); + + path = "//server/share/folder/"; + search_path = utils::file::smb_create_search_path(path); + EXPECT_STREQ("\\folder\\*", search_path.c_str()); + + path = "//server/share/folder/next"; + search_path = utils::file::smb_create_search_path(path); + EXPECT_STREQ("\\folder\\next\\*", search_path.c_str()); + + path = "//server/share/folder/next/"; + search_path = utils::file::smb_create_search_path(path); + EXPECT_STREQ("\\folder\\next\\*", search_path.c_str()); +} + +TEST(utils_file, smb_parent_is_same) { + const auto *path1 = "//server/share"; + const auto *path2 = "//server/share"; + EXPECT_TRUE(utils::file::smb_parent_is_same(path1, path2)); + + path1 = "//server/share/"; + path2 = "//server/share/"; + EXPECT_TRUE(utils::file::smb_parent_is_same(path1, path2)); + + path1 = "//server/share/one"; + path2 = "//server/share/two"; + EXPECT_TRUE(utils::file::smb_parent_is_same(path1, path2)); + + path1 = "// server/cow"; + path2 = "// server/cow"; + EXPECT_TRUE(utils::file::smb_parent_is_same(path1, path2)); +} + +TEST(utils_file, smb_parent_is_not_same) { + const auto *path1 = "server/share"; + const auto *path2 = "//server/share"; + EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2)); + + path1 = "server/share/"; + path2 = "server/share/"; + EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2)); + + path1 = "//server1/share/one"; + path2 = "//server/share/two"; + EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2)); + + path1 = "//server/share"; + path2 = "//server/share2"; + EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2)); + + path1 = "//server/share/"; + path2 = "//server/share2/"; + EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2)); + + path1 = "//server/share/one"; + path2 = "//server/share2/two"; + EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2)); + + path1 = "//server"; + path2 = "//server/share/two"; + EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2)); + + path1 = "//server/"; + path2 = "//server/"; + EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2)); + + path1 = "//server"; + path2 = "//server"; + EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2)); +} +#endif // defined(PROJECT_ENABLE_LIBDSM) + +TEST(utils_file, directory_exists_in_path) { + auto &test_dir = test::generate_test_directory(); + EXPECT_FALSE( + utils::file::directory_exists_in_path(test_dir.get_path(), "moose")); + + EXPECT_FALSE(utils::file::directory_exists_in_path( + utils::string::from_utf8(test_dir.get_path()), L"moose")); + + EXPECT_FALSE(utils::file::file_exists_in_path(test_dir.get_path(), "moose")); + + EXPECT_FALSE(utils::file::file_exists_in_path( + utils::string::from_utf8(test_dir.get_path()), L"moose")); + + auto sub_dir = test_dir.create_directory("moose"); + EXPECT_TRUE(sub_dir != nullptr); + if (sub_dir) { + EXPECT_TRUE( + utils::file::directory_exists_in_path(test_dir.get_path(), "moose")); + + EXPECT_FALSE( + utils::file::file_exists_in_path(test_dir.get_path(), "moose")); + + EXPECT_TRUE(utils::file::directory_exists_in_path( + utils::string::from_utf8(test_dir.get_path()), L"moose")); + + EXPECT_FALSE(utils::file::file_exists_in_path( + utils::string::from_utf8(test_dir.get_path()), L"moose")); + } +} + +TEST(utils_file, file_exists_in_path) { + auto &test_dir = test::generate_test_directory(); + EXPECT_FALSE( + utils::file::file_exists_in_path(test_dir.get_path(), "moose.txt")); + + EXPECT_FALSE(utils::file::file_exists_in_path( + utils::string::from_utf8(test_dir.get_path()), L"moose.txt")); + + EXPECT_FALSE( + utils::file::directory_exists_in_path(test_dir.get_path(), "moose.txt")); + + EXPECT_FALSE(utils::file::directory_exists_in_path( + utils::string::from_utf8(test_dir.get_path()), L"moose.txt")); + + auto sub_file = test_dir.create_file("moose.txt", false); + EXPECT_TRUE(sub_file != nullptr); + if (sub_file) { + EXPECT_TRUE( + utils::file::file_exists_in_path(test_dir.get_path(), "moose.txt")); + + EXPECT_FALSE(utils::file::directory_exists_in_path(test_dir.get_path(), + "moose.txt")); + + EXPECT_TRUE(utils::file::file_exists_in_path( + utils::string::from_utf8(test_dir.get_path()), L"moose.txt")); + + EXPECT_FALSE(utils::file::directory_exists_in_path( + utils::string::from_utf8(test_dir.get_path()), L"moose.txt")); + } +} + +TEST(utils_file, get_free_drive_space) { +#if defined(_WIN32) + auto space = utils::file::get_free_drive_space("C:"); + auto space2 = utils::file::get_free_drive_space(L"C:"); +#else // defined(_WIN32) + auto space = utils::file::get_free_drive_space("/"); + auto space2 = utils::file::get_free_drive_space(L"/"); +#endif // !defined(_WIN32) + + EXPECT_TRUE(space.has_value()); + EXPECT_LT(0U, space.value()); + + EXPECT_TRUE(space2.has_value()); + EXPECT_EQ(space.value(), space2.value()); +} + +TEST(utils_file, get_free_drive_space_fails_for_bad_path) { + std::string name{"free_drive_space_test_XXXXXX"}; + auto temp = utils::file::create_temp_name("free_drive_space_test"); + + auto space = utils::file::get_free_drive_space(temp); + EXPECT_FALSE(space.has_value()); +} + +TEST(utils_file, get_total_drive_space) { +#if defined(_WIN32) + auto space = utils::file::get_total_drive_space("C:"); + auto space2 = utils::file::get_total_drive_space(L"C:"); +#else // defined(_WIN32) + auto space = utils::file::get_total_drive_space("/"); + auto space2 = utils::file::get_total_drive_space(L"/"); +#endif // !defined(_WIN32) + + EXPECT_TRUE(space.has_value()); + EXPECT_LT(0U, space.value()); + + EXPECT_TRUE(space2.has_value()); + EXPECT_EQ(space.value(), space2.value()); +} + +TEST(utils_file, create_temp_name) { + { + auto temp = utils::file::create_temp_name("test_temp"); + EXPECT_EQ(18U, temp.size()); + + auto temp2 = utils::file::create_temp_name("test_temp"); + EXPECT_STRNE(temp.c_str(), temp2.c_str()); + + EXPECT_TRUE(utils::string::begins_with(temp, "test_temp_")); + } + + { + auto temp = utils::file::create_temp_name(L"test_temp"); + EXPECT_EQ(18U, temp.size()); + + auto temp2 = utils::file::create_temp_name(L"test_temp"); + EXPECT_STRNE(temp.c_str(), temp2.c_str()); + + EXPECT_TRUE(utils::string::begins_with(temp, L"test_temp_")); + } +} + +TEST(utils_file, get_total_drive_space_fails_for_bad_path) { + auto temp = utils::file::create_temp_name("total_drive_space_test"); + auto space = utils::file::get_total_drive_space(temp); + EXPECT_FALSE(space.has_value()); +} + +TEST(utils_file, get_times) { + { + auto times = + utils::file::get_times(test::create_random_file(1U).get_path()); + EXPECT_TRUE(times.has_value()); + EXPECT_LT(0U, times->get(utils::file::time_type::accessed)); + EXPECT_LT(0U, times->get(utils::file::time_type::created)); + EXPECT_LT(0U, times->get(utils::file::time_type::modified)); + EXPECT_LT(0U, times->get(utils::file::time_type::written)); + } + + { + auto times = utils::file::get_times( + utils::string::from_utf8(test::create_random_file(1U).get_path())); + EXPECT_TRUE(times.has_value()); + EXPECT_LT(0U, times->get(utils::file::time_type::accessed)); + EXPECT_LT(0U, times->get(utils::file::time_type::created)); + EXPECT_LT(0U, times->get(utils::file::time_type::modified)); + EXPECT_LT(0U, times->get(utils::file::time_type::written)); + } +} + +TEST(utils_file, get_times_fails_if_not_found) { + auto temp = utils::path::combine(".", {"get_times_test"}); + auto times = utils::file::get_times(temp); + EXPECT_FALSE(times.has_value()); +} + +TEST(utils_file, get_time) { + { + auto file_path = test::create_random_file(1U).get_path(); + auto file_time = + utils::file::get_time(file_path, utils::file::time_type::accessed); + EXPECT_TRUE(file_time.has_value()); + EXPECT_LT(0U, file_time.value()); + + file_time = + utils::file::get_time(file_path, utils::file::time_type::created); + EXPECT_TRUE(file_time.has_value()); + EXPECT_LT(0U, file_time.value()); + + file_time = + utils::file::get_time(file_path, utils::file::time_type::modified); + EXPECT_TRUE(file_time.has_value()); + EXPECT_LT(0U, file_time.value()); + + file_time = + utils::file::get_time(file_path, utils::file::time_type::written); + EXPECT_TRUE(file_time.has_value()); + EXPECT_LT(0U, file_time.value()); + } + + { + auto file_path = + utils::string::from_utf8(test::create_random_file(1U).get_path()); + + auto file_time = + utils::file::get_time(file_path, utils::file::time_type::accessed); + EXPECT_TRUE(file_time.has_value()); + EXPECT_LT(0U, file_time.value()); + + file_time = + utils::file::get_time(file_path, utils::file::time_type::created); + EXPECT_TRUE(file_time.has_value()); + EXPECT_LT(0U, file_time.value()); + + file_time = + utils::file::get_time(file_path, utils::file::time_type::modified); + EXPECT_TRUE(file_time.has_value()); + EXPECT_LT(0U, file_time.value()); + + file_time = + utils::file::get_time(file_path, utils::file::time_type::written); + EXPECT_TRUE(file_time.has_value()); + EXPECT_LT(0U, file_time.value()); + } +} + +TEST(utils_file, get_time_fails_if_not_found) { + auto temp = utils::path::combine(".", {"get_times_test"}); + auto file_time = + utils::file::get_time(temp, utils::file::time_type::accessed); + EXPECT_FALSE(file_time.has_value()); +} +} // namespace monitarr diff --git a/support/test/src/utils/hash_test.cpp b/support/test/src/utils/hash_test.cpp new file mode 100644 index 0000000..a46d4e4 --- /dev/null +++ b/support/test/src/utils/hash_test.cpp @@ -0,0 +1,186 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "test.hpp" + +#if defined(PROJECT_ENABLE_LIBSODIUM) + +namespace monitarr { +TEST(utils_hash, hash_type_sizes) { + EXPECT_EQ(32U, utils::encryption::hash_256_t{}.size()); + EXPECT_EQ(48U, utils::encryption::hash_384_t{}.size()); + EXPECT_EQ(64U, utils::encryption::hash_512_t{}.size()); +} + +TEST(utils_hash, default_hasher_is_blake2b) { + EXPECT_EQ( + &utils::encryption::blake2b_256_hasher, + &utils::encryption::default_create_hash()); + + EXPECT_EQ( + &utils::encryption::blake2b_384_hasher, + &utils::encryption::default_create_hash()); + + EXPECT_EQ( + &utils::encryption::blake2b_512_hasher, + &utils::encryption::default_create_hash()); +} + +TEST(utils_hash, blake2b_256) { + auto hash = utils::collection::to_hex_string( + utils::encryption::create_hash_blake2b_256("a")); + EXPECT_STREQ( + "8928aae63c84d87ea098564d1e03ad813f107add474e56aedd286349c0c03ea4", + hash.c_str()); + + hash = utils::collection::to_hex_string( + utils::encryption::create_hash_blake2b_256(L"a")); +#if defined(_WIN32) + EXPECT_STREQ( + "d2373b17cd8a8e19e39f52fa4905a274f93805fbb8bb4c7f3cb4b2cd6708ec8a", + hash.c_str()); +#else // !defined(_WIN32) + EXPECT_STREQ( + "9fdf5757d7eea386f0d34d2c0e202527986febf1ebb4315fcf7fff40776fa41d", + hash.c_str()); +#endif + + hash = utils::collection::to_hex_string( + utils::encryption::create_hash_blake2b_256({1U})); + EXPECT_STREQ( + "ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25", + hash.c_str()); +} + +TEST(utils_hash, blake2b_384) { + auto hash = utils::collection::to_hex_string( + utils::encryption::create_hash_blake2b_384("a")); + EXPECT_STREQ("7d40de16ff771d4595bf70cbda0c4ea0a066a6046fa73d34471cd4d93d827d7" + "c94c29399c50de86983af1ec61d5dcef0", + hash.c_str()); + + hash = utils::collection::to_hex_string( + utils::encryption::create_hash_blake2b_384(L"a")); +#if defined(_WIN32) + EXPECT_STREQ("637fe31d1e955760ef31043d525d9321826a778ddbe82fcde45a98394241380" + "96675e2f87e36b53ab223a7fd254198fd", + hash.c_str()); +#else // !defined(_WIN32) + EXPECT_STREQ("9d469bd9dab9d4b48b8688de7c22704a8de1b081294f9be294100dfa9f05c92" + "e8d3616476e46cd14f9e613fed80fd157", + hash.c_str()); +#endif + + hash = utils::collection::to_hex_string( + utils::encryption::create_hash_blake2b_384({1U})); + EXPECT_STREQ("42cfe875d08d816538103b906bb0b05202e0b09c4e981680c1110684fc7845b" + "c91c178fa167afcc445490644b2bf5f5b", + hash.c_str()); +} + +TEST(utils_hash, blake2b_512) { + auto hash = utils::collection::to_hex_string( + utils::encryption::create_hash_blake2b_512("a")); + EXPECT_STREQ( + "333fcb4ee1aa7c115355ec66ceac917c8bfd815bf7587d325aec1864edd24e34d5abe2c6" + "b1b5ee3face62fed78dbef802f2a85cb91d455a8f5249d330853cb3c", + hash.c_str()); + + hash = utils::collection::to_hex_string( + utils::encryption::create_hash_blake2b_512(L"a")); +#if defined(_WIN32) + EXPECT_STREQ( + "05970b95468b0b1941066ff189091493e73859ce41cde5ad08118e93ea1d81a57a144296" + "a26a9fe7781481bde97b886725e36e30b305d8bd5cce1ae36bf1564a", + hash.c_str()); +#else // !defined(_WIN32) + EXPECT_STREQ( + "bbc187c6e4d8525655d0ada62d16eed59f3db3ab07e04fb0483fd4ae21d88b984774add9" + "b3fbcff56f9638091013994f8e2d4646fdbbcb4879e2b5160bbb755d", + hash.c_str()); +#endif + + hash = utils::collection::to_hex_string( + utils::encryption::create_hash_blake2b_512({1U})); + EXPECT_STREQ( + "9545ba37b230d8a2e716c4707586542780815b7c4088edcb9af6a9452d50f32474d5ba9a" + "ab52a67aca864ef2696981c2eadf49020416136afd838fb048d21653", + hash.c_str()); +} + +TEST(utils_hash, sha256) { + auto hash = utils::collection::to_hex_string( + utils::encryption::create_hash_sha256("a")); + EXPECT_STREQ( + "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", + hash.c_str()); + + hash = utils::collection::to_hex_string( + utils::encryption::create_hash_sha256(L"a")); +#if defined(_WIN32) + EXPECT_STREQ( + "ffe9aaeaa2a2d5048174df0b80599ef0197ec024c4b051bc9860cff58ef7f9f3", + hash.c_str()); +#else // !defined(_WIN32) + EXPECT_STREQ( + "a2d398922901344d08180dc41d3e9d73d8c148c7f6e092835bbb28e02dbcf184", + hash.c_str()); +#endif + + hash = utils::collection::to_hex_string( + utils::encryption::create_hash_sha256({1U})); + EXPECT_STREQ( + "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a", + hash.c_str()); +} + +TEST(utils_hash, sha512) { + auto hash = utils::collection::to_hex_string( + utils::encryption::create_hash_sha512("a")); + EXPECT_STREQ( + "1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c65" + "2bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75", + hash.c_str()); + + hash = utils::collection::to_hex_string( + utils::encryption::create_hash_sha512(L"a")); +#if defined(_WIN32) + EXPECT_STREQ( + "5c2ca3d50f46ece6066c53bd1a490cbe5f72d2738ae9417332e91e5c3f75205c639d71a9" + "a41d67d965fa137dddf439e0ab9443a6ea44915e90d8b5b566d1c076", + hash.c_str()); +#else // !defined(_WIN32) + EXPECT_STREQ( + "a93498d992e81915075144cb304d2bdf040b336283f888252244882d8366dd3a6e2d9749" + "077114dda1a9aa1a7b69d33f7a781f003ccd12e599a6341014f29aaf", + hash.c_str()); +#endif + + hash = utils::collection::to_hex_string( + utils::encryption::create_hash_sha512({1U})); + EXPECT_STREQ( + "7b54b66836c1fbdd13d2441d9e1434dc62ca677fb68f5fe66a464baadecdbd00576f8d6b" + "5ac3bcc80844b7d50b1cc6603444bbe7cfcf8fc0aa1ee3c636d9e339", + hash.c_str()); +} +} // namespace monitarr + +#endif // defined(PROJECT_ENABLE_LIBSODIUM) diff --git a/support/test/src/utils/path_test.cpp b/support/test/src/utils/path_test.cpp new file mode 100644 index 0000000..58a7aa1 --- /dev/null +++ b/support/test/src/utils/path_test.cpp @@ -0,0 +1,552 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "test.hpp" + +#if defined(_WIN32) +namespace { +static const auto test_path = [](std::string str) -> std::string { +#if defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) + if (monitarr::utils::string::begins_with(str, "\\")) { + str = monitarr::utils::string::to_lower( + std::filesystem::current_path().string().substr(0U, 2U)) + + str; + } + + str = std::string{monitarr::utils::path::long_notation} + str; +#else // !defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) + if (monitarr::utils::string::begins_with(str, "\\")) { + str = monitarr::utils::string::to_lower( + std::filesystem::current_path().string().substr(0U, 2U)) + + str; + } +#endif // defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES) + + return monitarr::utils::string::right_trim(str, '\\'); +}; +} // namespace +#endif // defined(_WIN32) + +namespace monitarr { +TEST(utils_path, constants) { + EXPECT_EQ(std::string_view{"\\"}, utils::path::backslash); + EXPECT_EQ(std::wstring_view{L"\\"}, utils::path::backslash_w); + EXPECT_EQ(std::string_view{"."}, utils::path::dot); + EXPECT_EQ(std::wstring_view{L"."}, utils::path::dot_w); + EXPECT_EQ(std::string_view{".\\"}, utils::path::dot_backslash); + EXPECT_EQ(std::wstring_view{L".\\"}, utils::path::dot_backslash_w); + EXPECT_EQ(std::string_view{"./"}, utils::path::dot_slash); + EXPECT_EQ(std::wstring_view{L"./"}, utils::path::dot_slash_w); + EXPECT_EQ(std::string_view{"/"}, utils::path::slash); + EXPECT_EQ(std::wstring_view{L"/"}, utils::path::slash_w); + +#if defined(_WIN32) + EXPECT_EQ(std::string_view{"\\\\"}, utils::path::unc_notation); + EXPECT_EQ(std::wstring_view{L"\\\\"}, utils::path::unc_notation_w); +#endif // defined(_WIN32) +} + +TEST(utils_path, directory_seperator) { +#if defined(_WIN32) + EXPECT_EQ(utils::path::backslash, utils::path::directory_seperator); + EXPECT_EQ(utils::path::backslash_w, utils::path::directory_seperator_w); + + EXPECT_EQ(utils::path::slash, utils::path::not_directory_seperator); + EXPECT_EQ(utils::path::slash_w, utils::path::not_directory_seperator_w); +#else // !defined(_WIN32) + EXPECT_EQ(utils::path::slash, utils::path::directory_seperator); + EXPECT_EQ(utils::path::slash_w, utils::path::directory_seperator_w); + + EXPECT_EQ(utils::path::backslash, utils::path::not_directory_seperator); + EXPECT_EQ(utils::path::backslash_w, utils::path::not_directory_seperator_w); +#endif // defined(_WIN32) +} + +TEST(utils_path, get_directory_seperator) { +#if defined(_WIN32) + EXPECT_EQ(utils::path::backslash, + utils::path::get_directory_seperator()); + EXPECT_EQ(utils::path::backslash_w, + utils::path::get_directory_seperator()); + + EXPECT_EQ(utils::path::slash, + utils::path::get_not_directory_seperator()); + EXPECT_EQ(utils::path::slash_w, + utils::path::get_not_directory_seperator()); +#else // !defined(_WIN32) + EXPECT_EQ(utils::path::slash, utils::path::get_directory_seperator()); + EXPECT_EQ(utils::path::slash_w, + utils::path::get_directory_seperator()); + + EXPECT_EQ(utils::path::backslash, + utils::path::get_not_directory_seperator()); + EXPECT_EQ(utils::path::backslash_w, + utils::path::get_not_directory_seperator()); +#endif // defined(_WIN32) +} + +TEST(utils_path, get_backslash) { + EXPECT_EQ(utils::path::backslash, utils::path::get_backslash()); + EXPECT_EQ(utils::path::backslash_w, utils::path::get_backslash()); +} + +TEST(utils_path, get_dot) { + EXPECT_EQ(utils::path::dot, utils::path::get_dot()); + EXPECT_EQ(utils::path::dot_w, utils::path::get_dot()); +} + +TEST(utils_path, get_dot_backslash) { + EXPECT_EQ(utils::path::dot_backslash, utils::path::get_dot_backslash()); + EXPECT_EQ(utils::path::dot_backslash_w, + utils::path::get_dot_backslash()); +} + +TEST(utils_path, get_dot_slash) { + EXPECT_EQ(utils::path::dot_slash, utils::path::get_dot_slash()); + EXPECT_EQ(utils::path::dot_slash_w, utils::path::get_dot_slash()); +} + +TEST(utils_path, get_slash) { + EXPECT_EQ(utils::path::slash, utils::path::get_slash()); + EXPECT_EQ(utils::path::slash_w, utils::path::get_slash()); +} + +TEST(utils_path, get_long_notation) { + EXPECT_EQ(utils::path::long_notation, utils::path::get_long_notation()); + EXPECT_EQ(utils::path::long_notation_w, + utils::path::get_long_notation()); +} + +TEST(utils_path, combine) { + auto s = utils::path::combine(R"(\test\path)", {}); +#if defined(_WIN32) + EXPECT_STREQ(test_path(R"(\test\path)").c_str(), s.c_str()); +#else + EXPECT_STREQ("/test/path", s.c_str()); +#endif + + s = utils::path::combine(R"(\test)", {R"(\path)"}); +#if defined(_WIN32) + EXPECT_STREQ(test_path(R"(\test\path)").c_str(), s.c_str()); +#else + EXPECT_STREQ("/test/path", s.c_str()); +#endif + + s = utils::path::combine(R"(\test)", {R"(\path)", R"(\again\)"}); +#if defined(_WIN32) + EXPECT_STREQ(test_path(R"(\test\path\again)").c_str(), s.c_str()); +#else + EXPECT_STREQ("/test/path/again", s.c_str()); +#endif + + s = utils::path::combine("/home/test/.dest", {".state"}); +#if defined(_WIN32) + EXPECT_STREQ(test_path(R"(\home\test\.dest\.state)").c_str(), s.c_str()); +#else + EXPECT_STREQ("/home/test/.dest/.state", s.c_str()); +#endif + +#if defined(_WIN32) + s = utils::path::combine(R"(R:\test)", {R"(\path)", R"(\again\)"}); + EXPECT_STREQ(test_path(R"(r:\test\path\again)").c_str(), s.c_str()); + + s = utils::path::combine("R:", {R"(\path)", R"(\again\)"}); + EXPECT_STREQ(test_path(R"(r:\path\again)").c_str(), s.c_str()); + + s = utils::path::combine("R:", {}); + EXPECT_STREQ(test_path("r:").c_str(), s.c_str()); + + s = utils::path::combine("R:", {"\\"}); + EXPECT_STREQ(test_path("r:").c_str(), s.c_str()); + + s = utils::path::combine("\\\\moose", {"cow"}); + EXPECT_STREQ("\\\\moose\\cow", s.c_str()); +#endif +} + +TEST(utils_path, format_path) { + std::string path{"./"}; + utils::path::format_path(path, utils::path::slash, utils::path::backslash); + EXPECT_STREQ(".", path.c_str()); + + path = "~/.test"; + utils::path::format_path(path, utils::path::slash, utils::path::backslash); + EXPECT_STREQ("~/.test", path.c_str()); + + path = "\\"; + utils::path::format_path(path, utils::path::slash, utils::path::backslash); + EXPECT_STREQ("/", path.c_str()); + + path = "\\\\"; + utils::path::format_path(path, utils::path::slash, utils::path::backslash); + EXPECT_STREQ("/", path.c_str()); + + path = "\\\\\\"; + utils::path::format_path(path, utils::path::slash, utils::path::backslash); + EXPECT_STREQ("/", path.c_str()); + + path = "\\\\\\\\"; + utils::path::format_path(path, utils::path::slash, utils::path::backslash); + EXPECT_STREQ("/", path.c_str()); + + path = "/"; + utils::path::format_path(path, utils::path::slash, utils::path::backslash); + EXPECT_STREQ("/", path.c_str()); + path = "//"; + utils::path::format_path(path, utils::path::slash, utils::path::backslash); + EXPECT_STREQ("/", path.c_str()); + + path = "///"; + utils::path::format_path(path, utils::path::slash, utils::path::backslash); + EXPECT_STREQ("/", path.c_str()); + + path = "////"; + utils::path::format_path(path, utils::path::slash, utils::path::backslash); + EXPECT_STREQ("/", path.c_str()); +} + +TEST(utils_path, create_api_path) { + auto s = utils::path::create_api_path(""); + EXPECT_STREQ("/", s.c_str()); + + s = utils::path::create_api_path(R"(\)"); + EXPECT_STREQ("/", s.c_str()); + + s = utils::path::create_api_path("/"); + EXPECT_STREQ("/", s.c_str()); + + s = utils::path::create_api_path("."); + EXPECT_STREQ("/", s.c_str()); + + s = utils::path::create_api_path("./"); + EXPECT_STREQ("/", s.c_str()); + + s = utils::path::create_api_path(R"(\\)"); + EXPECT_STREQ("/", s.c_str()); + + s = utils::path::create_api_path("//"); + EXPECT_STREQ("/", s.c_str()); + + s = utils::path::create_api_path("/cow///moose/////dog/chicken"); + EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str()); + + s = utils::path::create_api_path("\\cow\\\\\\moose\\\\\\\\dog\\chicken/"); + EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str()); + + s = utils::path::create_api_path("/cow\\\\/moose\\\\/\\dog\\chicken\\"); + EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str()); + + s = utils::path::create_api_path(".state"); + EXPECT_STREQ("/.state", s.c_str()); + + s = utils::path::create_api_path("/.state/.local"); + EXPECT_STREQ("/.state/.local", s.c_str()); + + s = utils::path::create_api_path("./.state/.local"); + EXPECT_STREQ("/.state/.local", s.c_str()); +} + +TEST(utils_path, get_parent_api_path) { + auto s = utils::path::get_parent_api_path(""); + EXPECT_STREQ("/", s.c_str()); + + s = utils::path::get_parent_api_path("/"); + EXPECT_STREQ("/", s.c_str()); + + s = utils::path::get_parent_api_path("/moose"); + EXPECT_STREQ("/", s.c_str()); + + s = utils::path::get_parent_api_path("/moose/cow"); + EXPECT_STREQ("/moose", s.c_str()); + + s = utils::path::get_parent_api_path("/moose/cow/"); + EXPECT_STREQ("/moose", s.c_str()); +} + +TEST(utils_path, finalize) { + auto s = utils::path::finalize(""); + EXPECT_STREQ("", s.c_str()); + + s = utils::path::finalize(R"(\)"); +#if defined(_WIN32) + EXPECT_STREQ(test_path(R"(\)").c_str(), s.c_str()); +#else + EXPECT_STREQ("/", s.c_str()); +#endif + + s = utils::path::finalize("/"); +#if defined(_WIN32) + EXPECT_STREQ(test_path(R"(\)").c_str(), s.c_str()); +#else + EXPECT_STREQ("/", s.c_str()); +#endif + + s = utils::path::finalize(R"(\\)"); +#if defined(_WIN32) + EXPECT_STREQ("\\\\", s.c_str()); +#else + EXPECT_STREQ("/", s.c_str()); +#endif + + s = utils::path::finalize("//"); +#if defined(_WIN32) + EXPECT_STREQ("\\\\", s.c_str()); +#else + EXPECT_STREQ("/", s.c_str()); +#endif + + s = utils::path::finalize("/cow///moose/////dog/chicken"); +#if defined(_WIN32) + EXPECT_STREQ(test_path(R"(\cow\moose\dog\chicken)").c_str(), s.c_str()); +#else + EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str()); +#endif + + s = utils::path::finalize("\\cow\\\\\\moose\\\\\\\\dog\\chicken/"); +#if defined(_WIN32) + EXPECT_STREQ(test_path(R"(\cow\moose\dog\chicken)").c_str(), s.c_str()); +#else + EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str()); +#endif + + s = utils::path::finalize("/cow\\\\/moose\\\\/\\dog\\chicken\\"); +#if defined(_WIN32) + EXPECT_STREQ(test_path(R"(\cow\moose\dog\chicken)").c_str(), s.c_str()); +#else + EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str()); +#endif + +#if defined(_WIN32) + s = utils::path::finalize("D:"); + EXPECT_STREQ(test_path("d:").c_str(), s.c_str()); + + s = utils::path::finalize("D:\\"); + EXPECT_STREQ(test_path("d:").c_str(), s.c_str()); + + s = utils::path::finalize("D:\\moose"); + EXPECT_STREQ(test_path("d:\\moose").c_str(), s.c_str()); + + s = utils::path::finalize("D:\\moose\\"); + EXPECT_STREQ(test_path("d:\\moose").c_str(), s.c_str()); + + s = utils::path::finalize("D:/"); + EXPECT_STREQ(test_path("d:").c_str(), s.c_str()); + + s = utils::path::finalize("D:/moose"); + EXPECT_STREQ(test_path("d:\\moose").c_str(), s.c_str()); + + s = utils::path::finalize("D:/moose/"); + EXPECT_STREQ(test_path("d:\\moose").c_str(), s.c_str()); + + s = utils::path::finalize("\\\\moose\\cow"); + EXPECT_STREQ("\\\\moose\\cow", s.c_str()); + + s = utils::path::finalize("//moose/cow"); + EXPECT_STREQ("\\\\moose\\cow", s.c_str()); +#else // !defined(_WIN32) + s = utils::path::finalize("\\\\moose\\cow"); + EXPECT_STREQ("/moose/cow", s.c_str()); + + s = utils::path::finalize("//moose/cow"); + EXPECT_STREQ("/moose/cow", s.c_str()); +#endif // defined(_WIN32) +} + +TEST(utils_path, absolute) { + auto dir = utils::path::get_current_path(); + auto path = utils::path::absolute("."); + EXPECT_STREQ(dir.c_str(), path.c_str()); + + path = utils::path::absolute("./"); + EXPECT_STREQ(dir.c_str(), path.c_str()); + + path = utils::path::absolute(R"(.\)"); + EXPECT_STREQ(dir.c_str(), path.c_str()); + +#if defined(_WIN32) + path = utils::path::absolute(R"(.\moose)"); + EXPECT_STREQ((dir + R"(\moose)").c_str(), path.c_str()); + + path = utils::path::absolute(R"(./moose)"); + EXPECT_STREQ((dir + R"(\moose)").c_str(), path.c_str()); + + path = utils::path::absolute(R"(\\server\share)"); + EXPECT_STREQ(R"(\\server\share)", path.c_str()); + + path = utils::path::absolute(R"(//server/share)"); + EXPECT_STREQ(R"(\\server\share)", path.c_str()); + + auto home_env = utils::get_environment_variable("USERPROFILE"); +#else // !defined(_WIN32) + path = utils::path::absolute(R"(.\moose)"); + EXPECT_STREQ((dir + R"(/moose)").c_str(), path.c_str()); + + path = utils::path::absolute(R"(./moose)"); + EXPECT_STREQ((dir + R"(/moose)").c_str(), path.c_str()); + + path = utils::path::absolute(R"(\\server\share)"); + EXPECT_STREQ(R"(/server/share)", path.c_str()); +#endif // defined(_WIN32) +} + +TEST(utils_path, absolute_can_resolve_path_variables) { +#if defined(_WIN32) + auto home = + utils::path::absolute(utils::get_environment_variable("USERPROFILE")); + EXPECT_STREQ(home.c_str(), utils::path::absolute("%USERPROFILE%").c_str()); +#else // !defined(_WIN32) + auto home = utils::path::absolute(utils::get_environment_variable("HOME")); +#endif // defined(_WIN32) + + auto expanded_str = utils::path::absolute("~\\"); + EXPECT_STREQ(home.c_str(), expanded_str.c_str()); + + expanded_str = utils::path::absolute("~/"); + EXPECT_STREQ(home.c_str(), expanded_str.c_str()); + + expanded_str = utils::path::absolute("~"); + EXPECT_STREQ("~", expanded_str.c_str()); +} + +TEST(utils_path, get_parent_path) { +#if defined(_WIN32) + { + auto dir = R"(c:\test)"; + auto parent = utils::path::get_parent_path(dir); + EXPECT_STREQ("c:", parent.c_str()); + + dir = R"(c:\test\file.txt)"; + parent = utils::path::get_parent_path(dir); + EXPECT_STREQ(R"(c:\test)", parent.c_str()); + + dir = "c:"; + parent = utils::path::get_parent_path(dir); + EXPECT_STREQ("c:", parent.c_str()); + } + + { + auto dir = LR"(c:\test)"; + auto parent = utils::path::get_parent_path(dir); + EXPECT_STREQ(L"c:", parent.c_str()); + + dir = LR"(c:\test\file.txt)"; + parent = utils::path::get_parent_path(dir); + EXPECT_STREQ(LR"(c:\test)", parent.c_str()); + + dir = L"c:"; + parent = utils::path::get_parent_path(dir); + EXPECT_STREQ(L"c:", parent.c_str()); + } +#else // !defined(_WIN32) + { + auto dir = "/test"; + auto parent = utils::path::get_parent_path(dir); + EXPECT_STREQ("/", parent.c_str()); + + dir = "/test/test"; + parent = utils::path::get_parent_path(dir); + EXPECT_STREQ("/test", parent.c_str()); + } + + { + auto dir = L"/test"; + auto parent = utils::path::get_parent_path(dir); + EXPECT_STREQ(L"/", parent.c_str()); + + dir = L"/test/test"; + parent = utils::path::get_parent_path(dir); + EXPECT_STREQ(L"/test", parent.c_str()); + } +#endif // defined(_WIN32) +} + +TEST(utils_path, contains_trash_directory) { +#if defined(_WIN32) + { + auto dir = R"(c:\$recycle.bin)"; + EXPECT_TRUE(utils::path::contains_trash_directory(dir)); + + dir = R"(c:\$recycle.bin\moose.txt)"; + EXPECT_TRUE(utils::path::contains_trash_directory(dir)); + } + + { + auto dir = LR"(c:\$recycle.bin)"; + EXPECT_TRUE(utils::path::contains_trash_directory(dir)); + + dir = LR"(c:\$recycle.bin\moose.txt)"; + EXPECT_TRUE(utils::path::contains_trash_directory(dir)); + } +#else // !defined(_WIN32) + { + auto dir = "/$recycle.bin"; + EXPECT_TRUE(utils::path::contains_trash_directory(dir)); + + dir = "/$recycle.bin/moose.txt"; + EXPECT_TRUE(utils::path::contains_trash_directory(dir)); + } + + { + auto dir = L"/$recycle.bin"; + EXPECT_TRUE(utils::path::contains_trash_directory(dir)); + + dir = L"/$recycle.bin/moose.txt"; + EXPECT_TRUE(utils::path::contains_trash_directory(dir)); + } +#endif // defined(_WIN32) +} + +TEST(utils_path, does_not_contain_trash_directory) { +#if defined(_WIN32) + { + auto dir = R"(c:\recycle.bin)"; + EXPECT_FALSE(utils::path::contains_trash_directory(dir)); + + dir = R"(c:\recycle.bin\moose.txt)"; + EXPECT_FALSE(utils::path::contains_trash_directory(dir)); + } + + { + auto dir = LR"(c:\recycle.bin)"; + EXPECT_FALSE(utils::path::contains_trash_directory(dir)); + + dir = LR"(c:\recycle.bin\moose.txt)"; + EXPECT_FALSE(utils::path::contains_trash_directory(dir)); + } +#else // !defined(_WIN32) + { + auto dir = "/recycle.bin"; + EXPECT_FALSE(utils::path::contains_trash_directory(dir)); + + dir = "/recycle.bin/moose.txt)"; + EXPECT_FALSE(utils::path::contains_trash_directory(dir)); + } + + { + auto dir = L"/recycle.bin"; + EXPECT_FALSE(utils::path::contains_trash_directory(dir)); + + dir = L"/recycle.bin/moose.txt)"; + EXPECT_FALSE(utils::path::contains_trash_directory(dir)); + } +#endif // defined(_WIN32) +} +} // namespace monitarr diff --git a/support/test/src/utils/string_test.cpp b/support/test/src/utils/string_test.cpp new file mode 100644 index 0000000..bfb4fb6 --- /dev/null +++ b/support/test/src/utils/string_test.cpp @@ -0,0 +1,137 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "test.hpp" + +namespace monitarr { +TEST(utils_string, begins_with) { + std::string str{"moose"}; + EXPECT_TRUE(utils::string::begins_with(str, "m")); + EXPECT_TRUE(utils::string::begins_with(str, "mo")); + EXPECT_TRUE(utils::string::begins_with(str, "moo")); + EXPECT_TRUE(utils::string::begins_with(str, "moos")); + EXPECT_TRUE(utils::string::begins_with(str, "moose")); + + EXPECT_FALSE(utils::string::begins_with(str, "a")); + EXPECT_FALSE(utils::string::begins_with(str, "ma")); + EXPECT_FALSE(utils::string::begins_with(str, "moose1")); + + std::wstring str_w{L"moose"}; + EXPECT_TRUE(utils::string::begins_with(str_w, L"m")); + EXPECT_TRUE(utils::string::begins_with(str_w, L"mo")); + EXPECT_TRUE(utils::string::begins_with(str_w, L"moo")); + EXPECT_TRUE(utils::string::begins_with(str_w, L"moos")); + EXPECT_TRUE(utils::string::begins_with(str_w, L"moose")); + + EXPECT_FALSE(utils::string::begins_with(str_w, L"a")); + EXPECT_FALSE(utils::string::begins_with(str_w, L"ma")); + EXPECT_FALSE(utils::string::begins_with(str_w, L"moose1")); +} + +TEST(utils_string, contains) { + std::string str{R"(\\)"}; + EXPECT_TRUE(utils::string::contains(str, "\\")); + EXPECT_FALSE(utils::string::contains(str, "/")); + + std::wstring str_w{LR"(\\)"}; + EXPECT_TRUE(utils::string::contains(str_w, L"\\")); + EXPECT_FALSE(utils::string::contains(str_w, L"/")); +} + +TEST(utils_string, replace) { + std::string str{"moose"}; + utils::string::replace(str, 'o', '0'); + EXPECT_STREQ("m00se", str.c_str()); + + std::wstring str_w{L"moose"}; + utils::string::replace(str_w, 'o', '0'); + EXPECT_STREQ(L"m00se", str_w.c_str()); + + std::string str2{"\\\\\\"}; + utils::string::replace(str2, '\\', '/'); + EXPECT_STREQ("///", str2.c_str()); + + std::wstring str_w2{L"\\\\\\"}; + utils::string::replace(str_w2, '\\', '/'); + EXPECT_STREQ(L"///", str_w2.c_str()); + + std::string str3{"///"}; + utils::string::replace(str3, '/', '\\'); + EXPECT_STREQ("\\\\\\", str3.c_str()); + + std::wstring str_w3{L"///"}; + utils::string::replace(str_w3, '/', '\\'); + EXPECT_STREQ(L"\\\\\\", str_w3.c_str()); + + str.clear(); + utils::string::replace(str, '/', '\\'); + EXPECT_STREQ("", str.c_str()); + + str_w.clear(); + utils::string::replace(str_w, '/', '\\'); + EXPECT_STREQ(L"", str_w.c_str()); +} + +TEST(utils_string, replace_string) { + std::string str{"moose"}; + utils::string::replace(str, "o", "0"); + EXPECT_STREQ("m00se", str.c_str()); + + std::wstring str_w{L"moose"}; + utils::string::replace(str_w, L"o", L"0"); + EXPECT_STREQ(L"m00se", str_w.c_str()); +} + +TEST(utils_string, is_numeric) { + EXPECT_TRUE(utils::string::is_numeric("100")); + EXPECT_TRUE(utils::string::is_numeric("+100")); + EXPECT_TRUE(utils::string::is_numeric("-100")); + + EXPECT_TRUE(utils::string::is_numeric("100.00")); + EXPECT_TRUE(utils::string::is_numeric("+100.00")); + EXPECT_TRUE(utils::string::is_numeric("-100.00")); + + EXPECT_FALSE(utils::string::is_numeric("1.00.00")); + EXPECT_FALSE(utils::string::is_numeric("+1.00.00")); + EXPECT_FALSE(utils::string::is_numeric("-1.00.00")); + + EXPECT_FALSE(utils::string::is_numeric("a1")); + EXPECT_FALSE(utils::string::is_numeric("1a")); + + EXPECT_FALSE(utils::string::is_numeric("+")); + EXPECT_FALSE(utils::string::is_numeric("-")); + + EXPECT_FALSE(utils::string::is_numeric("")); +} + +TEST(utils_string, to_bool) { + EXPECT_TRUE(utils::string::to_bool("1")); + EXPECT_TRUE(utils::string::to_bool("-1")); + EXPECT_TRUE(utils::string::to_bool("0.1")); + EXPECT_TRUE(utils::string::to_bool("-0.1")); + EXPECT_TRUE(utils::string::to_bool("00000.1000000")); + EXPECT_TRUE(utils::string::to_bool("true")); + + EXPECT_FALSE(utils::string::to_bool("false")); + EXPECT_FALSE(utils::string::to_bool("0")); + EXPECT_FALSE(utils::string::to_bool("00000.00000")); +} +} // namespace monitarr