├── .github └── workflows │ ├── ci.yaml │ ├── codeql.yaml │ └── documentation-links.yaml ├── .gitignore ├── CMakeLists.txt ├── Extra ├── Arch │ └── PKGBUILD └── libmodule.pc.in ├── LICENSE ├── Lib ├── CMakeLists.txt ├── core │ ├── CMakeLists.txt │ ├── ctx.c │ ├── ctx.h │ ├── evts.c │ ├── evts.h │ ├── fs │ │ ├── fs.c │ │ ├── fs.h │ │ └── fs_noop.c │ ├── globals.h │ ├── main.c │ ├── mod.c │ ├── mod.h │ ├── poll │ │ ├── README.md │ │ ├── cmn_linux.c │ │ ├── epoll.c │ │ ├── kqueue.c │ │ ├── poll.h │ │ └── uring.c │ ├── ps.c │ ├── ps.h │ ├── public │ │ └── module │ │ │ ├── .gitignore │ │ │ ├── cmn.h.in │ │ │ ├── ctx.h.in │ │ │ ├── mod.h │ │ │ └── mod_easy.h │ ├── src.c │ └── src.h ├── mem │ ├── CMakeLists.txt │ ├── mem.c │ └── public │ │ └── module │ │ └── mem │ │ └── mem.h ├── structs │ ├── CMakeLists.txt │ ├── README.md │ ├── bst.c │ ├── list.c │ ├── map.c │ ├── public │ │ └── module │ │ │ └── structs │ │ │ ├── bst.h │ │ │ ├── itr.h │ │ │ ├── list.h │ │ │ ├── map.h │ │ │ ├── queue.h │ │ │ └── stack.h │ ├── queue.c │ └── stack.c ├── thpool │ ├── CMakeLists.txt │ ├── public │ │ └── module │ │ │ └── thpool │ │ │ └── thpool.h │ └── thpool.c └── utils │ ├── CMakeLists.txt │ ├── log.c │ ├── log.h │ ├── mem.c │ ├── mem.h │ ├── utils.c │ └── utils.h ├── README.md ├── Samples ├── CMakeLists.txt ├── Easy │ ├── README.md │ ├── doggo.c │ └── pippo.c ├── Poll │ ├── README.md │ ├── doggo.c │ ├── main.c │ └── pippo.c ├── README.md ├── SharedSrc │ ├── README.md │ ├── main.c │ └── mod.c ├── Task │ ├── README.md │ └── pippo.c ├── Thpool │ ├── README.md │ └── main.c └── testModule.c ├── TODO.md ├── cmake ├── FindCmocka.cmake ├── FindSphinx.cmake ├── FindValgrind.cmake └── JoinPaths.cmake ├── docs ├── concepts │ ├── build.md │ ├── concepts.md │ ├── ctx.md │ └── mod.md ├── core │ ├── core.md │ ├── ctx.md │ └── mod.md ├── index.md ├── mem │ └── mem.md ├── structs │ ├── bst.md │ ├── list.md │ ├── map.md │ ├── queue.md │ ├── stack.md │ └── structs.md └── thpool │ └── thpool.md ├── mkdocs.yml └── tests ├── CMakeLists.txt ├── README.md ├── main.c ├── test_bst.c ├── test_bst.h ├── test_commons.h ├── test_ctx.c ├── test_ctx.h ├── test_evt_ref.c ├── test_evt_ref.h ├── test_list.c ├── test_list.h ├── test_map.c ├── test_map.h ├── test_mem.c ├── test_mem.h ├── test_mod.c ├── test_mod.h ├── test_perf.c ├── test_perf.h ├── test_queue.c ├── test_queue.h ├── test_stack.c ├── test_stack.h ├── test_thpool.c └── test_thpool.h /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build-ubuntu-amd64: 11 | name: build-ubuntu-amd64 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Install CMocka 16 | run: | 17 | sudo apt update 18 | sudo apt install -y --no-install-recommends libcmocka-dev valgrind dpkg rpm libfuse3-dev fuse3 libkqueue-dev liburing-dev 19 | - name: Build and test no FS 20 | run: | 21 | mkdir build && cd build 22 | cmake -DBUILD_SAMPLES=On -DBUILD_TESTS=On .. 23 | make package 24 | ctest -V 25 | cmake -DBUILD_OOT_TEST=On .. 26 | sudo make install 27 | - name: Build and test FS 28 | run: | 29 | mkdir build_fs && cd build_fs 30 | cmake -DBUILD_TESTS=On -DBUILD_SAMPLES=On -DWITH_FS=On .. 31 | make package 32 | ctest -V 33 | cmake -DBUILD_OOT_TEST=On .. 34 | sudo make install 35 | - name: Build and test libkqueue 36 | run: | 37 | mkdir build_kqueue && cd build_kqueue 38 | cmake -DBUILD_SAMPLES=On -DBUILD_TESTS=On -DWITH_LIBKQUEUE=true -DWITH_VALGRIND=false .. 39 | make package 40 | ctest -V 41 | cmake -DBUILD_OOT_TEST=On .. 42 | sudo make install 43 | - name: Build and test liburing 44 | run: | 45 | mkdir build_uring && cd build_uring 46 | cmake -DBUILD_SAMPLES=On -DBUILD_TESTS=On -DWITH_LIBURING=true -DWITH_VALGRIND=false .. 47 | make package 48 | ctest -V 49 | cmake -DBUILD_OOT_TEST=On .. 50 | sudo make install 51 | 52 | build-freebsd-amd64: 53 | runs-on: macos-12 54 | name: build-freebsd-amd64 55 | steps: 56 | - uses: actions/checkout@v3 57 | with: 58 | path: libmodule 59 | 60 | - name: Build and test 61 | uses: vmactions/freebsd-vm@v0 62 | with: 63 | usesh: true 64 | sync: sshfs 65 | prepare: | 66 | pkg install -y cmake cmocka pkgconf valgrind fusefs-libs3 67 | 68 | run: | 69 | cd libmodule/ 70 | mkdir build && cd build 71 | cmake -DBUILD_SAMPLES=On -DBUILD_TESTS=On -DBUILD_OOT_TEST=true .. 72 | make 73 | ctest -V 74 | make install 75 | cd .. 76 | mkdir build_fs && cd build_fs 77 | cmake -DBUILD_SAMPLES=On -DBUILD_TESTS=On -DBUILD_OOT_TEST=true -DWITH_FS=true .. 78 | make 79 | kldload fusefs 80 | ctest -V 81 | make install 82 | 83 | build-osx-amd64: 84 | name: build-osx-amd64 85 | runs-on: macos-latest 86 | steps: 87 | - uses: actions/checkout@v3 88 | - name: Install CMocka 89 | run: | 90 | brew update 91 | brew install cmocka 92 | - name: Build and test 93 | run: | 94 | mkdir -p build 95 | cd build && cmake -DBUILD_SAMPLES=On -DBUILD_TESTS=On .. 96 | cmake --build . 97 | ctest -V 98 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yaml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | branches: [ "master" ] 19 | 20 | jobs: 21 | analyze: 22 | name: Analyze 23 | runs-on: ubuntu-20.04 24 | permissions: 25 | actions: read 26 | contents: read 27 | security-events: write 28 | 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | language: [ 'cpp' ] 33 | 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v3 37 | with: 38 | fetch-depth: 0 39 | 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@v2 42 | with: 43 | languages: ${{ matrix.language }} 44 | 45 | - name: Update base image 46 | run: sudo apt update -y 47 | 48 | - name: Install build dependencies 49 | run: sudo DEBIAN_FRONTEND=noninteractive apt install cmake build-essential -y 50 | 51 | - name: Build project 52 | run: | 53 | mkdir build && cd build 54 | cmake .. 55 | make 56 | 57 | - name: Perform CodeQL Analysis 58 | uses: github/codeql-action/analyze@v2 59 | -------------------------------------------------------------------------------- /.github/workflows/documentation-links.yaml: -------------------------------------------------------------------------------- 1 | name: Read the Docs Pull Request Preview 2 | on: 3 | pull_request_target: 4 | types: 5 | - opened 6 | 7 | permissions: 8 | pull-requests: write 9 | 10 | jobs: 11 | documentation-links: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: readthedocs/actions/preview@v1 15 | with: 16 | project-slug: "libmodule" 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # Cmake build dir 55 | build/ 56 | Build/ 57 | 58 | # Kdevelop 59 | *.kdev4 60 | 61 | # Readthedocs doc build folder 62 | docs/_build 63 | 64 | .idea/ 65 | cmake-build-debug/ 66 | cmake-build-release/ 67 | venv/ 68 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.3.2) 2 | 3 | project(module VERSION 6.0.0 LANGUAGES C) 4 | 5 | if(NOT CMAKE_BUILD_TYPE) 6 | set(CMAKE_BUILD_TYPE Release) 7 | endif() 8 | 9 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") 10 | 11 | set(CMAKE_C_STANDARD_REQUIRED ON) 12 | set(CMAKE_C_STANDARD 11) 13 | 14 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wtype-limits -Wstrict-overflow -fno-strict-aliasing -Wformat -Wformat-security -fsanitize=undefined") 15 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") 16 | 17 | option(BUILD_TESTS "build ${PROJECT_NAME} tests" OFF) 18 | if(BUILD_TESTS) 19 | find_package(Cmocka) 20 | if (CMOCKA_FOUND) 21 | enable_testing() 22 | add_subdirectory(tests) 23 | message(STATUS "Tests building enabled.") 24 | else() 25 | message(WARNING "Missing cmocka.") 26 | endif() 27 | endif() 28 | 29 | option(BUILD_SAMPLES "build ${PROJECT_NAME} examples" OFF) 30 | if(BUILD_SAMPLES) 31 | add_subdirectory(Samples) 32 | message(STATUS "Examples building enabled.") 33 | endif() 34 | 35 | include(Lib/CMakeLists.txt) 36 | -------------------------------------------------------------------------------- /Extra/Arch/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Federico Di Pierro 2 | 3 | pkgname=libmodule 4 | pkgver=5.0.0 5 | pkgrel=1 6 | pkgdesc="C linux library to build simple and modular projects" 7 | arch=(any) 8 | url="https://github.com/FedeDP/${pkgname}" 9 | license=(MIT) 10 | depends=() 11 | makedepends=(git cmake) 12 | source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/${pkgver}.tar.gz") 13 | sha256sums=("fa82567c56313b69bea589f7a1a027217b722b9c885c2d645a826d8af307a6c9") 14 | 15 | prepare() { 16 | cd "${srcdir}/${pkgname}-${pkgver}" 17 | mkdir -p build 18 | } 19 | 20 | build() { 21 | cd "${srcdir}/${pkgname}-${pkgver}"/build 22 | cmake \ 23 | -G "Unix Makefiles" \ 24 | -DCMAKE_INSTALL_PREFIX=/usr \ 25 | -DCMAKE_INSTALL_LIBDIR=lib \ 26 | -DCMAKE_BUILD_TYPE="Release" \ 27 | ../ 28 | make 29 | } 30 | 31 | package() { 32 | cd "${srcdir}/${pkgname}-${pkgver}"/build 33 | make DESTDIR="$pkgdir" install 34 | } 35 | -------------------------------------------------------------------------------- /Extra/libmodule.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=@CMAKE_INSTALL_PREFIX@ 3 | libdir=@libdir_pc@ 4 | includedir=@includedir_pc@ 5 | 6 | Name: lib@PKG_TARGET@ 7 | Description: @PKG_DESC@ 8 | Version: @PROJECT_VERSION@ 9 | 10 | Requires: 11 | Cflags: -I${includedir} @PKG_INCS@ 12 | Libs: -L${libdir} @PKG_DIRS@ @PKG_DEPS@ 13 | Libs.private: -L${libdir} -lmodule_utils_internal 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Federico Di Pierro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.3.2) 2 | 3 | include(GNUInstallDirs) 4 | include(JoinPaths) 5 | 6 | option(STATIC_MODULE "build ${PROJECT_NAME} as static library" OFF) 7 | if(STATIC_MODULE) 8 | set(LIBRARY_TYPE STATIC) 9 | else() 10 | set(LIBRARY_TYPE SHARED) 11 | endif() 12 | 13 | macro(fill_pc_vars target desc) 14 | set(PKG_TARGET ${target}) 15 | set(PKG_DESC ${desc}) 16 | 17 | get_target_property(int_link_dirs ${target} INTERFACE_LINK_DIRECTORIES) # -L flags 18 | if(NOT int_link_dirs) 19 | set(PKG_DIRS "") 20 | else() 21 | string(REPLACE ";" " -L" PKG_DIRS ";${int_link_dirs}") 22 | endif() 23 | 24 | get_target_property(int_link_libs ${target} INTERFACE_LINK_LIBRARIES) # -l flags 25 | if(NOT int_link_libs) 26 | set(PKG_DEPS "-l${target}") 27 | else() 28 | string(REPLACE ";" " -l" PKG_DEPS ";${int_link_libs};${target}") 29 | endif() 30 | 31 | get_target_property(int_incl_dirs ${target} INTERFACE_INCLUDE_DIRECTORIES) # -I flags 32 | if(NOT int_incl_dirs) 33 | set(PKG_INCS "") 34 | else() 35 | string(REPLACE ";" " -I" PKG_INCS ";${int_incl_dirs}") 36 | endif() 37 | endmacro() 38 | 39 | join_paths(libdir_pc "\${exec_prefix}" "${CMAKE_INSTALL_LIBDIR}") 40 | join_paths(includedir_pc "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") 41 | 42 | include(Lib/core/CMakeLists.txt) 43 | 44 | # Some pretty printings 45 | MESSAGE(STATUS "Building ${PROJECT_NAME}-${PROJECT_VERSION}") 46 | MESSAGE(STATUS "Build type: ${CMAKE_BUILD_TYPE}") 47 | MESSAGE(STATUS "Library Type: ${LIBRARY_TYPE}") 48 | MESSAGE(STATUS "Poll plugin: ${POLL_PLUGIN}") 49 | MESSAGE(STATUS "Target OS: ${CMAKE_SYSTEM_NAME}") 50 | 51 | option(BUILD_OOT_TEST "build ${PROJECT_NAME} out of tree test (system integration test)" OFF) 52 | if(BUILD_OOT_TEST) 53 | # Script that tests that pkg-config is able to find libmodule_core 54 | # and a small binary can be compiled succesfully (Task example) 55 | install( 56 | CODE 57 | " 58 | message(STATUS \"Testing out of tree build.\") 59 | set(ENV{PKG_CONFIG_PATH} \"${CMAKE_INSTALL_PREFIX}/share/pkgconfig/\") 60 | find_package(PkgConfig REQUIRED) 61 | pkg_check_modules(PKG_LIBMODULE REQUIRED libmodule_core) 62 | execute_process( 63 | COMMAND ${CMAKE_C_COMPILER} ${PROJECT_SOURCE_DIR}/Samples/Task/pippo.c -o ${CMAKE_CURRENT_BINARY_DIR}/pippo \${PKG_LIBMODULE_LDFLAGS} \${PKG_LIBMODULE_CFLAGS} 64 | RESULT_VARIABLE IS_ERR 65 | COMMAND_ECHO STDOUT 66 | ) 67 | if(IS_ERR) 68 | message(FATAL_ERROR \"error building the source file.\") 69 | endif() 70 | set(ENV{LD_LIBRARY_PATH} \"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/\") 71 | execute_process( 72 | COMMAND ${CMAKE_CURRENT_BINARY_DIR}/pippo 73 | RESULT_VARIABLE IS_ERR 74 | ) 75 | file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/pippo) 76 | if(IS_ERR) 77 | message(FATAL_ERROR \"error running the source file.\") 78 | else() 79 | message(STATUS \"SUCCESS: source file built and run.\") 80 | endif() 81 | " 82 | COMPONENT runtime 83 | ) 84 | endif() 85 | -------------------------------------------------------------------------------- /Lib/core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.3.2) 2 | 3 | include(GNUInstallDirs) 4 | 5 | # Find sources 6 | file(GLOB SOURCES Lib/core/*.c) 7 | 8 | # Internal deps 9 | include(Lib/utils/CMakeLists.txt) 10 | include(Lib/structs/CMakeLists.txt) 11 | include(Lib/thpool/CMakeLists.txt) 12 | include(Lib/mem/CMakeLists.txt) 13 | 14 | option(WITH_FS "build ${PROJECT_NAME} with libfuse support" OFF) 15 | if (WITH_FS) 16 | find_package(PkgConfig) 17 | pkg_check_modules(FUSE REQUIRED fuse3) 18 | set(SOURCES ${SOURCES} Lib/core/fs/fs.c) 19 | set(M_CTX_HAS_FS "#define M_CTX_HAS_FS 1") 20 | else() 21 | set(SOURCES ${SOURCES} Lib/core/fs/fs_noop.c) 22 | endif() 23 | 24 | # Set requested poll plugin 25 | if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") 26 | option(WITH_LIBURING "build ${PROJECT_NAME} with liburing support" OFF) 27 | option(WITH_LIBKQUEUE "build ${PROJECT_NAME} with libkqueue support" OFF) 28 | add_compile_definitions(_GNU_SOURCE) 29 | else() 30 | include(CheckFunctionExists) 31 | check_function_exists(kqueue KQUEUE_IN_LIBC) 32 | if(NOT KQUEUE_IN_LIBC) 33 | set(WITH_LIBKQUEUE true) 34 | else() 35 | set(WITH_KQUEUE true) 36 | endif() 37 | endif() 38 | 39 | if(WITH_LIBURING) 40 | find_package(PkgConfig) 41 | pkg_check_modules(URING REQUIRED liburing) 42 | set(SOURCES ${SOURCES} Lib/core/poll/uring.c Lib/core/poll/cmn_linux.c) 43 | set(POLL_PLUGIN "liburing") 44 | elseif(WITH_LIBKQUEUE) 45 | find_package(PkgConfig) 46 | pkg_check_modules(KQUEUE REQUIRED libkqueue) 47 | set(SOURCES ${SOURCES} Lib/core/poll/kqueue.c) 48 | set(POLL_PLUGIN "libkqueue") 49 | elseif(WITH_KQUEUE) 50 | set(SOURCES ${SOURCES} Lib/core/poll/kqueue.c) 51 | set(POLL_PLUGIN "kqueue") 52 | else() 53 | set(SOURCES ${SOURCES} Lib/core/poll/epoll.c Lib/core/poll/cmn_linux.c) 54 | set(POLL_PLUGIN "epoll") 55 | endif() 56 | 57 | # Configure headers 58 | configure_file(Lib/core/public/module/cmn.h.in ${PROJECT_SOURCE_DIR}/Lib/core/public/module/cmn.h @ONLY) 59 | configure_file(Lib/core/public/module/ctx.h.in ${PROJECT_SOURCE_DIR}/Lib/core/public/module/ctx.h @ONLY) 60 | 61 | file(GLOB PUBLIC_H Lib/core/public/module/*.h) 62 | 63 | add_library(${PROJECT_NAME}_core ${LIBRARY_TYPE} ${SOURCES}) 64 | 65 | set_target_properties( 66 | ${PROJECT_NAME}_core PROPERTIES 67 | VERSION ${PROJECT_VERSION} 68 | SOVERSION ${PROJECT_VERSION_MAJOR} 69 | PUBLIC_HEADER "${PUBLIC_H}" 70 | C_VISIBILITY_PRESET hidden 71 | ) 72 | 73 | target_include_directories(${PROJECT_NAME}_core PRIVATE Lib/core/ Lib/core/public/ Lib/core/fs/ Lib/core/poll/ Lib/utils/ Lib/structs/ Lib/structs/public/ Lib/mem/ Lib/mem/public/ Lib/thpool/) 74 | target_include_directories(${PROJECT_NAME}_core PUBLIC ${URING_INCLUDE_DIRS} ${KQUEUE_INCLUDE_DIRS} ${FUSE_INCLUDE_DIRS}) 75 | target_link_libraries(${PROJECT_NAME}_core PUBLIC ${URING_LIBRARIES} ${KQUEUE_LIBRARIES} ${FUSE_LIBRARIES} ${PROJECT_NAME}_structs ${PROJECT_NAME}_thpool ${PROJECT_NAME}_mem dl) 76 | target_link_libraries(${PROJECT_NAME}_core PRIVATE ${PROJECT_NAME}_utils_internal) 77 | target_link_directories(${PROJECT_NAME}_core PUBLIC ${URING_LIBRARY_DIRS} ${KQUEUE_LIBRARY_DIRS} ${FUSE_LIBRARY_DIRS}) 78 | target_compile_definitions(${PROJECT_NAME}_core PRIVATE LIBMODULE_LOG_CTX=CORE) 79 | 80 | fill_pc_vars(${PROJECT_NAME}_core "Libmodule core library") 81 | configure_file(Extra/libmodule.pc.in libmodule_core.pc @ONLY) 82 | 83 | install(TARGETS ${PROJECT_NAME}_core 84 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 85 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 86 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/module 87 | ) 88 | 89 | install(FILES ${CMAKE_BINARY_DIR}/libmodule_core.pc 90 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) 91 | 92 | install(FILES LICENSE 93 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/licenses/lib${PROJECT_NAME}) 94 | 95 | # Packaging support 96 | # THANKS to libkqueue CMakeLists.txt for packaging support :) 97 | SET(CPACK_SET_DESTDIR "on") 98 | set(CPACK_PACKAGE_NAME lib${PROJECT_NAME}) 99 | set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) 100 | set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) 101 | set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) 102 | set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) 103 | 104 | # Metadata common to all packaging systems 105 | set(CPACK_PACKAGE_CONTACT "Federico Di Pierro ") 106 | set(CPACK_PACKAGE_DESCRIPTION "Libmodule offers an elegant and simple implementation of an actor library, aiming at letting developers easily create modular C projects.") 107 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Simple and elegant implementation of an actor library for C.") 108 | 109 | # RPM Specific configuration 110 | set(CPACK_RPM_PACKAGE_LICENSE "MIT") 111 | set(CPACK_RPM_PACKAGE_URL "https://github.com/FedeDP/libmodule") 112 | set(CPACK_RPM_PACKAGE_GROUP "System Environment/Libraries") 113 | set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "${CMAKE_INSTALL_DATAROOTDIR}/licenses ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig") 114 | if(WITH_FS) 115 | set(CPACK_RPM_PACKAGE_REQUIRES "fuse3-libs") 116 | endif() 117 | if(WITH_LIBURING) 118 | set(CPACK_RPM_PACKAGE_REQUIRES "liburing") 119 | elseif(WITH_LIBKQUEUE) 120 | set(CPACK_RPM_PACKAGE_REQUIRES "libkqueue") 121 | endif() 122 | set(CPACK_RPM_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION}) 123 | set(CPACK_RPM_FILE_NAME RPM-DEFAULT) 124 | 125 | # DEB Specific configuration 126 | set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/FedeDP/libmodule") 127 | set(CPACK_DEBIAN_PACKAGE_SECTION "libs") 128 | if(WITH_FS) 129 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libfuse3-3") 130 | endif() 131 | if(WITH_LIBURING) 132 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "liburing") 133 | elseif(WITH_LIBKQUEUE) 134 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libkqueue0") 135 | endif() 136 | set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) 137 | 138 | set(CPACK_GENERATOR DEB RPM TGZ) 139 | 140 | include(CPack) 141 | -------------------------------------------------------------------------------- /Lib/core/ctx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "public/module/ctx.h" 4 | #include "public/module/structs/map.h" 5 | #include "public/module/thpool/thpool.h" 6 | #include "globals.h" 7 | #include "src.h" 8 | 9 | #define M_CTX_DEFAULT_EVENTS 64 10 | 11 | #define M_CTX() m_ctx_t *c = m_ctx(); 12 | 13 | #define M_CTX_ASSERT() \ 14 | M_CTX(); \ 15 | M_RET_ASSERT(c, -EPIPE) 16 | 17 | typedef struct { 18 | void *data; // Context's poll priv data (depends upon poll_plugin) 19 | int max_events; // Max number of returned events for poll_plugin 20 | } poll_priv_t; 21 | 22 | typedef struct { 23 | uint64_t looping_start_time; 24 | uint64_t idle_time; 25 | uint64_t recv_msgs; 26 | size_t running_modules; 27 | } ctx_stats_t; 28 | 29 | /* Ctx states */ 30 | typedef enum { 31 | M_CTX_IDLE, 32 | M_CTX_LOOPING, 33 | } m_ctx_states; 34 | 35 | typedef struct { 36 | m_src_tmr_t tmr; 37 | ev_src_t *src; 38 | } ctx_tick_t; 39 | 40 | /* Struct that holds data for context */ 41 | /* 42 | * MEM-REFS for ctx: 43 | * + 1 because it is registered 44 | * + 1 for each module registered in the context 45 | * (thus it won't be actually destroyed until any module is inside it) 46 | */ 47 | struct _ctx { 48 | CONST const char *name; 49 | m_ctx_states state; 50 | bool quit; // Context's quit flag 51 | uint8_t quit_code; // Context's quit code, returned by modules_ctx_loop() 52 | bool finalized; // Whether the context is finalized, ie: no more modules can be registered 53 | m_log_cb logger; // Context's log callback 54 | m_map_t *modules; // Context's modules 55 | m_mod_t *curr_mod; // Current module's being processed. NULL when we are outside of any module. 56 | poll_priv_t ppriv; // Priv data for poll_plugin implementation 57 | CONST m_ctx_flags flags; // Context's flags 58 | void *fs; // FS context handler. Null if unsupported 59 | ctx_stats_t stats; // Context' stats 60 | m_thpool_t *thpool; // thpool for M_SRC_TYPE_TASK srcs; lazily created 61 | ctx_tick_t tick; // Tick for ctx sending a M_PS_CTX_TICK message 62 | CONST const void *userdata; // Context's user defined data 63 | }; 64 | 65 | m_ctx_t *m_ctx(void); 66 | void ctx_logger(const m_ctx_t *c, const m_mod_t *mod, const char *fmt, ...); 67 | -------------------------------------------------------------------------------- /Lib/core/evts.c: -------------------------------------------------------------------------------- 1 | #include "evts.h" 2 | #include "ps.h" 3 | #include "ctx.h" 4 | 5 | /************************************ 6 | * Code related to events handling. * 7 | ************************************/ 8 | 9 | static void evt_dtor(void *data) { 10 | evt_priv_t *evt = (evt_priv_t *)data; 11 | m_mem_unref(evt->src); 12 | /* We use fd_evt as all messages share address inside union */ 13 | m_mem_unref(evt->evt.fd_evt); 14 | } 15 | 16 | /** Private API **/ 17 | 18 | evt_priv_t *new_evt(ev_src_t *src) { 19 | evt_priv_t *msg = m_mem_new(sizeof(evt_priv_t), evt_dtor); 20 | if (msg) { 21 | msg->evt.type = src->type; 22 | msg->src = m_mem_ref(src); 23 | } 24 | return msg; 25 | } 26 | 27 | /** Public API **/ 28 | 29 | _public_ int m_mod_become(m_mod_t *mod, m_evt_cb new_on_evt) { 30 | M_PARAM_ASSERT(new_on_evt); 31 | M_MOD_ASSERT_STATE(mod, M_MOD_RUNNING); 32 | M_MOD_CONSUME_TOKEN(mod); 33 | 34 | return m_stack_push(mod->recvs, new_on_evt);; 35 | } 36 | 37 | _public_ int m_mod_unbecome(m_mod_t *mod) { 38 | M_MOD_ASSERT_STATE(mod, M_MOD_RUNNING); 39 | M_MOD_CONSUME_TOKEN(mod); 40 | 41 | if (m_stack_pop(mod->recvs) != NULL) { 42 | return 0; 43 | } 44 | return -EINVAL; 45 | } 46 | 47 | _public_ int m_mod_stash(m_mod_t *mod, const m_evt_t *evt) { 48 | M_MOD_ASSERT_STATE(mod, M_MOD_RUNNING); 49 | M_PARAM_ASSERT(evt); 50 | M_MOD_CONSUME_TOKEN(mod); 51 | 52 | evt_priv_t *priv_evt = (evt_priv_t *)evt; 53 | m_src_flags prio_flags = 0; 54 | if (priv_evt->src) { 55 | prio_flags = priv_evt->src->flags & M_SRC_PRIO_MASK; 56 | } 57 | // Cannot stash HIGH priority evts! 58 | M_RET_ASSERT(!(prio_flags & M_SRC_PRIO_HIGH), -EPERM); 59 | 60 | return m_queue_enqueue(mod->stashed, m_mem_ref((void *)evt));; 61 | } 62 | 63 | _public_ ssize_t m_mod_unstash(m_mod_t *mod, size_t len) { 64 | M_MOD_ASSERT_STATE(mod, M_MOD_RUNNING); 65 | M_PARAM_ASSERT(len > 0); 66 | M_MOD_CONSUME_TOKEN(mod); 67 | 68 | m_queue_t *unstashed = m_queue_new(mem_dtor); 69 | M_ALLOC_ASSERT(unstashed); 70 | 71 | m_itr_foreach(mod->stashed, { 72 | if (m_idx + 1 == len) { 73 | memhook._free(m_itr); 74 | break; 75 | } 76 | m_evt_t *evt = m_itr_get(m_itr); 77 | /* 78 | * Here evt has 1 ref; call_pubsub_cb() would drop it 79 | * thus invalidating ptr. 80 | * But m_itr_rm() still needs ptrs! 81 | * Keep evts alive. 82 | */ 83 | m_queue_enqueue(unstashed, m_mem_ref(evt)); 84 | m_itr_rm(m_itr); 85 | }); 86 | 87 | const size_t processed = m_queue_len(unstashed); 88 | call_pubsub_cb(mod, unstashed); 89 | return processed; 90 | } 91 | 92 | _public_ int m_mod_set_batch_size(m_mod_t *mod, size_t len) { 93 | M_MOD_ASSERT(mod); 94 | M_MOD_CONSUME_TOKEN(mod); 95 | 96 | mod->batch.len = len; 97 | return 0; 98 | } 99 | 100 | _public_ int m_mod_set_batch_timeout(m_mod_t *mod, uint64_t timeout_ns) { 101 | M_MOD_ASSERT(mod); 102 | 103 | // src_deregister and src_register already consume a token 104 | 105 | /* If it was already set, remove old timer */ 106 | if (mod->batch.timer.ns != 0) { 107 | m_mod_src_deregister_tmr(mod, &mod->batch.timer); 108 | } 109 | mod->batch.timer.clock_id = CLOCK_MONOTONIC; 110 | mod->batch.timer.ns = timeout_ns; 111 | if (timeout_ns != 0) { 112 | // If batching by size is disabled 113 | if (mod->batch.len == 0) { 114 | // Set a maximum value for batching so that only timed batching will be effective 115 | mod->batch.len = SIZE_MAX; 116 | } 117 | return m_mod_src_register_tmr(mod, &mod->batch.timer, M_SRC_INTERNAL | M_SRC_PRIO_HIGH, &mod->batch); 118 | } 119 | return 0; 120 | } 121 | -------------------------------------------------------------------------------- /Lib/core/evts.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "src.h" 4 | 5 | /* Struct that holds an event + its source, private */ 6 | typedef struct _ev_priv { 7 | m_evt_t evt; 8 | ev_src_t *src; // Ref to src that caused the event 9 | } evt_priv_t; 10 | 11 | evt_priv_t *new_evt(ev_src_t *src); 12 | 13 | -------------------------------------------------------------------------------- /Lib/core/fs/fs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "public/module/mod.h" 4 | #include "ctx.h" 5 | 6 | int fs_create(m_ctx_t *c); 7 | int fs_start(m_ctx_t *c); 8 | int fs_notify(m_mod_t *mod, const m_queue_t *const evts); 9 | int fs_ctx_stopped(m_mod_t *mod); 10 | int fs_cleanup(m_mod_t *mod); 11 | int fs_stop(m_ctx_t *c); 12 | int fs_destroy(m_ctx_t *c); 13 | -------------------------------------------------------------------------------- /Lib/core/fs/fs_noop.c: -------------------------------------------------------------------------------- 1 | #include "fs.h" 2 | 3 | int fs_create(m_ctx_t *c) { 4 | return 0; 5 | } 6 | 7 | int fs_start(m_ctx_t *c) { 8 | return 0; 9 | } 10 | 11 | int fs_notify(m_mod_t *mod, const m_queue_t *const evts) { 12 | return 0; 13 | } 14 | 15 | int fs_ctx_stopped(m_mod_t *mod) { 16 | return 0; 17 | } 18 | 19 | int fs_cleanup(m_mod_t *mod) { 20 | return 0; 21 | } 22 | 23 | int fs_stop(m_ctx_t *c) { 24 | return 0; 25 | } 26 | 27 | int fs_destroy(m_ctx_t *c) { 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /Lib/core/globals.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include // PRIu64 7 | #include 8 | #include 9 | #include "public/module/structs/itr.h" 10 | #include "public/module/mem/mem.h" 11 | #include "public/module/ctx.h" 12 | #include "utils.h" 13 | #include "mem.h" 14 | #include "log.h" 15 | 16 | /* 17 | * Const-after-init struct members 18 | * are marked by this macro 19 | */ 20 | #define CONST 21 | 22 | #define BILLION 1000000000 23 | 24 | #define M_MEM_LOCK(mem, func) \ 25 | m_mem_ref(mem); \ 26 | func; \ 27 | m_mem_unref(mem); 28 | 29 | void mem_dtor(void *src); 30 | -------------------------------------------------------------------------------- /Lib/core/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "globals.h" 4 | #include "public/module/mod_easy.h" 5 | 6 | /*********************************************************** 7 | * Code related to main library ctor/dtor + main() symbol. * 8 | ***********************************************************/ 9 | 10 | _public_ _weak_ void m_ctx_pre_loop(int argc, char *argv[]) { 11 | M_DEBUG("Pre-looping libmodule easy API.\n"); 12 | } 13 | 14 | _public_ _weak_ void m_ctx_post_loop(int argc, char *argv[]) { 15 | M_DEBUG("Post-looping libmodule easy API.\n"); 16 | } 17 | 18 | /* 19 | * This is an exported global weak symbol. 20 | * It means that if a program does not implement any main(int, char *[]), 21 | * this will be used by default. 22 | * 23 | * All it does is looping on default ctx. 24 | */ 25 | _public_ _weak_ int main(int argc, char *argv[]) { 26 | m_ctx_pre_loop(argc, argv); 27 | const int ret = m_ctx_loop(); 28 | m_ctx_post_loop(argc, argv); 29 | m_ctx_deregister(); // default_ctx may be NULL here, if eg: all modules where deregistered. We don't care 30 | return ret; 31 | } 32 | 33 | void mem_dtor(void *src) { 34 | m_mem_unref(src); 35 | } 36 | 37 | _public_ int m_set_memhook( void *(*_malloc)(size_t), 38 | void *(*_calloc)(size_t, size_t), 39 | void (*_free)(void *)) { 40 | M_PARAM_ASSERT(_malloc); 41 | M_PARAM_ASSERT(_calloc); 42 | M_PARAM_ASSERT(_free); 43 | 44 | memhook._malloc = _malloc; 45 | memhook._calloc = _calloc; 46 | memhook._free = _free; 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /Lib/core/mod.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "public/module/mod.h" 4 | #include "globals.h" 5 | 6 | #define M_MOD_CTX(mod) \ 7 | m_ctx_t *c = mod->ctx; 8 | 9 | #define M_MOD_ASSERT(mod) \ 10 | M_PARAM_ASSERT(mod); \ 11 | M_RET_ASSERT(!m_mod_is(mod, M_MOD_ZOMBIE), -EACCES); \ 12 | M_RET_ASSERT(mod->ctx == m_ctx(), -EPERM) 13 | 14 | #define M_MOD_ASSERT_PERM(mod, perm) \ 15 | M_MOD_ASSERT(mod); \ 16 | M_RET_ASSERT(!(mod->flags & perm), -EPERM) 17 | 18 | #define M_MOD_ASSERT_STATE(mod, state) \ 19 | M_MOD_ASSERT(mod); \ 20 | M_RET_ASSERT(m_mod_is(mod, state), -EACCES) 21 | 22 | #define M_MOD_CONSUME_TOKEN(mod) \ 23 | M_RET_ASSERT(mod->tb.tokens > 0, -EAGAIN) \ 24 | mod->tb.tokens--; \ 25 | fetch_ms(&mod->stats.last_seen, &mod->stats.action_ctr); 26 | 27 | typedef struct { 28 | uint64_t registration_time; 29 | uint64_t last_seen; 30 | uint64_t action_ctr; 31 | uint64_t sent_msgs; 32 | uint64_t recv_msgs; 33 | } mod_stats_t; 34 | 35 | typedef struct { 36 | size_t len; 37 | m_src_tmr_t timer; 38 | m_queue_t *events; 39 | } mod_batch_t; 40 | 41 | typedef struct { 42 | uint16_t rate; 43 | uint64_t burst; 44 | uint64_t tokens; 45 | m_src_tmr_t timer; 46 | } mod_tb_t; 47 | 48 | /* Forward declare ctx handler */ 49 | typedef struct _ctx m_ctx_t; 50 | 51 | /* Struct that holds data for each module */ 52 | /* 53 | * MEM-REFS for mod: 54 | * + 1 because it is registered 55 | * + 1 for each m_mod_ref() called on it (included m_mod_register() when mod_ref != NULL) 56 | * + 1 for each PS message sent (ie: message's sender is a new reference for sender) 57 | * + 1 for each fs open() call on it 58 | * Moreover, a reference is held while retrieving an event for the module and calling its on_evt() cb, 59 | * to avoid user calling m_mod_deregister() and invalidating the pointer. 60 | */ 61 | struct _mod { 62 | m_mod_states state; // module's state 63 | CONST m_mod_flags flags; // Module's flags 64 | int pubsub_fd[2]; // In and Out pipe for pubsub msg 65 | mod_stats_t stats; // Module's stats 66 | CONST m_mod_hook_t hook; // module's user defined callbacks 67 | m_stack_t *recvs; // Stack of recv functions for module_become/unbecome (stack of funpointers) 68 | CONST const void *userdata; // module's user defined data 69 | void *fs; // FS module priv data. NULL if unsupported 70 | CONST const char *name; // module's name 71 | mod_batch_t batch; // Events' batching informations 72 | mod_tb_t tb; // Mod's tockenbucket 73 | CONST void *dlhandle; // Handle for plugin (NULL if not a plugin) 74 | m_bst_t *srcs[M_SRC_TYPE_END]; // module's event sources 75 | m_map_t *subscriptions; // module's subscriptions (map of ev_src_t*) 76 | m_queue_t *stashed; // module's stashed messages 77 | m_list_t *bound_mods; // modules that are bound to this module's state 78 | CONST m_ctx_t *ctx; // Module's ctx -> even if ctx is threadspecific data, we need to know the context a module was registered into, to avoid user passing modules around to another thread/context 79 | }; 80 | 81 | int evaluate_module(void *data, const char *key, void *value); 82 | int start(m_mod_t *mod, bool starting); 83 | int stop(m_mod_t *mod, bool stopping); 84 | int mod_deregister(m_mod_t **mod, bool from_user); 85 | void mod_dump(const m_mod_t *mod, bool log_mod, const char *indent); 86 | -------------------------------------------------------------------------------- /Lib/core/poll/README.md: -------------------------------------------------------------------------------- 1 | ## Poll plugins 2 | 3 | In this folder you can find poll plugins to support various OSes. 4 | As of today, an *epoll* plugin (for Linux) and a *kqueue* plugin (for BSD and MacOS) are provided. 5 | Moreover, an experimental *uring* plugin is provided; it must be explicitly built with -DWITH_LIBURING=true cmake option. 6 | Each plugin must implement and provide [poll_priv](https://github.com/FedeDP/libmodule/blob/master/Lib/poll_priv.h) interface. 7 | In [CMakeLists.txt](https://github.com/FedeDP/libmodule/blob/master/CMakeLists.txt#L12) correct plugin for target OS is built. 8 | 9 | *Any pull request to expand libmodule's availability is warmly welcomed.* 10 | -------------------------------------------------------------------------------- /Lib/core/poll/cmn_linux.c: -------------------------------------------------------------------------------- 1 | #include "poll.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /* Inotify related defines */ 14 | #define BUF_LEN (sizeof(struct inotify_event) + NAME_MAX + 1) 15 | 16 | static void create_timerfd(ev_src_t *tmp); 17 | static void create_signalfd(ev_src_t *tmp); 18 | static void create_inotifyfd(ev_src_t *tmp); 19 | static void create_pidfd(ev_src_t *tmp); 20 | static void create_eventfd(ev_src_t *tmp); 21 | 22 | static void create_timerfd(ev_src_t *tmp) { 23 | tmp->tmr_src.f.fd = timerfd_create(tmp->tmr_src.its.clock_id, TFD_NONBLOCK | TFD_CLOEXEC); 24 | struct itimerspec timerValue = {{0}}; 25 | timerValue.it_value.tv_sec = tmp->tmr_src.its.ns / BILLION; 26 | timerValue.it_value.tv_nsec = tmp->tmr_src.its.ns % BILLION; 27 | if (!(tmp->flags & M_SRC_ONESHOT)) { 28 | /* Set interval */ 29 | memcpy(&timerValue.it_interval, &timerValue.it_value, sizeof(struct timespec)); 30 | } 31 | const int abs_fl = tmp->flags & M_SRC_TMR_ABSOLUTE ? TFD_TIMER_ABSTIME : 0; 32 | timerfd_settime(tmp->tmr_src.f.fd, abs_fl, &timerValue, NULL); 33 | } 34 | 35 | static void create_signalfd(ev_src_t *tmp) { 36 | sigset_t mask; 37 | sigemptyset(&mask); 38 | sigaddset(&mask, tmp->sgn_src.sgs.signo); 39 | sigprocmask(SIG_BLOCK, &mask, NULL); 40 | tmp->sgn_src.f.fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); 41 | } 42 | 43 | static void create_inotifyfd(ev_src_t *tmp) { 44 | tmp->path_src.f.fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 45 | inotify_add_watch(tmp->path_src.f.fd, tmp->path_src.pt.path, tmp->path_src.pt.events); 46 | } 47 | 48 | static void create_pidfd(ev_src_t *tmp) { 49 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0) 50 | tmp->pid_src.f.fd = syscall(__NR_pidfd_open, tmp->pid_src.pid.pid, 0); 51 | #endif 52 | } 53 | 54 | static void create_eventfd(ev_src_t *tmp) { 55 | tmp->task_src.f.fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); 56 | } 57 | 58 | int poll_notify_userevent(poll_priv_t *priv, ev_src_t *src) { 59 | uint64_t u = 1; 60 | /* task_src and thresh_src share memory layout, thus using task_src.f.fd is ok */ 61 | if (write(src->task_src.f.fd, &u, sizeof(uint64_t)) == sizeof(uint64_t)) { 62 | return 0; 63 | } 64 | return -errno; 65 | } 66 | 67 | void create_priv_fd(ev_src_t *tmp) { 68 | switch (tmp->type) { 69 | case M_SRC_TYPE_TMR: 70 | create_timerfd(tmp); 71 | break; 72 | case M_SRC_TYPE_SGN: 73 | create_signalfd(tmp); 74 | break; 75 | case M_SRC_TYPE_PATH: 76 | create_inotifyfd(tmp); 77 | break; 78 | case M_SRC_TYPE_PID: 79 | create_pidfd(tmp); 80 | break; 81 | case M_SRC_TYPE_TASK: 82 | case M_SRC_TYPE_THRESH: 83 | create_eventfd(tmp); 84 | break; 85 | default: 86 | break; 87 | } 88 | } 89 | 90 | int poll_consume_sgn(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_sgn_t *sgn_msg) { 91 | struct signalfd_siginfo fdsi; 92 | const size_t s = read(src->sgn_src.f.fd, &fdsi, sizeof(struct signalfd_siginfo)); 93 | if (s == sizeof(struct signalfd_siginfo)) { 94 | return 0; 95 | } 96 | return -errno; 97 | } 98 | 99 | int poll_consume_tmr(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_tmr_t *tm_msg) { 100 | uint64_t t; 101 | if (read(src->tmr_src.f.fd, &t, sizeof(uint64_t)) == sizeof(uint64_t)) { 102 | return 0; 103 | } 104 | return -errno; 105 | } 106 | 107 | int poll_consume_pt(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_path_t *pt_msg) { 108 | char buffer[BUF_LEN]; 109 | const size_t length = read(src->path_src.f.fd, buffer, BUF_LEN); 110 | if (length > 0) { 111 | struct inotify_event *event = (struct inotify_event *) buffer; 112 | if (event->len) { 113 | pt_msg->events = event->mask; 114 | return 0; 115 | } 116 | } 117 | return -errno; 118 | } 119 | 120 | int poll_consume_pid(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_pid_t *pid_msg) { 121 | pid_msg->events = 0; 122 | return 0; // nothing to do 123 | } 124 | 125 | int poll_consume_task(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_task_t *task_msg) { 126 | uint64_t u; 127 | if (read(src->task_src.f.fd, &u, sizeof(uint64_t)) == sizeof(uint64_t)) { 128 | task_msg->retval = src->task_src.retval; 129 | return 0; 130 | } 131 | return -errno; 132 | } 133 | 134 | int poll_consume_thresh(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_thresh_t *thresh_msg) { 135 | uint64_t u; 136 | if (read(src->task_src.f.fd, &u, sizeof(uint64_t)) == sizeof(uint64_t)) { 137 | return 0; 138 | } 139 | return -errno; 140 | } 141 | -------------------------------------------------------------------------------- /Lib/core/poll/epoll.c: -------------------------------------------------------------------------------- 1 | #include "poll.h" 2 | #include 3 | 4 | extern void create_priv_fd(ev_src_t *tmp); 5 | 6 | typedef struct { 7 | int fd; 8 | struct epoll_event *pevents; 9 | } epoll_priv_t; 10 | 11 | #define GET_PRIV_DATA() epoll_priv_t *ep = (epoll_priv_t *)priv->data 12 | 13 | int poll_create(poll_priv_t *priv) { 14 | priv->data = memhook._calloc(1, sizeof(epoll_priv_t)); 15 | M_ALLOC_ASSERT(priv->data); 16 | GET_PRIV_DATA(); 17 | ep->fd = epoll_create1(EPOLL_CLOEXEC); 18 | return ep->fd != -1 ? 0 : -1; 19 | } 20 | 21 | int poll_set_new_evt(poll_priv_t *priv, ev_src_t *tmp, const enum op_type flag) { 22 | GET_PRIV_DATA(); 23 | 24 | /* Eventually alloc epoll data if needed */ 25 | if (!tmp->ev) { 26 | if (flag == ADD) { 27 | tmp->ev = memhook._calloc(1, sizeof(struct epoll_event)); 28 | M_ALLOC_ASSERT(tmp->ev); 29 | } else { 30 | /* We need to RM an unregistered ev. Fine. */ 31 | return 0; 32 | } 33 | } 34 | 35 | int f = flag == ADD ? EPOLL_CTL_ADD : EPOLL_CTL_DEL; 36 | struct epoll_event *ev = (struct epoll_event *)tmp->ev; 37 | ev->data.ptr = tmp; 38 | ev->events = EPOLLIN; 39 | if (tmp->flags & M_SRC_ONESHOT) { 40 | ev->events |= EPOLLONESHOT; 41 | } 42 | 43 | errno = 0; 44 | if (flag == ADD) { 45 | create_priv_fd(tmp); 46 | } 47 | 48 | /* 49 | * Note that fd_src shares 50 | * memory space with other fds (inside union) 51 | */ 52 | const int fd = tmp->fd_src.fd; 53 | int ret = epoll_ctl(ep->fd, f, fd, ev); 54 | /* Workaround for STDIN_FILENO: it returns EPERM but it is actually pollable */ 55 | if (ret == -1 && fd == STDIN_FILENO && errno == EPERM) { 56 | ret = 0; 57 | } 58 | 59 | /* Eventually free epoll data if needed */ 60 | if (flag == RM) { 61 | memhook._free(tmp->ev); 62 | tmp->ev = NULL; 63 | 64 | /* 65 | * Automatically close internally used FDs 66 | * for special internal fds 67 | */ 68 | if (tmp->type > M_SRC_TYPE_FD) { 69 | close(fd); 70 | /* 71 | * Reset to -1. Note that fd_src has same 72 | * memory space as other fds (inside union) 73 | */ 74 | tmp->fd_src.fd = -1; 75 | } 76 | } 77 | 78 | return ret; 79 | } 80 | 81 | int poll_init(poll_priv_t *priv) { 82 | GET_PRIV_DATA(); 83 | ep->pevents = memhook._calloc(priv->max_events, sizeof(struct epoll_event)); 84 | M_ALLOC_ASSERT(ep->pevents); 85 | return 0; 86 | } 87 | 88 | int poll_wait(poll_priv_t *priv, const int timeout) { 89 | GET_PRIV_DATA(); 90 | return epoll_wait(ep->fd, (struct epoll_event *) ep->pevents, priv->max_events, timeout); 91 | } 92 | 93 | ev_src_t *poll_recv(poll_priv_t *priv, const int idx) { 94 | GET_PRIV_DATA(); 95 | if (ep->pevents[idx].events & EPOLLERR) { 96 | return NULL; 97 | } 98 | return (ev_src_t *)ep->pevents[idx].data.ptr; 99 | } 100 | 101 | int poll_get_fd(const poll_priv_t *priv) { 102 | GET_PRIV_DATA(); 103 | return ep->fd; 104 | } 105 | 106 | int poll_clear(poll_priv_t *priv) { 107 | GET_PRIV_DATA(); 108 | memhook._free(ep->pevents); 109 | ep->pevents = NULL; 110 | return 0; 111 | } 112 | 113 | int poll_destroy(poll_priv_t *priv) { 114 | GET_PRIV_DATA(); 115 | poll_clear(priv); 116 | close(ep->fd); 117 | return 0; 118 | } 119 | -------------------------------------------------------------------------------- /Lib/core/poll/kqueue.c: -------------------------------------------------------------------------------- 1 | #include "poll.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | int fd; 9 | struct kevent *pevents; 10 | } kqueue_priv_t; 11 | 12 | #define GET_PRIV_DATA() kqueue_priv_t *kp = (kqueue_priv_t *)priv->data 13 | 14 | int poll_create(poll_priv_t *priv) { 15 | priv->data = memhook._calloc(1, sizeof(kqueue_priv_t)); 16 | M_ALLOC_ASSERT(priv->data); 17 | GET_PRIV_DATA(); 18 | kp->fd = kqueue(); 19 | fcntl(kp->fd, F_SETFD, FD_CLOEXEC); 20 | return 0; 21 | } 22 | 23 | int poll_set_new_evt(poll_priv_t *priv, ev_src_t *tmp, const enum op_type flag) { 24 | static int timer_ids = 1; 25 | GET_PRIV_DATA(); 26 | 27 | /* Eventually alloc kqueue data if needed */ 28 | if (!tmp->ev) { 29 | if (flag == ADD) { 30 | tmp->ev = memhook._calloc(1, sizeof(struct kevent)); 31 | M_ALLOC_ASSERT(tmp->ev); 32 | } else { 33 | /* We need to RM an unregistered ev. Fine. */ 34 | return 0; 35 | } 36 | } 37 | 38 | int f = flag == ADD ? EV_ADD : EV_DELETE; 39 | if (tmp->flags & M_SRC_ONESHOT) { 40 | f |= EV_ONESHOT; 41 | } 42 | struct kevent *_ev = (struct kevent *)tmp->ev; 43 | switch (tmp->type) { 44 | case M_SRC_TYPE_PS: // M_SRC_TYPE_PS is used for pubsub_fd[0] in init_pubsub_fd() 45 | case M_SRC_TYPE_FD: 46 | EV_SET(_ev, tmp->fd_src.fd, EVFILT_READ, f, 0, 0, tmp); 47 | break; 48 | case M_SRC_TYPE_TMR: { 49 | #if defined NOTE_ABSOLUTE // Apple (https://www.unix.com/man-page/osx/2/kqueue/) 50 | const int flags = tmp->flags & M_SRC_TMR_ABSOLUTE ? NOTE_ABSOLUTE : 0; 51 | #elif defined NOTE_ABSTIME // BSD (https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2) 52 | const int flags = tmp->flags & M_SRC_TMR_ABSOLUTE ? NOTE_ABSTIME : 0; 53 | #else 54 | const int flags = 0; // unsupported... 55 | #endif 56 | EV_SET(_ev, timer_ids++, EVFILT_TIMER, f, flags | NOTE_NSECONDS, tmp->tmr_src.its.ns, tmp); 57 | break; 58 | } 59 | case M_SRC_TYPE_SGN: 60 | EV_SET(_ev, tmp->sgn_src.sgs.signo, EVFILT_SIGNAL, f, 0, 0, tmp); 61 | break; 62 | case M_SRC_TYPE_PATH: 63 | tmp->path_src.f.fd = open(tmp->path_src.pt.path, O_RDONLY); 64 | if (tmp->path_src.f.fd != -1) { 65 | EV_SET(_ev, tmp->path_src.f.fd, EVFILT_VNODE, f, tmp->path_src.pt.events, 0, tmp); 66 | } else { 67 | return -EBADF; 68 | } 69 | break; 70 | case M_SRC_TYPE_PID: 71 | EV_SET(_ev, tmp->pid_src.pid.pid, EVFILT_PROC, f, tmp->pid_src.pid.events, 0, tmp); 72 | break; 73 | case M_SRC_TYPE_TASK: 74 | case M_SRC_TYPE_THRESH: 75 | EV_SET(_ev, (uintptr_t)tmp, EVFILT_USER, f, NOTE_FFNOP, 0, tmp); 76 | break; 77 | default: 78 | break; 79 | } 80 | 81 | int ret = kevent(kp->fd, _ev, 1, NULL, 0, NULL); 82 | /* Workaround for STDIN_FILENO: it is actually pollable */ 83 | if (tmp->type == M_SRC_TYPE_FD && tmp->fd_src.fd == STDIN_FILENO) { 84 | ret = 0; 85 | } 86 | 87 | /* Eventually free kqueue data if needed */ 88 | if (flag == RM) { 89 | memhook._free(tmp->ev); 90 | tmp->ev = NULL; 91 | 92 | if (tmp->type == M_SRC_TYPE_PATH) { 93 | close(tmp->path_src.f.fd); // automatically close internally used FDs 94 | tmp->path_src.f.fd = -1; 95 | } 96 | } 97 | 98 | return ret; 99 | } 100 | 101 | int poll_init(poll_priv_t *priv) { 102 | GET_PRIV_DATA(); 103 | kp->pevents = memhook._calloc(priv->max_events, sizeof(struct kevent)); 104 | M_ALLOC_ASSERT(kp->pevents); 105 | return 0; 106 | } 107 | 108 | int poll_wait(poll_priv_t *priv, const int timeout) { 109 | GET_PRIV_DATA(); 110 | struct timespec t = {0}; 111 | t.tv_sec = timeout; 112 | return kevent(kp->fd, NULL, 0, kp->pevents, priv->max_events, timeout >= 0 ? &t : NULL); 113 | } 114 | 115 | ev_src_t *poll_recv(poll_priv_t *priv, const int idx) { 116 | GET_PRIV_DATA(); 117 | if (kp->pevents[idx].flags & EV_ERROR) { 118 | return NULL; 119 | } 120 | return (ev_src_t *)kp->pevents[idx].udata; 121 | } 122 | 123 | int poll_consume_sgn(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_sgn_t *sgn_msg) { 124 | return 0; 125 | } 126 | 127 | int poll_consume_tmr(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_tmr_t *tm_msg) { 128 | return 0; 129 | } 130 | 131 | int poll_consume_pt(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_path_t *pt_msg) { 132 | GET_PRIV_DATA(); 133 | pt_msg->events = kp->pevents[idx].fflags; 134 | return 0; 135 | } 136 | 137 | int poll_consume_pid(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_pid_t *pid_msg) { 138 | GET_PRIV_DATA(); 139 | pid_msg->events = kp->pevents[idx].fflags; 140 | return 0; 141 | } 142 | 143 | int poll_consume_task(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_task_t *task_msg) { 144 | GET_PRIV_DATA(); 145 | task_msg->retval = src->task_src.retval; 146 | return 0; 147 | } 148 | 149 | int poll_consume_thresh(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_thresh_t *thresh_msg) { 150 | return 0; 151 | } 152 | 153 | int poll_get_fd(const poll_priv_t *priv) { 154 | GET_PRIV_DATA(); 155 | return kp->fd; 156 | } 157 | 158 | int poll_clear(poll_priv_t *priv) { 159 | GET_PRIV_DATA(); 160 | memhook._free(kp->pevents); 161 | kp->pevents = NULL; 162 | return 0; 163 | } 164 | 165 | int poll_destroy(poll_priv_t *priv) { 166 | GET_PRIV_DATA(); 167 | poll_clear(priv); 168 | close(kp->fd); 169 | return 0; 170 | } 171 | 172 | int poll_notify_userevent(poll_priv_t *priv, ev_src_t *src) { 173 | GET_PRIV_DATA(); 174 | struct kevent *_ev = (struct kevent *)src->ev; 175 | EV_SET(_ev, (uintptr_t)src, EVFILT_USER, 0, NOTE_FFNOP | NOTE_TRIGGER, 0, src); 176 | return kevent(kp->fd, _ev, 1, NULL, 0, NULL); 177 | } 178 | -------------------------------------------------------------------------------- /Lib/core/poll/poll.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "src.h" 4 | #include "ctx.h" 5 | #include 6 | 7 | /* Useful macros to smooth away differences between supported OS */ 8 | enum op_type { ADD, RM }; 9 | 10 | int poll_create(poll_priv_t *priv); 11 | int poll_set_new_evt(poll_priv_t *priv, ev_src_t *tmp, const enum op_type flag); 12 | int poll_init(poll_priv_t *priv); 13 | int poll_wait(poll_priv_t *priv, const int timeout); 14 | ev_src_t *poll_recv(poll_priv_t *priv, const int idx); 15 | int poll_get_fd(const poll_priv_t *priv); 16 | int poll_clear(poll_priv_t *priv); 17 | int poll_destroy(poll_priv_t *priv); 18 | 19 | int poll_consume_sgn(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_sgn_t *msg); 20 | int poll_consume_tmr(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_tmr_t *msg); 21 | int poll_consume_pt(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_path_t *pt_msg); 22 | int poll_consume_pid(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_pid_t *pid_msg); 23 | int poll_consume_task(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_task_t *task_msg); 24 | int poll_consume_thresh(poll_priv_t *priv, const int idx, ev_src_t *src, m_evt_thresh_t *thresh_msg); 25 | 26 | int poll_notify_userevent(poll_priv_t *priv, ev_src_t *src); 27 | -------------------------------------------------------------------------------- /Lib/core/poll/uring.c: -------------------------------------------------------------------------------- 1 | #include "poll.h" 2 | #include 3 | #include 4 | 5 | #warning "Uring support is unstable." 6 | 7 | /* 8 | * Doc: https://kernel.dk/io_uring.pdf 9 | * 10 | * Naming: 11 | * SQE -> Submission Queue Event 12 | * CQE -> Completion Queue Event 13 | */ 14 | 15 | extern void create_priv_fd(ev_src_t *tmp); 16 | 17 | typedef struct { 18 | struct io_uring ring; 19 | struct io_uring_cqe *cqe; 20 | } uring_priv_t; 21 | 22 | #define GET_PRIV_DATA() uring_priv_t *up = (uring_priv_t *)priv->data 23 | 24 | int poll_create(poll_priv_t *priv) { 25 | priv->data = memhook._calloc(1, sizeof(uring_priv_t)); 26 | M_ALLOC_ASSERT(priv->data); 27 | 28 | GET_PRIV_DATA(); 29 | /* 30 | * Initialize queue with M_CTX_DEFAULT_EVENTS because uring must be UP 31 | * before registering event sources; but we do not yet know 32 | * priv->max_events. 33 | * Anyway, it is always set to M_CTX_DEFAULT_EVENTS right now. 34 | */ 35 | return io_uring_queue_init(M_CTX_DEFAULT_EVENTS, &up->ring, IORING_SETUP_IOPOLL); 36 | } 37 | 38 | int poll_set_new_evt(poll_priv_t *priv, ev_src_t *tmp, const enum op_type flag) { 39 | GET_PRIV_DATA(); 40 | 41 | int ret = 0; 42 | /* Eventually request uring sqe if needed */ 43 | if (!tmp->ev) { 44 | if (flag == ADD) { 45 | tmp->ev = io_uring_get_sqe(&up->ring); 46 | M_ALLOC_ASSERT(tmp->ev); 47 | } else { 48 | /* We need to RM an unregistered ev. Fine. */ 49 | return 0; 50 | } 51 | } 52 | 53 | struct io_uring_sqe *sqe = (struct io_uring_sqe *)tmp->ev; 54 | if (flag == ADD && tmp->fd_src.fd == -1) { 55 | create_priv_fd(tmp); 56 | } 57 | 58 | /* 59 | * Note that fd_src shares 60 | * memory space with other fds (inside union) 61 | */ 62 | const int fd = tmp->fd_src.fd; 63 | if (fd != -1) { 64 | if (flag == ADD) { 65 | io_uring_prep_poll_add(sqe, fd, POLLIN); 66 | /* properly set userdata */ 67 | io_uring_sqe_set_data(sqe, tmp); 68 | } else { 69 | io_uring_prep_poll_remove(sqe, tmp); 70 | tmp->ev = NULL; 71 | /* 72 | * Automatically close internally used FDs 73 | * for special internal fds 74 | */ 75 | if (tmp->type > M_SRC_TYPE_FD) { 76 | close(fd); 77 | /* 78 | * Reset to -1. Note that fd_src has same 79 | * memory space as other fds (inside union) 80 | */ 81 | tmp->fd_src.fd = -1; 82 | } 83 | } 84 | } else { 85 | ret = -1; 86 | } 87 | return ret; 88 | } 89 | 90 | int poll_init(poll_priv_t *priv) { 91 | return 0; 92 | } 93 | 94 | int poll_wait(poll_priv_t *priv, const int timeout) { 95 | GET_PRIV_DATA(); 96 | 97 | io_uring_submit(&up->ring); 98 | struct __kernel_timespec t = {0}; 99 | t.tv_sec = timeout; 100 | int ret = io_uring_wait_cqe_timeout(&up->ring, &up->cqe, timeout >= 0 ? &t : NULL); 101 | if (ret == 0) { 102 | return 1; 103 | } 104 | return ret; // errno error code 105 | } 106 | 107 | ev_src_t *poll_recv(poll_priv_t *priv, const int idx) { 108 | GET_PRIV_DATA(); 109 | ev_src_t *udata = (ev_src_t *)io_uring_cqe_get_data(up->cqe); 110 | io_uring_cqe_seen(&up->ring, up->cqe); 111 | 112 | /* udata != NULL only if fd is still registered */ 113 | if (udata) { 114 | /* We need to re-arm it: IORING_OP_POLL_ADD interface works in oneshot mode */ 115 | udata->ev = NULL; 116 | poll_set_new_evt(priv, udata, ADD); 117 | } 118 | return udata; 119 | } 120 | 121 | int poll_get_fd(const poll_priv_t *priv) { 122 | GET_PRIV_DATA(); 123 | return up->ring.ring_fd; 124 | } 125 | 126 | int poll_clear(poll_priv_t *priv) { 127 | return 0; 128 | } 129 | 130 | int poll_destroy(poll_priv_t *priv) { 131 | GET_PRIV_DATA(); 132 | io_uring_queue_exit(&up->ring); 133 | return 0; 134 | } 135 | -------------------------------------------------------------------------------- /Lib/core/ps.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mod.h" 4 | 5 | #define M_PS_MOD_POISONPILL "LIBMODULE_MOD_POISONPILL" 6 | 7 | int tell_system_pubsub_msg(const m_mod_t *recipient, m_ctx_t *c, m_mod_t *sender, const char *topic); 8 | int flush_pubsub_msgs(void *data, const char *key, void *value); 9 | void call_pubsub_cb(m_mod_t *mod, m_queue_t *evts); 10 | -------------------------------------------------------------------------------- /Lib/core/public/module/.gitignore: -------------------------------------------------------------------------------- 1 | # Exclude compile time created files by cmake 2 | cmn.h 3 | ctx.h 4 | -------------------------------------------------------------------------------- /Lib/core/public/module/cmn.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if (!defined LIBMODULE_CORE_H) 4 | #error "Libmodule cmn.h should not be explicitly included." 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define LIBMODULE_VERSION_MAJ 6 13 | #define LIBMODULE_VERSION_MIN 0 14 | #define LIBMODULE_VERSION_PAT 0 15 | 16 | /* 17 | * Function parameters that actually are 18 | * output values, are marked by this macro. 19 | */ 20 | #define OUT 21 | 22 | /** Structs types **/ 23 | 24 | /* Incomplete structure declaration to mod handler */ 25 | typedef struct _mod m_mod_t; 26 | 27 | /* 28 | * Common to any context; set this in m_on_boot(), 29 | * ie: before library is inited and internal structures allocated 30 | */ 31 | int m_set_memhook( void *(*_malloc)(size_t), 32 | void *(*_calloc)(size_t, size_t), 33 | void (*_free)(void *)); 34 | -------------------------------------------------------------------------------- /Lib/core/public/module/ctx.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBMODULE_CORE_H 4 | #define LIBMODULE_CORE_H 5 | #endif 6 | #include 7 | 8 | /* Context flags */ 9 | typedef enum { 10 | M_CTX_NAME_DUP = 1 << 0, // Should ctx's name be strdupped? (only if a ctx is created during a register) 11 | M_CTX_NAME_AUTOFREE = 1 << 1, // Should ctx's name be autofreed? (only if a ctx is created during a register) 12 | M_CTX_PERSIST = 1 << 2, // Prevent ctx automatic destroying when there are no modules in it anymore. With this option, context is kept alive until m_ctx_deregister() is called. 13 | M_CTX_USERDATA_AUTOFREE = 1 << 3 // Automatically free ctx userdata upon deregister 14 | } m_ctx_flags; 15 | 16 | typedef struct { 17 | double activity_freq; 18 | uint64_t total_idle_time; 19 | uint64_t total_looping_time; 20 | uint64_t recv_msgs; 21 | size_t num_modules; 22 | size_t running_modules; 23 | } m_ctx_stats_t; 24 | 25 | /* Logger callback */ 26 | typedef void (*m_log_cb)(const m_mod_t *ref, const char *fmt, va_list args); 27 | 28 | /* Context interface functions */ 29 | int m_ctx_register(const char *ctx_name, m_ctx_flags flags, const void *userdata); 30 | int m_ctx_deregister(void); 31 | 32 | int m_ctx_set_logger( m_log_cb logger); 33 | int m_ctx_loop(void); 34 | int m_ctx_quit(uint8_t quit_code); 35 | 36 | int m_ctx_fd(void); 37 | 38 | int m_ctx_dispatch(void); 39 | 40 | int m_ctx_dump(void); 41 | int m_ctx_stats(OUT m_ctx_stats_t *stats); 42 | 43 | const char *m_ctx_name(void); 44 | const void *m_ctx_userdata(void); 45 | 46 | ssize_t m_ctx_len(void); 47 | 48 | int m_ctx_finalize(void); 49 | 50 | int m_ctx_set_tick(uint64_t ns); 51 | 52 | /* FuseFS api */ 53 | @M_CTX_HAS_FS@ 54 | 55 | #ifdef M_CTX_HAS_FS 56 | 57 | #include 58 | #include // ioctl support 59 | #define FUSE_USE_VERSION 35 60 | #include 61 | 62 | #define LIBMODULE_MAGIC 'm' // libmodule API global prefix 63 | 64 | enum { 65 | M_MOD_FS_STATE = _IOR(LIBMODULE_MAGIC, 0, m_mod_states), 66 | M_MOD_FS_STATS = _IOR(LIBMODULE_MAGIC, 1, m_mod_stats_t), 67 | }; 68 | 69 | int m_ctx_fs_set_root(const char *path); 70 | int m_ctx_fs_set_ops(const struct fuse_operations *ops); 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /Lib/core/public/module/mod_easy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define M_CTX_DEFAULT "libmodule" 8 | 9 | /* 10 | * ctors order: 11 | * 0) each m_mod_on_boot() (only mod_easy API) 12 | * 1) each m_mod_ctor() (only mod_easy API) 13 | */ 14 | #define _m_ctor0_ __attribute__((constructor (111))) 15 | #define _m_ctor1_ __attribute__((constructor (112))) 16 | 17 | /* Simple macro to automatically manage module lifecycle and callbacks */ 18 | #define M_MOD(name) \ 19 | static bool m_mod_on_start(m_mod_t *mod); \ 20 | static bool m_mod_on_eval(m_mod_t *mod); \ 21 | static void m_mod_on_evt(m_mod_t *mod, const m_queue_t *const evts); \ 22 | static void m_mod_on_stop(m_mod_t *mod); \ 23 | static void _m_ctor1_ m_m_ctor(void) { \ 24 | m_ctx_register(M_CTX_DEFAULT, 0, NULL); \ 25 | m_mod_hook_t hook = { m_mod_on_start, m_mod_on_eval, m_mod_on_evt, m_mod_on_stop }; \ 26 | m_mod_register(name, NULL , &hook, M_MOD_PERSIST, NULL); \ 27 | } \ 28 | static void _m_ctor0_ m_mod_on_boot(void) 29 | -------------------------------------------------------------------------------- /Lib/core/src.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mod.h" 4 | 5 | #define M_SRC_INTERNAL 1 << 7 6 | #define M_SRC_PRIO_MASK (M_SRC_PRIO_HIGH << 1) - 1 7 | #define M_SRC_ASSERT_PRIO_FLAGS() \ 8 | int prio_flags = flags & M_SRC_PRIO_MASK; \ 9 | M_PARAM_ASSERT(prio_flags == 0 || __builtin_popcount(prio_flags) == 1); \ 10 | if (prio_flags == 0) \ 11 | flags |= M_SRC_PRIO_NORM; 12 | 13 | /* Forward declare evt_priv_t to avoid dep cycle */ 14 | typedef struct _ev_priv evt_priv_t; 15 | 16 | /* Struct that holds fds to self_t mapping for poll plugin */ 17 | typedef struct { 18 | int fd; 19 | } fd_src_t; 20 | 21 | /* Struct that holds timers to self_t mapping for poll plugin */ 22 | typedef struct { 23 | #ifdef __linux__ 24 | fd_src_t f; 25 | #endif 26 | m_src_tmr_t its; 27 | } tmr_src_t; 28 | 29 | /* Struct that holds signals to self_t mapping for poll plugin */ 30 | typedef struct { 31 | #ifdef __linux__ 32 | fd_src_t f; 33 | #endif 34 | m_src_sgn_t sgs; 35 | } sgn_src_t; 36 | 37 | /* Struct that holds paths to self_t mapping for poll plugin */ 38 | typedef struct { 39 | fd_src_t f; // in kqueue EVFILT_VNODE: open(path) is needed. Thus a fd is needed too. 40 | m_src_path_t pt; 41 | } path_src_t; 42 | 43 | /* Struct that holds pids to self_t mapping for poll plugin */ 44 | typedef struct { 45 | #ifdef __linux__ 46 | fd_src_t f; 47 | #endif 48 | m_src_pid_t pid; 49 | } pid_src_t; 50 | 51 | /* Struct that holds task to self_t mapping for poll plugin */ 52 | typedef struct { 53 | #ifdef __linux__ 54 | fd_src_t f; 55 | #endif 56 | m_src_task_t tid; 57 | pthread_t th; 58 | int retval; 59 | } task_src_t; 60 | 61 | /* Struct that holds thresh to self_t mapping for poll plugin */ 62 | typedef struct { 63 | #ifdef __linux__ 64 | fd_src_t f; 65 | #endif 66 | m_src_thresh_t thr; 67 | m_src_thresh_t alarm; 68 | } thresh_src_t; 69 | 70 | /* Struct that holds pubsub subscriptions source data */ 71 | typedef struct { 72 | regex_t reg; 73 | const char *topic; 74 | } ps_src_t; 75 | 76 | typedef struct _ev_src *(*process_cb)(struct _ev_src *this, m_ctx_t *c, int idx, evt_priv_t *evt); 77 | 78 | /* Struct that holds generic "event source" data */ 79 | typedef struct _ev_src { 80 | union { 81 | ps_src_t ps_src; 82 | fd_src_t fd_src; 83 | tmr_src_t tmr_src; 84 | sgn_src_t sgn_src; 85 | path_src_t path_src; 86 | pid_src_t pid_src; 87 | task_src_t task_src; 88 | thresh_src_t thresh_src; 89 | }; 90 | m_src_types type; 91 | m_src_flags flags; 92 | void *ev; // poll plugin defined data structure 93 | m_mod_t *mod; // ptr needed to map an event source to a module in poll_plugin 94 | const void *userptr; 95 | process_cb process; // Processors can update the src, that's why they return an ev_src_t (PS only) 96 | } ev_src_t; 97 | 98 | /* Struct that holds pubsub messaging, private */ 99 | typedef struct { 100 | m_evt_ps_t msg; 101 | m_ps_flags flags; 102 | ev_src_t *sub; 103 | } ps_priv_t; 104 | 105 | extern const char *src_names[]; 106 | int init_src(m_mod_t *mod, m_src_types t); 107 | int register_mod_src(m_mod_t *mod, m_src_types type, const void *src_data, 108 | m_src_flags flags, const void *userptr); 109 | int deregister_mod_src(m_mod_t *mod, m_src_types type, void *src_data); 110 | ev_src_t *register_ctx_src(m_ctx_t *c, m_src_types type, process_cb proc, const void *src_data); 111 | int deregister_ctx_src(m_ctx_t *c, ev_src_t **src); 112 | int start_task(m_ctx_t *c, ev_src_t *src); 113 | -------------------------------------------------------------------------------- /Lib/mem/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | file(GLOB PUBLIC_H_mem Lib/mem/public/module/mem/*.h) 4 | file(GLOB SRCS_mem Lib/mem/*.c) 5 | 6 | add_library(${PROJECT_NAME}_mem ${LIBRARY_TYPE} ${SRCS_mem}) 7 | set_target_properties( 8 | ${PROJECT_NAME}_mem PROPERTIES 9 | VERSION ${PROJECT_VERSION} 10 | SOVERSION ${PROJECT_VERSION_MAJOR} 11 | PUBLIC_HEADER "${PUBLIC_H_mem}" 12 | C_VISIBILITY_PRESET hidden 13 | ) 14 | target_link_libraries(${PROJECT_NAME}_mem PRIVATE ${PROJECT_NAME}_utils_internal) 15 | target_include_directories(${PROJECT_NAME}_mem PRIVATE Lib/utils/ Lib/mem/) 16 | target_compile_definitions(${PROJECT_NAME}_mem PRIVATE LIBMODULE_LOG_CTX=MEM) 17 | 18 | fill_pc_vars(${PROJECT_NAME}_mem "Libmodule mem utilities library") 19 | configure_file(Extra/libmodule.pc.in libmodule_mem.pc @ONLY) 20 | 21 | install(TARGETS ${PROJECT_NAME}_mem 22 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 23 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 24 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/module/mem/ 25 | ) 26 | 27 | install(FILES ${CMAKE_BINARY_DIR}/libmodule_mem.pc 28 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) 29 | -------------------------------------------------------------------------------- /Lib/mem/mem.c: -------------------------------------------------------------------------------- 1 | #include "public/module/mem/mem.h" 2 | #include "log.h" 3 | #include "mem.h" 4 | #include 5 | #include 6 | #include 7 | 8 | #define ALIGN_UP(x) __ALIGN_MASK(x, (__typeof__(x))(alignof(max_align_t)) - 1) 9 | #define __ALIGN_MASK(x, mask) (((x) + (mask)) &~ (mask)) 10 | 11 | typedef struct { 12 | size_t refs; // Number of reference for this memory object 13 | size_t size; // size of user data, returned by m_mem_size() 14 | m_ref_dtor dtor; // Dtor for the memory object 15 | uint8_t data[]; // Flexible array member for user data 16 | } mem_header_t; 17 | 18 | static inline mem_header_t *get_header(uint8_t *src) { 19 | const uint8_t align_shift = src[-1]; 20 | return (mem_header_t *)(src - sizeof(mem_header_t) - align_shift); 21 | } 22 | 23 | /** Public API **/ 24 | 25 | /* Create new ref counted memory area */ 26 | _public_ void *m_mem_new(size_t size, m_ref_dtor dtor) { 27 | /* Always use maximum alignment for the platform */ 28 | const size_t total_size = sizeof(mem_header_t) + size; 29 | size_t total_size_aligned = ALIGN_UP(total_size); 30 | uint8_t align_shift = total_size_aligned - total_size; 31 | if (align_shift == 0) { 32 | /* Add a new aligned block; it is needed to later store alignment information */ 33 | align_shift = alignof(max_align_t); 34 | } 35 | mem_header_t *header = memhook._calloc(1, total_size + align_shift); 36 | if (header) { 37 | header->refs = 1; 38 | header->dtor = dtor; 39 | header->size = size; 40 | uint8_t *data = header->data + align_shift; 41 | /* Store alignment shift */ 42 | data[-1] = align_shift; 43 | return data; 44 | } 45 | return NULL; 46 | } 47 | 48 | /* Gain a new ref on a memory area */ 49 | _public_ void *m_mem_ref(void *src) { 50 | if (src) { 51 | mem_header_t *header = get_header(src); 52 | header->refs++; 53 | } 54 | return src; 55 | } 56 | 57 | /* Remove a ref from a memory area */ 58 | _public_ void *m_mem_unref(void *src) { 59 | if (src) { 60 | mem_header_t *header = get_header(src); 61 | if (--header->refs == 0) { 62 | if (header->dtor) { 63 | header->dtor(src); // destroy private data 64 | } 65 | memhook._free(header); 66 | } 67 | } 68 | return NULL; 69 | } 70 | 71 | _public_ void m_mem_unrefp(void **src) { 72 | if (src) { 73 | *src = m_mem_unref(*src); 74 | } 75 | } 76 | 77 | _public_ size_t m_mem_size(void *src) { 78 | if (src) { 79 | mem_header_t *header = get_header(src); 80 | return header->size; 81 | } 82 | return 0; 83 | } 84 | -------------------------------------------------------------------------------- /Lib/mem/public/module/mem/mem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef void (*m_ref_dtor)(void *); 6 | 7 | void *m_mem_new(size_t size, m_ref_dtor dtor); 8 | void *m_mem_ref(void *src); 9 | void *m_mem_unref(void *src); 10 | void m_mem_unrefp(void **src); 11 | size_t m_mem_size(void *src); 12 | -------------------------------------------------------------------------------- /Lib/structs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | file(GLOB PUBLIC_H_structs Lib/structs/public/module/structs/*.h) 4 | file(GLOB SRCS_structs Lib/structs/*.c) 5 | 6 | add_library(${PROJECT_NAME}_structs ${LIBRARY_TYPE} ${SRCS_structs}) 7 | set_target_properties( 8 | ${PROJECT_NAME}_structs PROPERTIES 9 | VERSION ${PROJECT_VERSION} 10 | SOVERSION ${PROJECT_VERSION_MAJOR} 11 | PUBLIC_HEADER "${PUBLIC_H_structs}" 12 | C_VISIBILITY_PRESET hidden 13 | ) 14 | target_link_libraries(${PROJECT_NAME}_structs PRIVATE ${PROJECT_NAME}_utils_internal) 15 | target_include_directories(${PROJECT_NAME}_structs PRIVATE Lib/utils/ Lib/structs/ Lib/structs/public/) 16 | target_compile_definitions(${PROJECT_NAME}_structs PRIVATE LIBMODULE_LOG_CTX=STRUCTS) 17 | 18 | fill_pc_vars(${PROJECT_NAME}_structs "Libmodule data structures library") 19 | configure_file(Extra/libmodule.pc.in libmodule_structs.pc @ONLY) 20 | 21 | install(TARGETS ${PROJECT_NAME}_structs 22 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 23 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 24 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/module/structs/ 25 | ) 26 | 27 | install(FILES ${CMAKE_BINARY_DIR}/libmodule_structs.pc 28 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) 29 | -------------------------------------------------------------------------------- /Lib/structs/README.md: -------------------------------------------------------------------------------- 1 | ## Data structures 2 | 3 | Libmodule makes use of these data structures internally. 4 | They are made available in public API located in . 5 | 6 | -------------------------------------------------------------------------------- /Lib/structs/list.c: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | #include "mem.h" 3 | #include "public/module/structs/list.h" 4 | 5 | typedef struct _elem { 6 | void *userptr; 7 | struct _elem *next; 8 | } list_node; 9 | 10 | struct _list { 11 | size_t len; 12 | m_list_cmp comp; 13 | m_list_dtor dtor; 14 | list_node *data; 15 | }; 16 | 17 | struct _list_itr { 18 | list_node **elem; 19 | m_list_t *l; 20 | ssize_t diff; 21 | }; 22 | 23 | static inline int insert_node(m_list_t *l, list_node **elem, void *data); 24 | static inline int remove_node(m_list_t *l, list_node **elem); 25 | 26 | static inline int insert_node(m_list_t *l, list_node **elem, void *data) { 27 | list_node *node = memhook._malloc(sizeof(list_node)); 28 | M_ALLOC_ASSERT(node); 29 | node->next = *elem; 30 | node->userptr = data; 31 | *elem = node; 32 | l->len++; 33 | return 0; 34 | } 35 | 36 | static inline int remove_node(m_list_t *l, list_node **elem) { 37 | list_node *tmp = *elem; 38 | if (tmp) { 39 | *elem = (*elem)->next; 40 | if (l->dtor) { 41 | l->dtor(tmp->userptr); 42 | } 43 | memhook._free(tmp); 44 | l->len--; 45 | return 0; 46 | } 47 | return -ENOENT; 48 | } 49 | 50 | /** Public API **/ 51 | 52 | _public_ m_list_t *m_list_new(m_list_cmp comp, m_list_dtor fn) { 53 | m_list_t *l = memhook._calloc(1, sizeof(m_list_t)); 54 | if (l) { 55 | l->comp = comp; 56 | l->dtor = fn; 57 | } 58 | return l; 59 | } 60 | 61 | _public_ m_list_itr_t *m_list_itr_new(const m_list_t *l) { 62 | M_RET_ASSERT(m_list_len(l) > 0, NULL); 63 | 64 | m_list_itr_t *itr = memhook._calloc(1, sizeof(m_list_itr_t)); 65 | if (itr) { 66 | itr->elem = (list_node **)&(l->data); 67 | itr->l = (m_list_t *)l; 68 | } 69 | return itr; 70 | } 71 | 72 | _public_ int m_list_itr_next(m_list_itr_t **itr) { 73 | M_PARAM_ASSERT(itr && *itr); 74 | 75 | m_list_itr_t *i = *itr; 76 | if (*i->elem) { 77 | if (i->diff >= 0) { 78 | i->elem = &((*i->elem)->next); 79 | } 80 | i->diff = 0; 81 | } 82 | if (!*(i->elem)) { 83 | memhook._free(*itr); 84 | *itr = NULL; 85 | } 86 | return 0; 87 | } 88 | 89 | _public_ void *m_list_itr_get_data(const m_list_itr_t *itr) { 90 | M_RET_ASSERT(itr, NULL); 91 | M_RET_ASSERT(*itr->elem, NULL); 92 | 93 | return (*itr->elem)->userptr; 94 | } 95 | 96 | _public_ int m_list_itr_set_data(m_list_itr_t *itr, void *value) { 97 | M_PARAM_ASSERT(itr); 98 | M_PARAM_ASSERT(value); 99 | M_RET_ASSERT(*itr->elem, -EINVAL); 100 | 101 | (*itr->elem)->userptr = value; 102 | return 0; 103 | } 104 | 105 | _public_ int m_list_itr_insert(m_list_itr_t *itr, void *value) { 106 | M_PARAM_ASSERT(itr); 107 | M_PARAM_ASSERT(value); 108 | 109 | itr->diff++; 110 | return insert_node(itr->l, itr->elem, value); 111 | } 112 | 113 | _public_ int m_list_itr_remove(m_list_itr_t *itr) { 114 | M_PARAM_ASSERT(itr); 115 | M_RET_ASSERT(*itr->elem, -EINVAL); 116 | 117 | itr->diff--; // notify list to avoid skipping 1 element on next list_itr_next() call 118 | return remove_node(itr->l, itr->elem); 119 | } 120 | 121 | _public_ int m_list_iterate(const m_list_t *l, m_list_cb fn, void *userptr) { 122 | M_PARAM_ASSERT(fn); 123 | M_PARAM_ASSERT(m_list_len(l) > 0); 124 | 125 | list_node *elem = l->data; 126 | while (elem) { 127 | int rc = fn(userptr, elem->userptr); 128 | if (rc < 0) { 129 | /* Stop right now with error */ 130 | return rc; 131 | } 132 | if (rc > 0) { 133 | /* Stop right now with 0 */ 134 | return 0; 135 | } 136 | elem = elem->next; 137 | } 138 | return 0; 139 | } 140 | 141 | _public_ int m_list_insert(m_list_t *l, void *data) { 142 | M_PARAM_ASSERT(l); 143 | M_PARAM_ASSERT(data); 144 | 145 | list_node **tmp = &l->data; 146 | for (int i = 0; i < l->len && l->comp; i++) { 147 | if (l->comp(data, (*tmp)->userptr) == 0) { 148 | break; 149 | } 150 | tmp = &(*tmp)->next; 151 | } 152 | 153 | return insert_node(l, tmp, data); 154 | } 155 | 156 | _public_ int m_list_remove(m_list_t *l, void *data) { 157 | M_PARAM_ASSERT(m_list_len(l) > 0); 158 | M_PARAM_ASSERT(data); 159 | 160 | list_node **tmp = &l->data; 161 | for (int i = 0; i < l->len; i++) { 162 | if ((l->comp && l->comp(data, (*tmp)->userptr) == 0) || (*tmp)->userptr == data) { 163 | break; 164 | } 165 | tmp = &(*tmp)->next; 166 | } 167 | return remove_node(l, tmp); 168 | } 169 | 170 | _public_ void *m_list_find(m_list_t *l, void *data) { 171 | M_RET_ASSERT(l, NULL); 172 | M_RET_ASSERT(data, NULL); 173 | 174 | list_node **tmp = &l->data; 175 | for (int i = 0; i < l->len; i++) { 176 | if ((l->comp && l->comp(data, (*tmp)->userptr) == 0) || (*tmp)->userptr == data) { 177 | return (*tmp)->userptr; 178 | } 179 | tmp = &(*tmp)->next; 180 | } 181 | return NULL; 182 | } 183 | 184 | _public_ int m_list_clear(m_list_t *l) { 185 | M_PARAM_ASSERT(l); 186 | 187 | for (m_list_itr_t *itr = m_list_itr_new(l); itr; m_list_itr_next(&itr)) { 188 | m_list_itr_remove(itr); 189 | } 190 | return 0; 191 | } 192 | 193 | _public_ int m_list_free(m_list_t **l) { 194 | M_PARAM_ASSERT(l); 195 | 196 | int ret = m_list_clear(*l); 197 | if (ret == 0) { 198 | memhook._free(*l); 199 | *l = NULL; 200 | } 201 | return ret; 202 | } 203 | 204 | _public_ ssize_t m_list_len(const m_list_t *l) { 205 | M_PARAM_ASSERT(l); 206 | 207 | return l->len; 208 | } 209 | -------------------------------------------------------------------------------- /Lib/structs/public/module/structs/bst.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** Linked-List interface **/ 4 | 5 | /* Callback for tree_iterate; first parameter is userdata, second is tree data */ 6 | typedef int (*m_bst_cb)(void *, void *); 7 | 8 | /* Fn for tree_set_dtor */ 9 | typedef void (*m_bst_dtor)(void *); 10 | 11 | /* Callback for tree compare; first parameter is userdata, second is tree data */ 12 | typedef int (*m_bst_cmp)(void *, void *); 13 | 14 | /* Incomplete struct declaration for tree */ 15 | typedef struct _bst m_bst_t; 16 | 17 | /* Incomplete struct declaration for tree iterator */ 18 | typedef struct _bst_itr m_bst_itr_t; 19 | 20 | typedef enum { 21 | M_BST_PRE, 22 | M_BST_POST, 23 | M_BST_IN 24 | } m_bst_order; 25 | 26 | m_bst_t *m_bst_new(m_bst_cmp comp, m_bst_dtor fn); 27 | int m_bst_insert(m_bst_t *l, void *data); 28 | int m_bst_remove(m_bst_t *l, void *data); 29 | void *m_bst_find(m_bst_t *l, void *data); 30 | int m_bst_iterate(const m_bst_t *l, m_bst_cb cb, void *userptr); 31 | int m_bst_traverse(const m_bst_t *l, m_bst_order type, m_bst_cb cb, void *userptr); 32 | m_bst_itr_t *m_bst_itr_new(const m_bst_t *l); 33 | int m_bst_itr_next(m_bst_itr_t **itr); 34 | int m_bst_itr_remove(m_bst_itr_t *itr); 35 | void *m_bst_itr_get_data(const m_bst_itr_t *itr); 36 | int m_bst_clear(m_bst_t *l); 37 | int m_bst_free(m_bst_t **l); 38 | ssize_t m_bst_len(const m_bst_t *l); 39 | 40 | 41 | -------------------------------------------------------------------------------- /Lib/structs/public/module/structs/itr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Iterator interface to easily iterate over data structs APIs. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define m_itr_new(X) _Generic((X), \ 14 | m_map_t *: m_map_itr_new, \ 15 | const m_map_t *: m_map_itr_new, \ 16 | m_list_t *: m_list_itr_new, \ 17 | const m_list_t *: m_list_itr_new, \ 18 | m_stack_t *: m_stack_itr_new, \ 19 | const m_stack_t *: m_stack_itr_new, \ 20 | m_queue_t *: m_queue_itr_new, \ 21 | const m_queue_t *: m_queue_itr_new, \ 22 | m_bst_t *: m_bst_itr_new, \ 23 | const m_bst_t *: m_bst_itr_new \ 24 | )(X) 25 | 26 | #define m_itr_next(X) _Generic((X), \ 27 | m_map_itr_t **: m_map_itr_next, \ 28 | m_list_itr_t **: m_list_itr_next, \ 29 | m_stack_itr_t **: m_stack_itr_next, \ 30 | m_queue_itr_t **: m_queue_itr_next, \ 31 | m_bst_itr_t **: m_bst_itr_next \ 32 | )(X) 33 | 34 | #define m_itr_get(X) _Generic((X), \ 35 | m_map_itr_t *: m_map_itr_get_data, \ 36 | m_list_itr_t *: m_list_itr_get_data, \ 37 | m_stack_itr_t *: m_stack_itr_get_data, \ 38 | m_queue_itr_t *: m_queue_itr_get_data, \ 39 | m_bst_itr_t *: m_bst_itr_get_data \ 40 | )(X) 41 | 42 | /* Unavailable for bst API */ 43 | #define m_itr_set(X, data) _Generic((X), \ 44 | m_map_itr_t *: m_map_itr_set_data, \ 45 | m_list_itr_t *: m_list_itr_set_data, \ 46 | m_stack_itr_t *: m_stack_itr_set_data, \ 47 | m_queue_itr_t *: m_queue_itr_set_data \ 48 | )(X, data) 49 | 50 | #define m_itr_rm(X) _Generic((X), \ 51 | m_map_itr_t *: m_map_itr_remove, \ 52 | m_list_itr_t *: m_list_itr_remove, \ 53 | m_stack_itr_t *: m_stack_itr_remove, \ 54 | m_queue_itr_t *: m_queue_itr_remove, \ 55 | m_bst_itr_t *: m_bst_itr_remove \ 56 | )(X) 57 | 58 | #define m_itr_foreach(X, fn) { \ 59 | size_t m_idx = 0; \ 60 | for (__auto_type m_itr = m_itr_new(X); m_itr; m_itr_next(&m_itr), m_idx++) fn; \ 61 | } 62 | 63 | #define m_iterate(X, cb, up) _Generic((X), \ 64 | m_map_t *: m_map_iterate, \ 65 | const m_map_t *: m_map_iterate, \ 66 | m_list_itr_t *: m_list_iterate, \ 67 | const m_list_itr_t *: m_list_iterate, \ 68 | m_stack_itr_t *: m_stack_iterate, \ 69 | const m_stack_itr_t *: m_stack_iterate, \ 70 | m_queue_itr_t *: m_queue_iterate, \ 71 | const m_queue_itr_t *: m_queue_iterate, \ 72 | m_bst_itr_t *: m_bst_iterate, \ 73 | const m_bst_itr_t *: m_bst_iterate \ 74 | )(X, cb, up) 75 | -------------------------------------------------------------------------------- /Lib/structs/public/module/structs/list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** Linked-List interface **/ 4 | 5 | /* Callback for list_iterate; first parameter is userdata, second is list data */ 6 | typedef int (*m_list_cb)(void *, void *); 7 | 8 | /* Fn for list_set_dtor */ 9 | typedef void (*m_list_dtor)(void *); 10 | 11 | /* Callback for list compare; first parameter is userdata, second is list data */ 12 | typedef int (*m_list_cmp)(void *, void *); 13 | 14 | /* Incomplete struct declaration for list */ 15 | typedef struct _list m_list_t; 16 | 17 | /* Incomplete struct declaration for list iterator */ 18 | typedef struct _list_itr m_list_itr_t; 19 | 20 | m_list_t *m_list_new(m_list_cmp comp, m_list_dtor fn); 21 | m_list_itr_t *m_list_itr_new(const m_list_t *l); 22 | int m_list_itr_next(m_list_itr_t **itr); 23 | void *m_list_itr_get_data(const m_list_itr_t *itr); 24 | int m_list_itr_set_data(m_list_itr_t *itr, void *value); 25 | int m_list_itr_insert(m_list_itr_t *itr, void *value); 26 | int m_list_itr_remove(m_list_itr_t *itr); 27 | int m_list_iterate(const m_list_t *l, m_list_cb fn, void *userptr); 28 | int m_list_insert(m_list_t *l, void *data); 29 | int m_list_remove(m_list_t *l, void *data); 30 | void *m_list_find(m_list_t *l, void *data); 31 | int m_list_clear(m_list_t *l); 32 | int m_list_free(m_list_t **l); 33 | ssize_t m_list_len(const m_list_t *l); 34 | 35 | -------------------------------------------------------------------------------- /Lib/structs/public/module/structs/map.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** Hashmap interface **/ 6 | 7 | /* Callback for map_iterate; first parameter is userdata, second and third are {key,value} tuple */ 8 | typedef int (*m_map_cb)(void *, const char *, void *); 9 | 10 | /* Fn for map_set_dtor */ 11 | typedef void (*m_map_dtor)(void *); 12 | 13 | /* Incomplete struct declaration for hashmap */ 14 | typedef struct _map m_map_t; 15 | 16 | /* Incomplete struct declaration for hashmap iterator */ 17 | typedef struct _map_itr m_map_itr_t; 18 | 19 | /* 20 | * 8 bits for key related flags 21 | * 8 bits for value related flags 22 | */ 23 | typedef enum { 24 | M_MAP_KEY_DUP = 1 << 0, // Should map keys be dupped? 25 | M_MAP_KEY_AUTOFREE = 1 << 1, // Should map keys be freed automatically? 26 | M_MAP_VAL_ALLOW_UPDATE = 1 << 8 // Does map object allow for updating values? 27 | } m_map_flags; 28 | 29 | 30 | m_map_t *m_map_new(m_map_flags flags, m_map_dtor fn); 31 | m_map_itr_t *m_map_itr_new(const m_map_t *m); 32 | int m_map_itr_next(m_map_itr_t **itr); 33 | int m_map_itr_remove(m_map_itr_t *itr); 34 | const char *m_map_itr_get_key(const m_map_itr_t *itr); 35 | void *m_map_itr_get_data(const m_map_itr_t *itr); 36 | int m_map_itr_set_data(const m_map_itr_t *itr, void *value); 37 | int m_map_iterate(const m_map_t *m, m_map_cb fn, void *userptr); 38 | int m_map_put(m_map_t *m, const char *key, void *value); 39 | void *m_map_get(const m_map_t *m, const char *key); 40 | bool m_map_contains(const m_map_t *m, const char *key); 41 | int m_map_remove(m_map_t *m, const char *key); 42 | int m_map_clear(m_map_t *m); 43 | int m_map_free(m_map_t **m); 44 | ssize_t m_map_len(const m_map_t *m); 45 | -------------------------------------------------------------------------------- /Lib/structs/public/module/structs/queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** Queue interface **/ 4 | 5 | /* Callback for queue_iterate */ 6 | typedef int (*m_queue_cb)(void *, void *); 7 | 8 | /* Fn for queue_set_dtor */ 9 | typedef void (*m_queue_dtor)(void *); 10 | 11 | /* Incomplete struct declaration for queue */ 12 | typedef struct _queue m_queue_t; 13 | 14 | /* Incomplete struct declaration for queue iterator */ 15 | typedef struct _queue_itr m_queue_itr_t; 16 | 17 | m_queue_t *m_queue_new(m_queue_dtor fn); 18 | m_queue_itr_t *m_queue_itr_new(const m_queue_t *q); 19 | int m_queue_itr_next(m_queue_itr_t **itr); 20 | int m_queue_itr_remove(m_queue_itr_t *itr); 21 | void *m_queue_itr_get_data(const m_queue_itr_t *itr); 22 | int m_queue_itr_set_data(const m_queue_itr_t *itr, void *value); 23 | int m_queue_iterate(const m_queue_t *q, m_queue_cb fn, void *userptr); 24 | int m_queue_enqueue(m_queue_t *q, void *data); 25 | void *m_queue_dequeue(m_queue_t *q); 26 | void *m_queue_peek(const m_queue_t *q); 27 | int m_queue_remove(m_queue_t *q); 28 | int m_queue_clear(m_queue_t *q); 29 | int m_queue_free(m_queue_t **q); 30 | ssize_t m_queue_len(const m_queue_t *q); 31 | -------------------------------------------------------------------------------- /Lib/structs/public/module/structs/stack.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** Stack interface **/ 4 | 5 | /* Callback for stack_iterate */ 6 | typedef int (*m_stack_cb)(void *, void *); 7 | 8 | /* Fn for stack_set_dtor */ 9 | typedef void (*m_stack_dtor)(void *); 10 | 11 | /* Incomplete struct declaration for stack */ 12 | typedef struct _stack m_stack_t; 13 | 14 | /* Incomplete struct declaration for stack iterator */ 15 | typedef struct _stack_itr m_stack_itr_t; 16 | 17 | m_stack_t *m_stack_new(m_stack_dtor fn); 18 | m_stack_itr_t *m_stack_itr_new(const m_stack_t *s); 19 | int m_stack_itr_next(m_stack_itr_t **itr); 20 | int m_stack_itr_remove(m_stack_itr_t *itr); 21 | void *m_stack_itr_get_data(const m_stack_itr_t *itr); 22 | int m_stack_itr_set_data(const m_stack_itr_t *itr, void *value); 23 | int m_stack_iterate(const m_stack_t *s, m_stack_cb fn, void *userptr); 24 | int m_stack_push(m_stack_t *s, void *data); 25 | void *m_stack_pop(m_stack_t *s); 26 | void *m_stack_peek(const m_stack_t *s); 27 | int m_stack_clear(m_stack_t *s); 28 | int m_stack_remove(m_stack_t *s); 29 | int m_stack_free(m_stack_t **s); 30 | ssize_t m_stack_len(const m_stack_t *s); 31 | -------------------------------------------------------------------------------- /Lib/structs/queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "log.h" 3 | #include "mem.h" 4 | #include "public/module/structs/queue.h" 5 | 6 | typedef struct _elem { 7 | void *userptr; 8 | struct _elem *prev; 9 | } queue_elem; 10 | 11 | struct _queue { 12 | size_t len; 13 | m_queue_dtor dtor; 14 | queue_elem *head; 15 | queue_elem *tail; 16 | }; 17 | 18 | struct _queue_itr { 19 | m_queue_t *q; 20 | queue_elem **elem; 21 | bool removed; 22 | }; 23 | 24 | /** Public API **/ 25 | 26 | _public_ m_queue_t *m_queue_new(m_queue_dtor fn) { 27 | m_queue_t *q = memhook._calloc(1, sizeof(m_queue_t)); 28 | if (q) { 29 | q->dtor = fn; 30 | } 31 | return q; 32 | } 33 | 34 | _public_ m_queue_itr_t *m_queue_itr_new(const m_queue_t *q) { 35 | M_RET_ASSERT(m_queue_len(q) > 0, NULL); 36 | 37 | m_queue_itr_t *itr = memhook._calloc(1, sizeof(m_queue_itr_t)); 38 | if (itr) { 39 | itr->elem = (queue_elem **)&q->head; 40 | itr->q = (m_queue_t *)q; 41 | } 42 | return itr; 43 | } 44 | 45 | _public_ int m_queue_itr_next(m_queue_itr_t **itr) { 46 | M_PARAM_ASSERT(itr && *itr); 47 | 48 | m_queue_itr_t *i = *itr; 49 | if (!i->removed) { 50 | i->elem = &(*i->elem)->prev; 51 | } else { 52 | i->removed = false; 53 | } 54 | if (!*i->elem) { 55 | memhook._free(*itr); 56 | *itr = NULL; 57 | } 58 | return 0; 59 | } 60 | 61 | _public_ int m_queue_itr_remove(m_queue_itr_t *itr) { 62 | M_PARAM_ASSERT(itr && !itr->removed); 63 | 64 | queue_elem *tmp = *itr->elem; 65 | if (tmp) { 66 | *itr->elem = (*itr->elem)->prev; 67 | if (itr->q->dtor) { 68 | itr->q->dtor(tmp->userptr); 69 | } 70 | if (tmp == itr->q->tail) { 71 | itr->q->tail = NULL; 72 | } 73 | memhook._free(tmp); 74 | itr->q->len--; 75 | itr->removed = true; 76 | return 0; 77 | } 78 | return -ENOENT; 79 | } 80 | 81 | _public_ void *m_queue_itr_get_data(const m_queue_itr_t *itr) { 82 | M_RET_ASSERT(itr && !itr->removed, NULL); 83 | 84 | return (*itr->elem)->userptr; 85 | } 86 | 87 | _public_ int m_queue_itr_set_data(const m_queue_itr_t *itr, void *value) { 88 | M_PARAM_ASSERT(itr && !itr->removed); 89 | M_PARAM_ASSERT(value); 90 | 91 | (*itr->elem)->userptr = value; 92 | return 0; 93 | } 94 | 95 | _public_ int m_queue_iterate(const m_queue_t *q, m_queue_cb fn, void *userptr) { 96 | M_PARAM_ASSERT(fn); 97 | M_PARAM_ASSERT(m_queue_len(q) > 0); 98 | 99 | queue_elem *elem = q->head; 100 | while (elem) { 101 | int rc = fn(userptr, elem->userptr); 102 | if (rc < 0) { 103 | /* Stop right now with error */ 104 | return rc; 105 | } 106 | if (rc > 0) { 107 | /* Stop right now with 0 */ 108 | return 0; 109 | } 110 | elem = elem->prev; 111 | } 112 | return 0; 113 | } 114 | 115 | _public_ int m_queue_enqueue(m_queue_t *q, void *data) { 116 | M_PARAM_ASSERT(q); 117 | M_PARAM_ASSERT(data); 118 | 119 | queue_elem *elem = memhook._calloc(1, sizeof(queue_elem)); 120 | M_ALLOC_ASSERT(elem); 121 | q->len++; 122 | elem->userptr = data; 123 | if (q->tail) { 124 | q->tail->prev = elem; 125 | } 126 | q->tail = elem; 127 | if (!q->head) { 128 | q->head = q->tail; 129 | } 130 | return 0; 131 | } 132 | 133 | _public_ void *m_queue_dequeue(m_queue_t *q) { 134 | M_RET_ASSERT(m_queue_len(q) > 0, NULL); 135 | 136 | queue_elem *elem = q->head; 137 | if (q->tail == q->head) { 138 | q->tail = NULL; 139 | } 140 | q->head = q->head->prev; 141 | 142 | void *data = elem->userptr; 143 | memhook._free(elem); 144 | q->len--; 145 | return data; 146 | } 147 | 148 | _public_ void *m_queue_peek(const m_queue_t *q) { 149 | M_RET_ASSERT(m_queue_len(q) > 0, NULL); 150 | 151 | return q->head->userptr; 152 | } 153 | 154 | _public_ int m_queue_remove(m_queue_t *q) { 155 | void *data = m_queue_dequeue(q); 156 | if (data) { 157 | if (q->dtor) { 158 | q->dtor(data); 159 | } 160 | return 0; 161 | } 162 | return -EINVAL; 163 | } 164 | 165 | _public_ int m_queue_clear(m_queue_t *q) { 166 | M_PARAM_ASSERT(m_queue_len(q) > 0); 167 | 168 | while (q->len > 0) { 169 | m_queue_remove(q); 170 | } 171 | return 0; 172 | } 173 | 174 | _public_ int m_queue_free(m_queue_t **q) { 175 | M_PARAM_ASSERT(q); 176 | 177 | m_queue_clear(*q); 178 | memhook._free(*q); 179 | *q = NULL; 180 | return 0; 181 | } 182 | 183 | _public_ ssize_t m_queue_len(const m_queue_t *q) { 184 | M_PARAM_ASSERT(q); 185 | 186 | return q->len; 187 | } 188 | 189 | -------------------------------------------------------------------------------- /Lib/structs/stack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "log.h" 3 | #include "mem.h" 4 | #include "public/module/structs/stack.h" 5 | 6 | typedef struct _elem { 7 | void *userptr; 8 | struct _elem *prev; 9 | } stack_elem; 10 | 11 | struct _stack { 12 | size_t len; 13 | m_stack_dtor dtor; 14 | stack_elem *data; 15 | }; 16 | 17 | struct _stack_itr { 18 | m_stack_t *s; 19 | stack_elem **elem; 20 | bool removed; 21 | }; 22 | 23 | /** Public API **/ 24 | 25 | _public_ m_stack_t *m_stack_new(m_stack_dtor fn) { 26 | m_stack_t *s = memhook._calloc(1, sizeof(m_stack_t)); 27 | if (s) { 28 | s->dtor = fn; 29 | } 30 | return s; 31 | } 32 | 33 | _public_ m_stack_itr_t *m_stack_itr_new(const m_stack_t *s) { 34 | M_RET_ASSERT(m_stack_len(s) > 0, NULL); 35 | 36 | m_stack_itr_t *itr = memhook._calloc(1, sizeof(m_stack_itr_t)); 37 | if (itr) { 38 | itr->s = (m_stack_t *)s; 39 | itr->elem = (stack_elem **)&s->data; 40 | } 41 | return itr; 42 | } 43 | 44 | _public_ int m_stack_itr_next(m_stack_itr_t **itr) { 45 | M_PARAM_ASSERT(itr && *itr); 46 | 47 | m_stack_itr_t *i = *itr; 48 | if (!i->removed) { 49 | i->elem = &(*i->elem)->prev; 50 | } else { 51 | i->removed = false; 52 | } 53 | if (!*i->elem) { 54 | memhook._free(*itr); 55 | *itr = NULL; 56 | } 57 | return 0; 58 | } 59 | 60 | _public_ int m_stack_itr_remove(m_stack_itr_t *itr) { 61 | M_PARAM_ASSERT(itr && !itr->removed); 62 | 63 | stack_elem *tmp = *itr->elem; 64 | if (tmp) { 65 | *itr->elem = (*itr->elem)->prev; 66 | if (itr->s->dtor) { 67 | itr->s->dtor(tmp->userptr); 68 | } 69 | memhook._free(tmp); 70 | itr->s->len--; 71 | itr->removed = true; 72 | return 0; 73 | } 74 | return -ENOENT; 75 | } 76 | 77 | _public_ void *m_stack_itr_get_data(const m_stack_itr_t *itr) { 78 | M_RET_ASSERT(itr && !itr->removed, NULL); 79 | 80 | return (*itr->elem)->userptr; 81 | } 82 | 83 | _public_ int m_stack_itr_set_data(const m_stack_itr_t *itr, void *value) { 84 | M_PARAM_ASSERT(itr && !itr->removed); 85 | M_PARAM_ASSERT(value); 86 | 87 | (*itr->elem)->userptr = value; 88 | return 0; 89 | } 90 | 91 | _public_ int m_stack_iterate(const m_stack_t *s, m_stack_cb fn, void *userptr) { 92 | M_PARAM_ASSERT(fn); 93 | M_PARAM_ASSERT(m_stack_len(s) > 0); 94 | 95 | stack_elem *elem = s->data; 96 | while (elem) { 97 | int rc = fn(userptr, elem->userptr); 98 | if (rc < 0) { 99 | /* Stop right now with error */ 100 | return rc; 101 | } 102 | if (rc > 0) { 103 | /* Stop right now with 0 */ 104 | return 0; 105 | } 106 | elem = elem->prev; 107 | } 108 | return 0; 109 | } 110 | 111 | _public_ int m_stack_push(m_stack_t *s, void *data) { 112 | M_PARAM_ASSERT(s); 113 | M_PARAM_ASSERT(data); 114 | 115 | stack_elem *elem = memhook._malloc(sizeof(stack_elem)); 116 | M_ALLOC_ASSERT(elem); 117 | s->len++; 118 | elem->userptr = data; 119 | elem->prev = s->data; 120 | s->data = elem; 121 | return 0; 122 | } 123 | 124 | _public_ void *m_stack_pop(m_stack_t *s) { 125 | M_RET_ASSERT(m_stack_len(s) > 0, NULL); 126 | 127 | stack_elem *elem = s->data; 128 | s->data = s->data->prev; 129 | void *data = elem->userptr; 130 | memhook._free(elem); 131 | s->len--; 132 | return data; 133 | } 134 | 135 | _public_ void *m_stack_peek(const m_stack_t *s) { 136 | M_RET_ASSERT(m_stack_len(s) > 0, NULL); 137 | 138 | return s->data->userptr; // return most recent element data 139 | } 140 | 141 | _public_ int m_stack_clear(m_stack_t *s) { 142 | M_PARAM_ASSERT(s); 143 | 144 | while (s->len > 0) { 145 | m_stack_remove(s); 146 | } 147 | return 0; 148 | } 149 | 150 | _public_ int m_stack_remove(m_stack_t *s) { 151 | void *data = m_stack_pop(s); 152 | if (data) { 153 | if (s->dtor) { 154 | s->dtor(data); 155 | } 156 | return 0; 157 | } 158 | return -EINVAL; 159 | } 160 | 161 | _public_ int m_stack_free(m_stack_t **s) { 162 | M_PARAM_ASSERT(s); 163 | 164 | int ret = m_stack_clear(*s); 165 | if (ret == 0) { 166 | memhook._free(*s); 167 | *s = NULL; 168 | } 169 | return ret; 170 | } 171 | 172 | _public_ ssize_t m_stack_len(const m_stack_t *s) { 173 | M_PARAM_ASSERT(s); 174 | 175 | return s->len; 176 | } 177 | -------------------------------------------------------------------------------- /Lib/thpool/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | set(CMAKE_THREAD_PREFER_PTHREAD TRUE) 4 | set(THREADS_PREFER_PTHREAD_FLAG TRUE) 5 | find_package(Threads REQUIRED) 6 | 7 | file(GLOB PUBLIC_H_thpool Lib/thpool/public/module/thpool/*.h) 8 | file(GLOB SRCS_thpool Lib/thpool/*.c) 9 | 10 | add_library(${PROJECT_NAME}_thpool ${LIBRARY_TYPE} ${SRCS_thpool}) 11 | set_target_properties( 12 | ${PROJECT_NAME}_thpool PROPERTIES 13 | VERSION ${PROJECT_VERSION} 14 | SOVERSION ${PROJECT_VERSION_MAJOR} 15 | PUBLIC_HEADER "${PUBLIC_H_thpool}" 16 | C_VISIBILITY_PRESET hidden 17 | ) 18 | target_link_libraries(${PROJECT_NAME}_thpool PUBLIC ${CMAKE_THREAD_LIBS_INIT} ${PROJECT_NAME}_structs) 19 | target_link_libraries(${PROJECT_NAME}_thpool PRIVATE ${PROJECT_NAME}_utils_internal) 20 | target_include_directories(${PROJECT_NAME}_thpool PRIVATE Lib/utils/ Lib/structs/ Lib/structs/public/ Lib/thpool/) 21 | target_compile_definitions(${PROJECT_NAME}_thpool PRIVATE LIBMODULE_LOG_CTX=THPOOL) 22 | 23 | fill_pc_vars(${PROJECT_NAME}_thpool "Libmodule thread pool library") 24 | # avoid "-l-pthread" string 25 | string(REPLACE "-l-pthread" "-pthread" PKG_DEPS ${PKG_DEPS}) 26 | configure_file(Extra/libmodule.pc.in libmodule_thpool.pc @ONLY) 27 | 28 | install(TARGETS ${PROJECT_NAME}_thpool 29 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 30 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 31 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/module/thpool/ 32 | ) 33 | 34 | install(FILES ${CMAKE_BINARY_DIR}/libmodule_thpool.pc 35 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) 36 | -------------------------------------------------------------------------------- /Lib/thpool/public/module/thpool/thpool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef void *(*m_thpool_task)(void *); 10 | 11 | typedef struct _thpool m_thpool_t; 12 | 13 | typedef enum { 14 | M_THPOOL_LAZY = 1 << 0, // lazy creation of threads 15 | M_THPOOL_DETACHED = 1 << 1, // create threads detached 16 | } m_thpool_flags; 17 | 18 | m_thpool_t *m_thpool_new(uint8_t thread_count, m_thpool_flags flags); 19 | int m_thpool_add(m_thpool_t *pool, m_thpool_task task, void *arg); 20 | ssize_t m_thpool_length(m_thpool_t *pool); 21 | ssize_t m_thpool_clear(m_thpool_t *pool); 22 | int m_thpool_free(m_thpool_t **pool, bool wait_all); 23 | -------------------------------------------------------------------------------- /Lib/utils/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | file(GLOB SRCS_utils_internal Lib/utils/*.c) 4 | 5 | add_library(${PROJECT_NAME}_utils_internal ${LIBRARY_TYPE} ${SRCS_utils_internal}) 6 | set_target_properties( 7 | ${PROJECT_NAME}_utils_internal PROPERTIES 8 | VERSION ${PROJECT_VERSION} 9 | SOVERSION ${PROJECT_VERSION_MAJOR} 10 | ) 11 | 12 | install(TARGETS ${PROJECT_NAME}_utils_internal 13 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 14 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 15 | ) 16 | -------------------------------------------------------------------------------- /Lib/utils/log.c: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static void libmodule_log_noop(const char *caller, int lineno, const char *fmt, ...) {} 9 | 10 | m_logger libmodule_logger; 11 | 12 | /** Logging functions **/ 13 | #define X_LOG_LEVEL(level) \ 14 | static void libmodule_log_##level(const char *caller, int lineno, const char *fmt, ...) { \ 15 | FILE *out = libmodule_logger.log_file; \ 16 | if (!out) { \ 17 | out = level >= INFO ? stdout : stderr; \ 18 | } \ 19 | fprintf(out, "| %c | %s:%d | ", #level[0], caller, lineno); \ 20 | va_list args; \ 21 | va_start(args, fmt); \ 22 | vfprintf(out, fmt, args); \ 23 | va_end(args); \ 24 | } 25 | X_LOG_LEVELS 26 | #undef X_LOG_LEVEL 27 | /** **/ 28 | 29 | static inline m_logger_level find_level(const char *level_str) { 30 | static const char *lvl_names[] = { 31 | #define X_LOG_LEVEL(name) #name, 32 | X_LOG_LEVELS 33 | #undef X_LOG_LEVEL 34 | }; 35 | for (int i = 0; i <= sizeof(lvl_names) / sizeof(*lvl_names); i++) { 36 | if (strcasecmp(level_str, lvl_names[i]) == 0) { 37 | return i; 38 | } 39 | } 40 | return -1; 41 | } 42 | 43 | static __attribute__((constructor (111))) void libmodule_log_init(void) { 44 | /* Load log level for each context */ 45 | const char *ctx_names[] = { 46 | #define X_LOG_CTX(name) #name, 47 | X_LOG_CTXS 48 | #undef X_LOG_CTX 49 | }; 50 | char *log_env; 51 | char env_name[64]; 52 | bool log_set[X_LOG_CTX_MAX] = {0}; 53 | for (int i = 0; i < X_LOG_CTX_MAX; i++) { 54 | /* Default values */ 55 | libmodule_logger.DEBUG[i] = libmodule_log_noop; 56 | libmodule_logger.INFO[i] = libmodule_log_noop; 57 | libmodule_logger.WARN[i] = libmodule_log_noop; 58 | libmodule_logger.ERR[i] = libmodule_log_noop; 59 | 60 | int log_level = ERR; 61 | snprintf(env_name, sizeof(env_name), "LIBMODULE_LOG_%s", ctx_names[i]); 62 | log_env = getenv(env_name); 63 | if (log_env) { 64 | log_level = find_level(log_env); 65 | if (log_level != -1) { 66 | log_set[i] = true; 67 | } else { 68 | // Default value 69 | log_level = ERR; 70 | } 71 | } 72 | switch (log_level) { 73 | case DEBUG: 74 | libmodule_logger.DEBUG[i] = libmodule_log_DEBUG; 75 | case INFO: 76 | libmodule_logger.INFO[i] = libmodule_log_INFO; 77 | case WARN: 78 | libmodule_logger.WARN[i] = libmodule_log_WARN; 79 | default: 80 | libmodule_logger.ERR[i] = libmodule_log_ERR; 81 | break; 82 | } 83 | } 84 | 85 | int log_level = ERR; 86 | log_env = getenv("LIBMODULE_LOG"); 87 | if (log_env) { 88 | log_level = find_level(log_env); 89 | if (log_level == -1) { 90 | // Default value 91 | log_level = ERR; 92 | } 93 | } 94 | for (int i = 0; i < X_LOG_CTX_MAX; i++) { 95 | if (!log_set[i]) { 96 | switch (log_level) { 97 | case DEBUG: 98 | libmodule_logger.DEBUG[i] = libmodule_log_DEBUG; 99 | case INFO: 100 | libmodule_logger.INFO[i] = libmodule_log_INFO; 101 | case WARN: 102 | libmodule_logger.WARN[i] = libmodule_log_WARN; 103 | default: 104 | libmodule_logger.ERR[i] = libmodule_log_ERR; 105 | break; 106 | } 107 | } 108 | } 109 | 110 | const char *log_file_path = getenv("LIBMODULE_LOG_OUTPUT"); 111 | if (log_file_path) { 112 | libmodule_logger.log_file = fopen(log_file_path, "w"); 113 | } 114 | } 115 | 116 | static __attribute__((destructor (110))) void libmodule_log_deinit(void) { 117 | if (libmodule_logger.log_file) { 118 | fclose(libmodule_logger.log_file); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Lib/utils/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define unlikely(x) __builtin_expect((x),0) 8 | #define _weak_ __attribute__((weak)) 9 | #define _public_ __attribute__((visibility("default"))) 10 | 11 | #define X_LOG_LEVELS \ 12 | X_LOG_LEVEL(ERR) \ 13 | X_LOG_LEVEL(WARN) \ 14 | X_LOG_LEVEL(INFO) \ 15 | X_LOG_LEVEL(DEBUG) 16 | 17 | #define X_LOG_CTXS \ 18 | X_LOG_CTX(MEM) \ 19 | X_LOG_CTX(STRUCTS) \ 20 | X_LOG_CTX(THPOOL) \ 21 | X_LOG_CTX(CORE) \ 22 | X_LOG_CTX(OTHER) 23 | 24 | typedef enum { 25 | #define X_LOG_LEVEL(name) name, 26 | X_LOG_LEVELS 27 | #undef X_LOG_LEVEL 28 | X_LOG_LEVEL_MAX 29 | } m_logger_level; 30 | 31 | typedef enum { 32 | #define X_LOG_CTX(name) name, 33 | X_LOG_CTXS 34 | #undef X_LOG_CTX 35 | X_LOG_CTX_MAX 36 | } m_logger_context; 37 | 38 | typedef struct { 39 | #define X_LOG_LEVEL(name) void (*name[X_LOG_CTX_MAX])(const char *caller, int lineno, const char *fmt, ...); 40 | X_LOG_LEVELS 41 | #undef X_LOG_LEVEL 42 | FILE *log_file; 43 | } m_logger; 44 | 45 | #ifndef LIBMODULE_LOG_CTX 46 | #define LIBMODULE_LOG_CTX OTHER 47 | #endif 48 | #define M_DEBUG(...) libmodule_logger.DEBUG[LIBMODULE_LOG_CTX](__func__, __LINE__, __VA_ARGS__) 49 | #define M_INFO(...) libmodule_logger.INFO[LIBMODULE_LOG_CTX](__func__, __LINE__, __VA_ARGS__) 50 | #define M_WARN(...) libmodule_logger.WARN[LIBMODULE_LOG_CTX](__func__, __LINE__, __VA_ARGS__) 51 | #define M_ERR(...) libmodule_logger.ERR[LIBMODULE_LOG_CTX](__func__, __LINE__, __VA_ARGS__) 52 | 53 | #define M_LOG_ASSERT(cond, msg, ret) if (unlikely(!(cond))) { M_DEBUG("%s\n", msg); return ret; } 54 | #define M_RET_ASSERT(cond, ret) M_LOG_ASSERT(cond, "("#cond ") condition failed.", ret) 55 | #define M_ALLOC_ASSERT(cond) M_RET_ASSERT(cond, -ENOMEM) 56 | #define M_PARAM_ASSERT(cond) M_RET_ASSERT(cond, -EINVAL) 57 | 58 | #ifndef NDEBUG 59 | #define M_ASSERT(cond) assert(cond); 60 | #else 61 | #define M_ASSERT(cond) 62 | #endif 63 | 64 | extern m_logger libmodule_logger; 65 | -------------------------------------------------------------------------------- /Lib/utils/mem.c: -------------------------------------------------------------------------------- 1 | #include "mem.h" 2 | #include 3 | #include 4 | 5 | m_memhook_t memhook = { malloc, calloc, free }; 6 | 7 | char *mem_strdup(const char *s) { 8 | char *new = NULL; 9 | if (s) { 10 | const size_t len = strlen(s) + 1; 11 | new = memhook._malloc(len); 12 | if (new) { 13 | memcpy(new, s, len); 14 | } 15 | } 16 | return new; 17 | } 18 | -------------------------------------------------------------------------------- /Lib/utils/mem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | char *mem_strdup(const char *s); 6 | 7 | /* Struct that holds user defined memory functions */ 8 | typedef struct { 9 | void *(*_malloc)(size_t size); 10 | void *(*_calloc)(size_t nmemb, size_t size); 11 | void (*_free)(void *ptr); 12 | } m_memhook_t; 13 | 14 | extern m_memhook_t memhook; 15 | -------------------------------------------------------------------------------- /Lib/utils/utils.c: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | #include 3 | 4 | void fetch_ms(uint64_t *val, uint64_t *ctr) { 5 | struct timespec spec; 6 | #ifdef CLOCK_BOOTTIME 7 | clock_gettime(CLOCK_BOOTTIME, &spec); 8 | #else 9 | clock_gettime(CLOCK_MONOTONIC, &spec); 10 | #endif 11 | *val = spec.tv_sec * 1000 + spec.tv_nsec / 1000000; 12 | 13 | if (ctr) { 14 | (*ctr)++; 15 | } 16 | } 17 | 18 | bool str_not_empty(const char *str) { 19 | return str && str[0] != '\0'; 20 | } 21 | -------------------------------------------------------------------------------- /Lib/utils/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void fetch_ms(uint64_t *val, uint64_t *ctr); 7 | bool str_not_empty(const char *str); 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Libmodule 2 | 3 | [![CI Build](https://github.com/FedeDP/libmodule/actions/workflows/ci.yaml/badge.svg)](https://github.com/FedeDP/libmodule/actions/workflows/ci.yaml) 4 | [![CodeQL](https://github.com/FedeDP/libmodule/actions/workflows/codeql.yaml/badge.svg)](https://github.com/FedeDP/libmodule/actions/workflows/codeql.yaml) 5 | [![Documentation Status](https://readthedocs.org/projects/libmodule/badge/?version=latest)](https://libmodule.readthedocs.io/en/latest/?badge=latest) 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 7 | 8 | ## What is this? 9 | 10 | Libmodule offers a small and simple C implementation of an actor library that aims to let developers easily create modular C projects in a way which is both simple and elegant. 11 | Indeed, libmodule was heavily inspired by my own actor library experience with [akka](https://akka.io/) for its API. 12 | 13 | ## What is a module, anyway? 14 | 15 | Unsurprisingly, module is the core concept of libmodule architecture. 16 | A module is an Actor that can listen on non-pubsub events too. 17 | Frankly speaking, it is denoted by a M_MOD() macro plus a bunch of mandatory callbacks, eg: 18 | ```C 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | M_MOD("Pippo"); 26 | 27 | static bool m_mod_on_eval(mod_t *mod) { 28 | /* Should module be started? */ 29 | return true; 30 | } 31 | 32 | static bool m_mod_on_start(mod_t *mod) { 33 | /* Register STDIN fd */ 34 | m_mod_src_register(mod, STDIN_FILENO, 0, NULL); 35 | return true; 36 | } 37 | 38 | static void m_mod_on_stop(mod_t *mod) { 39 | 40 | } 41 | 42 | static void m_mod_on_evt(m_mod_t *mod, const m_queue_t *const evts) { 43 | m_itr_foreach(evts, { 44 | m_evt_t *msg = m_itr_get(m_itr); 45 | switch (msg->type) { 46 | case M_SRC_TYPE_FD: { 47 | char c; 48 | read(msg->fd_evt->fd, &c, sizeof(char)); 49 | switch (tolower(c)) { 50 | case 'q': 51 | m_mod_log(mod, "Leaving...\n"); 52 | m_mod_tell(mod, mod, "ByeBye", 0); 53 | break; 54 | default: 55 | if (c != ' ' && c != '\n') { 56 | m_mod_log(mod, "Pressed '%c'\n", c); 57 | } 58 | break; 59 | } 60 | break; 61 | } 62 | case M_SRC_TYPE_PS: 63 | if (strcmp((char *)msg->ps_evt->data, "ByeBye") == 0) { 64 | m_mod_log("Bye\n"): 65 | m_ctx_quit(0); 66 | } 67 | break; 68 | } 69 | } 70 | } 71 | ``` 72 | In this example, a "Pippo" module is created and will echo chars from stdin until 'q' is pressed, exiting with 0. 73 | Note that it does not even need a main function, as libmodule already provides a default one as a [weak, thus overridable, symbol](https://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Function-Attributes.html). 74 | 75 | ## Is it portable? 76 | 77 | Yes, it is. Non-portable code is actually [compile-time-plugins](Lib/core/poll/) based. 78 | On linux, libmodule's internal loop will use epoll, while on BSD and MacOS it will use kqueue. 79 | On other OS, cmake will fallback at looking for [libkqueue](https://github.com/mheily/libkqueue), a drop-in replacement for kqueue. 80 | 81 | Finally, it heavily relies upon gcc attributes that may or may not be available for your compiler. 82 | 83 | ## Is there any documentation? 84 | 85 | Yes, it is availabe at [readthedocs](http://libmodule.readthedocs.io/en/latest/). 86 | There are some simple examples too, see [Samples](Samples/) folder. 87 | To see a real project using libmodule, see [Clight](https://github.com/FedeDP/Clight) and [Clightd](https://github.com/FedeDP/Clightd). 88 | 89 | ## CI 90 | 91 | Libmodule, samples and tests are built and tested with github actions on ubuntu, freebsd, and macos. 92 | Moreover, tests are executed and valgrind checked too. 93 | Finally, a codeQL workflow ensures code quality. 94 | 95 | ## But...why? 96 | 97 | We all know OOP is not a solution to every problem and C is still a beautiful and much used language. 98 | Still, I admit to love code modularity that OOP enforces; moreover, I realized that I was using same code abstractions over and over in my C projects (both side projects and at my job). 99 | So I thought that writing a library to achieve those same abstractions in a cleaner and simpler way was the right thing to do and could help some other dev. 100 | 101 | ## Build dep and how to build 102 | 103 | You only need cmake to build libmodule on Linux and BSD/osx; it does not depend upon external software. 104 | On other platforms, you will need [libkqueue](https://github.com/mheily/libkqueue) too. 105 | To build, you only need to issue: 106 | 107 | $ mkdir build 108 | $ cd build 109 | $ cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib ../ 110 | $ make 111 | 112 | *Note that libmodule can also be built as static library, by passing -DSTATIC_MODULE=true parameter to cmake.* 113 | 114 | Installation - Generic OS 115 | ------------------------- 116 | 117 | # make install 118 | 119 | Installation - Red Hat 120 | ---------------------- 121 | 122 | $ cpack -G RPM 123 | 124 | And finally install generated RPM package. 125 | 126 | Installation - Debian 127 | --------------------- 128 | 129 | $ cpack -G DEB 130 | 131 | And finally install generated DEB package. 132 | 133 | Libmodule will install a pkg-config script too: use it to link libmodule in your projects, or use "-lmodule" linker flag. 134 | Please note that in order to test examples, there is no need to install the library. 135 | 136 | For Archlinux users, Libmodule is available on [AUR](https://aur.archlinux.org/packages/libmodule/). 137 | 138 | ## License 139 | Libmodule is made available with a MIT license to encourage people to actually try it out and maybe use it inside their project, no matter if open or closed source. 140 | 141 | *Are you using libmodule in your project? Please let me know and I will gladly list it here.* 142 | -------------------------------------------------------------------------------- /Samples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | file(GLOB EASY_SRC Easy/*.c) 4 | file(GLOB SHAREDSRC_SRC SharedSrc/*.c) 5 | file(GLOB POLL_SRC Poll/*.c) 6 | file(GLOB TASK_SRC Task/*.c) 7 | file(GLOB THPOOL_SRC Thpool/*.c) 8 | 9 | add_executable(Easy ${EASY_SRC}) 10 | add_executable(SharedSrc ${SHAREDSRC_SRC}) 11 | add_executable(Poll ${POLL_SRC}) 12 | add_executable(Task ${TASK_SRC}) 13 | add_executable(Thpool ${THPOOL_SRC}) 14 | 15 | add_library(testmod SHARED testModule.c) 16 | target_link_libraries(testmod ${PROJECT_NAME}_core) 17 | 18 | target_include_directories(testmod PRIVATE ${PROJECT_SOURCE_DIR}/Lib/core/public/ ${PROJECT_SOURCE_DIR}/Lib/structs/public/) 19 | 20 | target_link_libraries(Easy ${PROJECT_NAME}_core) 21 | target_include_directories(Easy PRIVATE ${PROJECT_SOURCE_DIR}/Lib/core/public/ ${PROJECT_SOURCE_DIR}/Lib/structs/public/ ${PROJECT_SOURCE_DIR}/Lib/mem/public/) 22 | 23 | target_link_libraries(SharedSrc ${PROJECT_NAME}_core) 24 | target_include_directories(SharedSrc PRIVATE ${PROJECT_SOURCE_DIR}/Lib/core/public/ ${PROJECT_SOURCE_DIR}/Lib/structs/public/ ${PROJECT_SOURCE_DIR}/Lib/mem/public/) 25 | 26 | target_link_libraries(Poll ${PROJECT_NAME}_core) 27 | target_include_directories(Poll PRIVATE ${PROJECT_SOURCE_DIR}/Lib/core/public/ ${PROJECT_SOURCE_DIR}/Lib/structs/public/ ${PROJECT_SOURCE_DIR}/Lib/mem/public/) 28 | 29 | target_link_libraries(Task ${PROJECT_NAME}_core) 30 | target_include_directories(Task PRIVATE ${PROJECT_SOURCE_DIR}/Lib/core/public/ ${PROJECT_SOURCE_DIR}/Lib/structs/public/ ${PROJECT_SOURCE_DIR}/Lib/mem/public/) 31 | 32 | target_link_libraries(Thpool ${PROJECT_NAME}_core) 33 | target_include_directories(Thpool PRIVATE ${PROJECT_SOURCE_DIR}/Lib/core/public/ ${PROJECT_SOURCE_DIR}/Lib/structs/public/ ${PROJECT_SOURCE_DIR}/Lib/mem/public/ ${PROJECT_SOURCE_DIR}/Lib/thpool/public/) 34 | -------------------------------------------------------------------------------- /Samples/Easy/README.md: -------------------------------------------------------------------------------- 1 | # Easy Example 2 | 3 | This example shows how to use libmodule's easy API. 4 | This is the simplest libmodule usage example, and it is pretty self-explanatory. 5 | Moreover, it runtime-loads another module, testModule.c (compiled as a shared object: testModule.so). 6 | 7 | Main() is missing as libmodule provides it as a weak symbol to be used with easy API. -------------------------------------------------------------------------------- /Samples/Easy/doggo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static void m_mod_on_evt_sleeping(m_mod_t *mod, const m_queue_t *const evts); 7 | static m_mod_t *plugin; 8 | 9 | M_MOD("Doggo"); 10 | 11 | static void m_mod_on_boot(void) { 12 | printf("Press 'c' to start playing with your own doggo...\n"); 13 | } 14 | 15 | #ifdef M_CTX_HAS_FS 16 | void m_ctx_pre_loop(int argc, char *argv[]) { 17 | m_ctx_fs_set_root("./EasyCtx"); 18 | } 19 | #endif 20 | 21 | static bool m_mod_on_start(m_mod_t *mod) { 22 | /* Doggo should subscribe to "leaving" topic, as regex */ 23 | m_mod_src_register(mod, "leav[i+]ng", 0, NULL); 24 | /* Subscribe to MOD_STOPPED system messages too! */ 25 | m_mod_src_register(mod, M_PS_MOD_STOPPED, 0, NULL); 26 | return true; 27 | } 28 | 29 | static bool m_mod_on_eval(m_mod_t *mod) { 30 | return true; 31 | } 32 | 33 | static void m_mod_on_stop(m_mod_t *mod) { 34 | if (plugin) { 35 | m_mem_unrefp((void **)&plugin); 36 | } 37 | } 38 | 39 | static void m_mod_on_evt(m_mod_t *mod, const m_queue_t *const evts) { 40 | m_itr_foreach(evts, { 41 | m_evt_t *msg = m_itr_get(m_itr); 42 | if (msg->type == M_SRC_TYPE_PS) { 43 | if (msg->ps_evt->topic && strcmp(msg->ps_evt->topic, M_PS_MOD_STOPPED) == 0) { 44 | if (msg->ps_evt->sender) { 45 | const char *name = m_mod_name(msg->ps_evt->sender); 46 | m_mod_log(mod, "Module '%s' has been stopped.\n", name); 47 | } else { 48 | m_mod_log(mod, "A module has been deregistered.\n"); 49 | } 50 | } else { 51 | if (!strcmp((char *)msg->ps_evt->data, "ComeHere")) { 52 | m_mod_log(mod, "Running...\n"); 53 | m_mod_ps_tell(mod, msg->ps_evt->sender, "BauBau", 0); 54 | } else if (!strcmp((char *)msg->ps_evt->data, "LetsPlay")) { 55 | m_mod_log(mod, "BauBau BauuBauuu!\n"); 56 | } else if (!strcmp((char *)msg->ps_evt->data, "LetsEat")) { 57 | m_mod_log(mod, "Burp!\n"); 58 | } else if (!strcmp((char *)msg->ps_evt->data, "LetsSleep")) { 59 | m_mod_become(mod, m_mod_on_evt_sleeping); 60 | m_mod_log(mod, "ZzzZzz...\n"); 61 | m_mod_src_register(mod, M_PS_MOD_STARTED, 0, NULL); 62 | 63 | /* 64 | * Test runtime module loading; loaded module won't have direct access to CTX. 65 | * We own a ref on it! 66 | */ 67 | m_mod_register("./libtestmod.so", &plugin, NULL, M_MOD_DENY_CTX, NULL); 68 | } else if (!strcmp((char *)msg->ps_evt->data, "ByeBye")) { 69 | m_mod_log(mod, "Sob...\n"); 70 | } else if (!strcmp((char *)msg->ps_evt->data, "WakeUp")) { 71 | m_mod_log(mod, "???\n"); 72 | } 73 | } 74 | } 75 | }); 76 | } 77 | 78 | static void m_mod_on_evt_sleeping(m_mod_t *mod, const m_queue_t *const evts) { 79 | m_itr_foreach(evts, { 80 | m_evt_t *msg = m_itr_get(m_itr); 81 | if (msg->type == M_SRC_TYPE_PS) { 82 | if (msg->ps_evt->topic && strcmp(msg->ps_evt->topic, M_PS_MOD_STARTED) == 0) { 83 | /* A new module has been started */ 84 | const char *name = m_mod_name(msg->ps_evt->sender); 85 | m_mod_log(mod, "Module '%s' has been started.\n", name); 86 | } else { 87 | if (!strcmp((char *)msg->ps_evt->data, "WakeUp")) { 88 | m_mod_unbecome(mod); 89 | m_mod_log(mod, "Yawn...\n"); 90 | m_ctx_dump(); 91 | m_mod_deregister(&plugin); 92 | m_mod_src_deregister(mod, M_PS_MOD_STARTED); 93 | } else { 94 | m_mod_log(mod, "ZzzZzz...\n"); 95 | } 96 | } 97 | } 98 | }); 99 | } 100 | -------------------------------------------------------------------------------- /Samples/Easy/pippo.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #ifdef __linux__ 13 | #include 14 | #else 15 | #include 16 | #endif 17 | 18 | static m_mod_t *doggo; 19 | 20 | M_MOD("Pippo"); 21 | 22 | static int myData = 5; 23 | static int tick_ctr = 0; 24 | 25 | static void m_mod_on_evt_ready(m_mod_t *mod, const m_queue_t *const evts); 26 | 27 | static void m_mod_on_boot() { 28 | 29 | } 30 | 31 | static bool m_mod_on_start(m_mod_t *mod) { 32 | m_mod_src_register(mod, &((m_src_sgn_t) { SIGINT }), 0, &myData); 33 | m_mod_src_register(mod, &((m_src_tmr_t) { CLOCK_MONOTONIC, (uint64_t)5 * 1000 * 1000 * 1000 }), M_SRC_ONESHOT, NULL); // 5s 34 | m_mod_src_register(mod, STDIN_FILENO, 0, NULL); 35 | #ifdef __linux__ 36 | m_mod_src_register(mod, &((m_src_path_t) { "/home/federico", IN_CREATE }), 0, &myData); 37 | #else 38 | m_mod_src_register(mod, &((m_src_path_t) { "/home/federico", NOTE_WRITE }), 0, &myData); 39 | #endif 40 | 41 | /* Get Doggo module reference */ 42 | doggo = m_mod_lookup(mod, "Doggo"); 43 | 44 | // let context tick every 5s and subscribe to it 45 | m_ctx_set_tick((uint64_t)5 * 1000 * 1000 * 1000); 46 | m_mod_src_register(mod, M_PS_CTX_TICK, 0, NULL); 47 | return true; 48 | } 49 | 50 | static bool m_mod_on_eval(m_mod_t *mod) { 51 | return true; 52 | } 53 | 54 | static void m_mod_on_stop(m_mod_t *mod) { 55 | 56 | } 57 | 58 | static void m_mod_on_evt(m_mod_t *mod, const m_queue_t *const evts) { 59 | m_itr_foreach(evts, { 60 | m_evt_t *msg = m_itr_get(m_itr); 61 | if (msg->type != M_SRC_TYPE_PS) { 62 | char c; 63 | 64 | /* Forcefully quit if we received a signal */ 65 | if (msg->type == M_SRC_TYPE_SGN) { 66 | c = 'q'; 67 | int *data = (int *)msg->userdata; 68 | if (data) { 69 | m_mod_log(mod, "Data is %d. Received %d.\n", *data, msg->sgn_evt->signo); 70 | } 71 | } else if (msg->type == M_SRC_TYPE_TMR) { 72 | m_mod_log(mod, "Timed out.\n"); 73 | c = 'q'; 74 | } else if (msg->type == M_SRC_TYPE_PATH) { 75 | m_mod_log(mod, "A file was created in %s.\n", msg->path_evt->path); 76 | c = 10; 77 | } else { 78 | (void)!read(msg->fd_evt->fd, &c, sizeof(char)); 79 | } 80 | 81 | switch (tolower(c)) { 82 | case 'c': 83 | m_mod_log(mod, "Doggo, come here!\n"); 84 | m_mod_ps_tell(mod, doggo, "ComeHere", 0); 85 | break; 86 | case 'q': 87 | m_mod_log(mod, "I have to go now!\n"); 88 | m_mod_ps_publish(mod, "leaving", "ByeBye", 0); 89 | m_ctx_quit(0); 90 | break; 91 | default: 92 | /* Avoid newline */ 93 | if (c != 10) { 94 | m_mod_log(mod, "You got to call your doggo first. Press 'c'.\n"); 95 | } 96 | break; 97 | } 98 | } else if (strcmp((char *)msg->ps_evt->data, "BauBau") == 0) { 99 | m_ctx_dump(); 100 | m_mod_become(mod, m_mod_on_evt_ready); 101 | m_mod_log(mod, "Press 'p' to play with Doggo! Or 'f' to feed your Doggo. 's' to have a nap. 'w' to wake him up. 'q' to leave him for now.\n"); 102 | } 103 | }); 104 | } 105 | 106 | /* 107 | * Secondary poll callback. 108 | * Use m_m_become(ready) to start using this second poll callback. 109 | */ 110 | static void m_mod_on_evt_ready(m_mod_t *mod, const m_queue_t *const evts) { 111 | m_itr_foreach(evts, { 112 | m_evt_t *msg = m_itr_get(m_itr); 113 | if (msg->type != M_SRC_TYPE_PS) { 114 | char c = 10; 115 | 116 | /* Forcefully quit if we received a signal */ 117 | if (msg->type == M_SRC_TYPE_SGN) { 118 | c = 'q'; 119 | m_mod_log(mod, "Received %d. Quit.\n", msg->sgn_evt->signo); 120 | } else if (msg->type == M_SRC_TYPE_FD) { 121 | (void)!read(msg->fd_evt->fd, &c, sizeof(char)); 122 | } else if (msg->type == M_SRC_TYPE_TMR) { 123 | m_mod_log(mod, "Timer expired.\n"); 124 | } else if (msg->type == M_SRC_TYPE_PATH) { 125 | m_mod_log(mod, "A file was created in %s.\n", msg->path_evt->path); 126 | } 127 | 128 | switch (tolower(c)) { 129 | case 'p': 130 | m_mod_log(mod, "Doggo, let's play a bit!\n"); 131 | m_mod_ps_tell(mod, doggo, "LetsPlay", 0); 132 | break; 133 | case 's': 134 | m_mod_log(mod, "Doggo, you should sleep a bit!\n"); 135 | m_mod_ps_tell(mod, doggo, "LetsSleep", 0); 136 | break; 137 | case 'f': 138 | m_mod_log(mod, "Doggo, you want some of these?\n"); 139 | m_mod_ps_tell(mod, doggo, "LetsEat", 0); 140 | break; 141 | case 'w': 142 | m_mod_log(mod, "Doggo, wake up!\n"); 143 | m_mod_ps_tell(mod, doggo, "WakeUp", 0); 144 | break; 145 | case 'q': 146 | m_mod_dump(mod); 147 | m_mod_log(mod, "I have to go now!\n"); 148 | m_mod_ps_publish(mod, "leaving", "ByeBye", 0); 149 | m_ctx_quit(0); 150 | break; 151 | default: 152 | /* Avoid newline */ 153 | if (c != 10) { 154 | m_mod_log(mod, "Unrecognized command. Beep. Please enter a new one... Totally not a bot.\n"); 155 | } 156 | break; 157 | } 158 | } else if (msg->ps_evt->topic && strcmp(msg->ps_evt->topic, M_PS_CTX_TICK) == 0) { 159 | m_mod_log(mod, "Received ctx tick.\n"); 160 | if (++tick_ctr == 5) { 161 | m_mod_log(mod, "Stop playing and get back to do stuff!\n"); 162 | m_ctx_quit(-EPIPE); 163 | } 164 | } 165 | }); 166 | } 167 | -------------------------------------------------------------------------------- /Samples/Poll/README.md: -------------------------------------------------------------------------------- 1 | # Poll Example 2 | 3 | This example shows 2 things: 4 | * how to integrate libmodule event dispatcher into your own loop 5 | * how to use easy API using a custom main function 6 | 7 | -------------------------------------------------------------------------------- /Samples/Poll/doggo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static void m_mod_on_evt_sleeping(m_mod_t *mod, const m_queue_t *const evts); 5 | 6 | M_MOD("Doggo"); 7 | 8 | static void m_mod_on_boot(void) { 9 | 10 | } 11 | 12 | static bool m_mod_on_start(m_mod_t *mod) { 13 | /* Doggo should subscribe to "leaving" topic */ 14 | m_mod_src_register(mod, "leaving", 0, NULL); 15 | return true; 16 | } 17 | 18 | static bool m_mod_on_eval(m_mod_t *mod) { 19 | return true; 20 | } 21 | 22 | static void m_mod_on_stop(m_mod_t *mod) { 23 | 24 | } 25 | 26 | static void m_mod_on_evt(m_mod_t *mod, const m_queue_t *const evts) { 27 | m_itr_foreach(evts, { 28 | m_evt_t *msg = m_itr_get(m_itr); 29 | if (msg->type == M_SRC_TYPE_PS) { 30 | if (!strcmp((char *)msg->ps_evt->data, "ComeHere")) { 31 | m_mod_log(mod, "Running...\n"); 32 | m_mod_ps_tell(mod, msg->ps_evt->sender, "BauBau", 0); 33 | } else if (!strcmp((char *)msg->ps_evt->data, "LetsPlay")) { 34 | m_mod_log(mod, "BauBau BauuBauuu!\n"); 35 | } else if (!strcmp((char *)msg->ps_evt->data, "LetsEat")) { 36 | m_mod_log(mod, "Burp!\n"); 37 | } else if (!strcmp((char *)msg->ps_evt->data, "LetsSleep")) { 38 | m_mod_become(mod, m_mod_on_evt_sleeping); 39 | m_mod_log(mod, "ZzzZzz...\n"); 40 | } else if (!strcmp((char *)msg->ps_evt->data, "ByeBye")) { 41 | m_mod_log(mod, "Sob...\n"); 42 | } else if (!strcmp((char *)msg->ps_evt->data, "WakeUp")) { 43 | m_mod_log(mod, "???\n"); 44 | } 45 | } 46 | }); 47 | } 48 | 49 | static void m_mod_on_evt_sleeping(m_mod_t *mod, const m_queue_t *const evts) { 50 | m_itr_foreach(evts, { 51 | m_evt_t *msg = m_itr_get(m_itr); 52 | if (msg->type == M_SRC_TYPE_PS) { 53 | if (!strcmp((char *)msg->ps_evt->data, "WakeUp")) { 54 | m_mod_unbecome(mod); 55 | m_mod_log(mod, "Yawn...\n"); 56 | } else { 57 | m_mod_log(mod, "ZzzZzz...\n"); 58 | } 59 | } 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /Samples/Poll/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* 6 | * This function is automatically called before registering any module. 7 | * Use this function to eg: parse config needed to decide 8 | * whether to start some module. 9 | * There is no need to explicitly call it. 10 | */ 11 | void m_on_boot(void) { 12 | printf("Poll_Sample -> Started Libmodule %d.%d.%d\n", LIBMODULE_VERSION_MAJ, LIBMODULE_VERSION_MIN, LIBMODULE_VERSION_PAT); 13 | printf("Press 'c' to start playing with your own doggo...\n"); 14 | } 15 | 16 | int main(int argc, char *argv[]) { 17 | int ret = 0; 18 | 19 | /* Initial dispatch */ 20 | if (m_ctx_dispatch() != 0) { 21 | return 1; 22 | } 23 | 24 | /* Get context fd */ 25 | int fd = m_ctx_fd(); 26 | if (fd < 0) { 27 | return 1; 28 | } 29 | 30 | struct pollfd fds; 31 | fds.fd = fd; 32 | fds.events = POLLIN; 33 | 34 | while (ret >= 0) { 35 | ret = poll(&fds, 1, -1); 36 | if (ret > 0) { 37 | if (fds.revents & POLLIN) { 38 | ret = m_ctx_dispatch(); 39 | if (ret < 0) { 40 | printf("Loop: error happened.\n"); 41 | } else if (ret > 0) { 42 | printf("Loop: dispatched %d messages.\n", ret); 43 | } else { 44 | printf("Loop exited.\n"); 45 | break; 46 | } 47 | } 48 | } 49 | } 50 | close(fd); 51 | m_ctx_deregister(); 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /Samples/Poll/pippo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static m_mod_t *doggo; 9 | 10 | M_MOD("Pippo"); 11 | 12 | static int myData = 5; 13 | 14 | static void m_mod_on_evt_ready(m_mod_t *mod, const m_queue_t *const evts); 15 | 16 | static void m_mod_on_boot(void) { 17 | 18 | } 19 | 20 | static bool m_mod_on_start(m_mod_t *mod) { 21 | m_mod_src_register(mod, &((m_src_sgn_t) { SIGINT }), 0, &myData); 22 | m_mod_src_register(mod, STDIN_FILENO, 0, NULL); 23 | 24 | /* Get Doggo reference */ 25 | doggo = m_mod_lookup(mod, "Doggo"); 26 | return true; 27 | } 28 | 29 | static bool m_mod_on_eval(m_mod_t *mod) { 30 | return true; 31 | } 32 | 33 | static void m_mod_on_stop(m_mod_t *mod) { 34 | 35 | } 36 | 37 | static void m_mod_on_evt(m_mod_t *mod, const m_queue_t *const evts) { 38 | m_itr_foreach(evts, { 39 | m_evt_t *msg = m_itr_get(m_itr); 40 | if (msg->type != M_SRC_TYPE_PS) { 41 | char c = 'q'; 42 | 43 | /* Forcefully quit if we received a SIGINT */ 44 | if (msg->type == M_SRC_TYPE_SGN) { 45 | c = 'q'; 46 | int *data = (int *)msg->userdata; 47 | if (data) { 48 | m_mod_log(mod, "Received %d. Data is %d\n", msg->sgn_evt->signo, *data); 49 | } 50 | } else if (msg->type == M_SRC_TYPE_FD) { 51 | (void)!read(msg->fd_evt->fd, &c, sizeof(char)); 52 | } 53 | 54 | switch (tolower(c)) { 55 | case 'c': 56 | m_mod_log(mod,"Doggo, come here!\n"); 57 | m_mod_ps_tell(mod, doggo, "ComeHere", 0); 58 | break; 59 | case 'q': 60 | m_mod_log(mod,"I have to go now!\n"); 61 | m_mod_ps_publish(mod, "leaving", "ByeBye", 0); 62 | m_ctx_quit(0); 63 | break; 64 | default: 65 | /* Avoid newline */ 66 | if (c != 10) { 67 | m_mod_log(mod,"You got to call your doggo first. Press 'c'.\n"); 68 | } 69 | break; 70 | } 71 | } else { 72 | if (strcmp((char *)msg->ps_evt->data, "BauBau") == 0) { 73 | m_mod_become(mod, m_mod_on_evt_ready); 74 | m_mod_log(mod,"Press 'p' to play with Doggo! Or 'f' to feed your Doggo. 's' to have a nap. 'w' to wake him up. 'q' to leave him for now.\n"); 75 | } 76 | } 77 | }); 78 | } 79 | 80 | /* 81 | * Secondary poll callback. 82 | * Use m_become(ready) to start using this second poll callback. 83 | */ 84 | static void m_mod_on_evt_ready(m_mod_t *mod, const m_queue_t *const evts) { 85 | m_itr_foreach(evts, { 86 | m_evt_t *msg = m_itr_get(m_itr); 87 | if (msg->type != M_SRC_TYPE_PS) { 88 | char c = 'q'; 89 | 90 | /* Forcefully quit if we received a SIGINT */ 91 | if (msg->type == M_SRC_TYPE_SGN) { 92 | c = 'q'; 93 | m_mod_log(mod,"Received %d.\n", msg->sgn_evt->signo); 94 | } else if (msg->type == M_SRC_TYPE_FD) { 95 | (void)!read(msg->fd_evt->fd, &c, sizeof(char)); 96 | } 97 | 98 | switch (tolower(c)) { 99 | case 'p': 100 | m_mod_log(mod,"Doggo, let's play a bit!\n"); 101 | m_mod_ps_tell(mod, doggo, "LetsPlay", 0); 102 | m_ctx_dump(); 103 | break; 104 | case 's': 105 | m_mod_log(mod,"Doggo, you should sleep a bit!\n"); 106 | m_mod_ps_tell(mod, doggo, "LetsSleep", 0); 107 | break; 108 | case 'f': 109 | m_mod_log(mod, "Doggo, you want some of these?\n"); 110 | m_mod_ps_tell(mod, doggo, "LetsEat", 0); 111 | break; 112 | case 'w': 113 | m_mod_log(mod, "Doggo, wake up!\n"); 114 | m_mod_ps_tell(mod, doggo, "WakeUp", 0); 115 | break; 116 | case 'q': 117 | m_mod_log(mod, "I have to go now!\n"); 118 | m_mod_ps_publish(mod, "leaving", "ByeBye", 0); 119 | m_ctx_quit(0); 120 | break; 121 | default: 122 | /* Avoid newline */ 123 | if (c != 10) { 124 | m_mod_log(mod, "Unrecognized command. Beep. Please enter a new one... Totally not a bot.\n"); 125 | } 126 | break; 127 | } 128 | } 129 | }); 130 | } 131 | -------------------------------------------------------------------------------- /Samples/README.md: -------------------------------------------------------------------------------- 1 | # Libmodule Samples 2 | 3 | This folder contains some libmodule's examples. 4 | 5 | ## Building 6 | 7 | To build these samples, pass "-DBUILD_SAMPLES=true" when building libmodule (from folder libmodule/build) 8 | 9 | $ cmake -DBUILD_SAMPLES=true ../ 10 | 11 | Then you'll find executables in Samples folder (eg: libmodule/build/Samples) -------------------------------------------------------------------------------- /Samples/SharedSrc/README.md: -------------------------------------------------------------------------------- 1 | # Shared Source Example 2 | 3 | This example shows a simple usage for libmodule advanced API: 4 | * context registering 5 | * modules registering 6 | * context looping -------------------------------------------------------------------------------- /Samples/SharedSrc/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern void create_modules(void); 5 | 6 | /* 7 | * This function is automatically called before initing any module. 8 | * Use this function to eg: parse config needed to decide 9 | * whether to start some module. 10 | * There is no need to explicitly call it. 11 | */ 12 | void m_on_boot(void) { 13 | printf("Press 'c' to start playing with your own doggo...\n"); 14 | } 15 | 16 | int main(int argc, char *argv[]) { 17 | /* 18 | * Register the context 19 | */ 20 | m_ctx_register("SharedSrc", 0, NULL); 21 | 22 | /* 23 | * Create the modules in the context 24 | */ 25 | create_modules(); 26 | 27 | /* 28 | * Loop on context to fetch modules' events 29 | */ 30 | m_ctx_loop(); 31 | 32 | /* 33 | * Finally, destroy the context, thus destroying its modules too. 34 | */ 35 | m_ctx_deregister(); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /Samples/SharedSrc/mod.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static bool A_init(m_mod_t *mod); 9 | static bool B_init(m_mod_t *mod); 10 | static void A_recv(m_mod_t *mod, const m_queue_t *const evts); 11 | static void A_recv_ready(m_mod_t *mod, const m_queue_t *const evts); 12 | static void B_recv(m_mod_t *mod, const m_queue_t *const evts); 13 | static void B_recv_sleeping(m_mod_t *mod, const m_queue_t *const evts); 14 | static void A_dtor(m_mod_t *mod); 15 | 16 | static m_mod_t *refB; 17 | 18 | /* 19 | * Create "A" and "B" modules in ctx_name context. 20 | * These modules can share some callbacks. 21 | */ 22 | void create_modules(void) { 23 | m_mod_hook_t hookA = (m_mod_hook_t) {A_init, NULL, A_recv, A_dtor }; 24 | m_mod_hook_t hookB = (m_mod_hook_t) {B_init, NULL, B_recv, NULL }; 25 | 26 | m_mod_register("Pippo", NULL, &hookA, 0, NULL); 27 | m_mod_register("Doggo", NULL, &hookB, 0, NULL); 28 | } 29 | 30 | static bool A_init(m_mod_t *mod) { 31 | refB = m_mod_lookup(mod, "Doggo"); 32 | m_mod_src_register(mod, STDIN_FILENO, 0, NULL); 33 | return true; 34 | } 35 | 36 | static bool B_init(m_mod_t *mod) { 37 | /* Doggo should subscribe to "leaving" topic */ 38 | m_mod_src_register(mod, "leaving", 0, NULL); 39 | return true; 40 | } 41 | 42 | static void A_dtor(m_mod_t *mod) { 43 | 44 | } 45 | 46 | /* 47 | * Our A module's poll callback. 48 | */ 49 | static void A_recv(m_mod_t *mod, const m_queue_t *const evts) { 50 | m_itr_foreach(evts, { 51 | m_evt_t *msg = m_itr_get(m_itr); 52 | if (msg->type != M_SRC_TYPE_PS) { 53 | char c; 54 | (void)!read(msg->fd_evt->fd, &c, sizeof(char)); 55 | 56 | switch (tolower(c)) { 57 | case 'c': 58 | m_mod_log(mod, "Doggo, come here!\n"); 59 | m_mod_ps_tell(mod, refB, (unsigned char *)"ComeHere", 0); 60 | break; 61 | case 'q': 62 | m_mod_log(mod, "I have to go now!\n"); 63 | m_mod_ps_publish(mod, "leaving", (unsigned char *)"ByeBye", 0); 64 | m_ctx_quit(0); 65 | break; 66 | default: 67 | /* Avoid newline */ 68 | if (c != 10) { 69 | m_mod_log(mod, "You got to call your doggo first. Press 'c'.\n"); 70 | } 71 | break; 72 | } 73 | } else { 74 | if (strcmp((char *)msg->ps_evt->data, "BauBau") == 0) { 75 | m_mod_become(mod, A_recv_ready); 76 | m_mod_log(mod, "Press 'p' to play with Doggo! Or 'f' to feed your Doggo. 's' to have a nap. 'w' to wake him up. 'q' to leave him for now.\n"); 77 | } 78 | } 79 | }); 80 | } 81 | 82 | static void A_recv_ready(m_mod_t *mod, const m_queue_t *const evts) { 83 | m_itr_foreach(evts, { 84 | m_evt_t *msg = m_itr_get(m_itr); 85 | if (msg->type != M_SRC_TYPE_PS) { 86 | char c; 87 | (void)!read(msg->fd_evt->fd, &c, sizeof(char)); 88 | 89 | switch (tolower(c)) { 90 | case 'p': 91 | m_mod_log(mod, "Doggo, let's play a bit!\n"); 92 | m_mod_ps_tell(mod, refB, (unsigned char *)"LetsPlay", 0); 93 | break; 94 | case 's': 95 | m_mod_log(mod, "Doggo, you should sleep a bit!\n"); 96 | m_mod_ps_tell(mod, refB, (unsigned char *)"LetsSleep", 0); 97 | break; 98 | case 'f': 99 | m_mod_log(mod, "Doggo, you want some of these?\n"); 100 | m_mod_ps_tell(mod, refB, (unsigned char *)"LetsEat", 0); 101 | break; 102 | case 'w': 103 | m_mod_log(mod, "Doggo, wake up!\n"); 104 | m_mod_ps_tell(mod, refB, (unsigned char *)"WakeUp", 0); 105 | break; 106 | case 'q': 107 | m_mod_log(mod, "I have to go now!\n"); 108 | m_mod_ps_publish(mod, "leaving", (unsigned char *)"ByeBye", 0); 109 | m_ctx_quit(0); 110 | break; 111 | default: 112 | /* Avoid newline */ 113 | if (c != 10) { 114 | m_mod_log(mod, "Unrecognized command. Beep. Please enter a new one... Totally not a bot.\n"); 115 | } 116 | break; 117 | } 118 | } 119 | }); 120 | } 121 | 122 | /* 123 | * Our B module's poll callback. 124 | */ 125 | static void B_recv(m_mod_t *mod, const m_queue_t *const evts) { 126 | m_itr_foreach(evts, { 127 | m_evt_t *msg = m_itr_get(m_itr); 128 | if (msg->type == M_SRC_TYPE_PS) { 129 | if (!strcmp((char *)msg->ps_evt->data, "ComeHere")) { 130 | m_mod_log(mod, "Running...\n"); 131 | m_mod_ps_tell(mod, msg->ps_evt->sender, (unsigned char *)"BauBau", 0); 132 | } else if (!strcmp((char *)msg->ps_evt->data, "LetsPlay")) { 133 | m_mod_log(mod, "BauBau BauuBauuu!\n"); 134 | } else if (!strcmp((char *)msg->ps_evt->data, "LetsEat")) { 135 | m_mod_log(mod, "Burp!\n"); 136 | } else if (!strcmp((char *)msg->ps_evt->data, "LetsSleep")) { 137 | m_mod_become(mod, B_recv_sleeping); 138 | m_mod_log(mod, "ZzzZzz...\n"); 139 | } else if (!strcmp((char *)msg->ps_evt->data, "ByeBye")) { 140 | m_mod_log(mod, "Sob...\n"); 141 | } else if (!strcmp((char *)msg->ps_evt->data, "WakeUp")) { 142 | m_mod_log(mod, "???\n"); 143 | } 144 | } 145 | }); 146 | } 147 | 148 | static void B_recv_sleeping(m_mod_t *mod, const m_queue_t *const evts) { 149 | m_itr_foreach(evts, { 150 | m_evt_t *msg = m_itr_get(m_itr); 151 | if (msg->type == M_SRC_TYPE_PS) { 152 | if (!strcmp((char *)msg->ps_evt->data, "WakeUp")) { 153 | m_mod_unbecome(mod); 154 | m_mod_log(mod, "Yawn...\n"); 155 | } else { 156 | m_mod_log(mod, "ZzzZzz...\n"); 157 | } 158 | } 159 | }); 160 | } 161 | -------------------------------------------------------------------------------- /Samples/Task/README.md: -------------------------------------------------------------------------------- 1 | # Task Example 2 | 3 | This super small example shows `m_src_task_t` event source usage. 4 | It allows to start a thread and being notified through libmodule when it finishes. -------------------------------------------------------------------------------- /Samples/Task/pippo.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | M_MOD("Pippo"); 8 | 9 | static int thData; 10 | static int tmrData; 11 | 12 | static int inc(void *udata); 13 | 14 | /* Hook on m_ctx_pre_loop() weak symbol */ 15 | void m_ctx_pre_loop(int argc, char *argv[]) { 16 | printf("Task example starting.\n"); 17 | } 18 | 19 | /* Hook on m_ctx_post_loop() weak symbol */ 20 | void m_ctx_post_loop(int argc, char *argv[]) { 21 | printf("Task example ended.\n"); 22 | } 23 | 24 | static void m_mod_on_boot(void) { 25 | printf("Booting task example.\n"); 26 | } 27 | 28 | static bool m_mod_on_start(m_mod_t *mod) { 29 | m_mod_log(mod,"Starting data val: %d\n", thData); 30 | 31 | m_mod_src_register(mod, &((m_src_tmr_t) { CLOCK_MONOTONIC, (uint64_t)1 * 1000 * 1000 * 1000 }), 0, &tmrData); // 1s 32 | m_mod_src_register(mod, &((m_src_task_t) { 8, inc }), 0, &thData); 33 | m_mod_set_batch_timeout(mod, 1500); // 1500ms! 34 | return true; 35 | } 36 | 37 | static bool m_mod_on_eval(m_mod_t *mod) { 38 | return true; 39 | } 40 | 41 | static void m_mod_on_stop(m_mod_t *mod) { 42 | 43 | } 44 | 45 | static void m_mod_on_evt(m_mod_t *mod, const m_queue_t *const evts) { 46 | m_itr_foreach(evts, { 47 | m_evt_t *msg = m_itr_get(m_itr); 48 | switch (msg->type) { 49 | case M_SRC_TYPE_TMR: { 50 | int *data = (int *)msg->userdata; 51 | if (*data == 5) { 52 | m_mod_log(mod, "Timed out.\n"); 53 | m_ctx_quit(0); 54 | m_mod_log(mod,"Final data val: %d\n", thData); 55 | } else { 56 | (*data)++; 57 | m_mod_log(mod, "Clock... %d\n", *data); 58 | } 59 | break; 60 | } 61 | case M_SRC_TYPE_TASK: 62 | m_mod_log(mod,"Task id: %u ended with retval: %d\n", msg->task_evt->tid, msg->task_evt->retval); 63 | break; 64 | default: 65 | break; 66 | } 67 | }); 68 | } 69 | 70 | static int inc(void *udata) { 71 | int *d = (int *)udata; 72 | while (*d < 3) { 73 | (*d)++; 74 | sleep(1); 75 | } 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /Samples/Thpool/README.md: -------------------------------------------------------------------------------- 1 | # Thread Pool Example 2 | 3 | This example shows how to use libmodule thpool API. -------------------------------------------------------------------------------- /Samples/Thpool/main.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define NUM_THREADS 8 8 | #define NUM_JOBS 64 9 | 10 | static void *print(void *udata); 11 | 12 | int main() { 13 | /* Create 8 threads, with unlimited number of jobs */ 14 | m_thpool_t *pool = m_thpool_new(NUM_THREADS, 0); 15 | if (pool) { 16 | for (int i = 0; i < NUM_JOBS; i++) { 17 | char name[50] = {0}; 18 | snprintf(name, sizeof(name) - 1, "Hello from job %d", i); 19 | m_thpool_add(pool, print, strdup(name)); 20 | } 21 | } 22 | m_thpool_free(&pool, true); 23 | return 0; 24 | } 25 | 26 | static void *print(void *udata) { 27 | char *str = (char *)udata; 28 | printf("%s\n", str); 29 | free(udata); 30 | return NULL; 31 | } 32 | -------------------------------------------------------------------------------- /Samples/testModule.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | bool m_mod_on_start(m_mod_t *mod) { 5 | m_mod_src_register(mod, "leaving", 0, NULL); 6 | m_mod_log(mod, "Linked.\n"); 7 | return true; 8 | } 9 | 10 | bool m_mod_on_eval(m_mod_t *mod) { 11 | return true; 12 | } 13 | 14 | void m_mod_on_stop(m_mod_t *mod) { 15 | 16 | } 17 | 18 | void m_mod_on_evt(m_mod_t *mod, const m_evt_t *msg) { 19 | if (msg->type == M_SRC_TYPE_PS) { 20 | if (!strcmp((char *)msg->ps_evt->data, "ByeBye")) { 21 | m_mod_log(mod, "Received quit.\n"); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## 6.0.0 2 | 3 | ### TODO 4 | 5 | #### DOC 6 | 7 | - [x] Fully rewrite documentation per-namespace 8 | - [x] Document that m_ctx_deregister() cannot be called on a looping context (`M_PARAM_ASSERT(c && *c && (*c)->state == M_CTX_IDLE);`) 9 | - [ ] document m_evt_t memref'd behaviour!!! 10 | - [ ] Document stats and thresh activity_freq (num_action_per_ms) 11 | 12 | ## 6.1.0 (7.0.0?) 13 | 14 | ### Liburing 15 | 16 | - [ ] Follow progress and possibly make some fixes 17 | - [ ] Fix examples: 18 | - [ ] Easy sample goes mad with STDIN_FILENO registered (it requires "enter" to be pressed before receiving any event) 19 | - [ ] Poll sample does not work 20 | 21 | ### Thread-safe (?) 22 | 23 | https://www.gnu.org/software/libc/manual/html_node/Pipe-Atomicity.html 24 | 25 | - [ ] Improve multithread support 26 | - [ ] As writing an address in a pipe is atomic, may be each call on a module can be an "op" written to its pipe 27 | - [ ] All modules have a cmd_piped_fd; calling eg: m_mod_become(X) would internally be translated to "m_cmd_enqueue(M_BECOME, X)" and that would be atomic; 28 | then module reads from cmd_piped_fd and executes the requested op 29 | - [ ] need a way to map m_mod_ API arguments though 30 | - [ ] offer an api to run module's recv on their own thread (register flag: M_MOD_THREADED), it means their receive() will be run async (in a thpool) 31 | 32 | ### Replay API 33 | 34 | - [ ] Publish to require a m_mem_t. Thus we have access to object size. 35 | - [ ] add a m_replay() api to replay any message received in a previous run; M_SRC_TYPE_FD cannot be stored... 36 | - [ ] add a m_evt_t flag that tells if we are running from a replay 37 | - [ ] add a "--m.replay-from" cmdline switch to default main 38 | - [ ] Basic flow: normally start program and when start looping, before actually polling, flush all messages loaded from file/db 39 | - [ ] Then, if "--m.store" is enabled, store any message received while looping 40 | 41 | ### Generic 42 | 43 | - [ ] Fix m_src_flags with 64b values (right now there is no value over 32b thus it is not a real issue) 44 | 45 | ## Ideas 46 | 47 | ### Srcs 48 | 49 | - [ ] new kernel hooks (like uretprobe/uprobe//kprobe/tracepoint) src? 50 | - [ ] new remote src? (like a webhook?) 51 | 52 | ### Remote API 53 | 54 | - [ ] M_MOD_REMOTE_SRV(name, port, certificate) to create a remote module that listens on port X eventually with key Y 55 | - [ ] M_MOD_REMOTE_CL(name, ip:port, certificate) to create a module that wraps a remote module and acts as a router 56 | - [ ] Use https://github.com/babelouest/ulfius ? (can a fd be fetched from it and polled async in a context's loop (as an additional module fd)?) 57 | 58 | ### Submodules 59 | 60 | - [ ] M_SUBM(B, A) checks if A is registered, then registers B and calls m_mod_bind_to(A); 61 | - [ ] Else, stores "B" in a map <"A","B"> to be later retrieved while registering "A" 62 | - [ ] deregister children at parent deregister 63 | - [ ] bind children to parent states (ie: parent paused -> children paused; parent resumed -> children resumed...) 64 | - [ ] m_forward -> like m_tell but to all children 65 | -------------------------------------------------------------------------------- /cmake/FindCmocka.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find CMocka 2 | # Once done this will define 3 | # 4 | # CMOCKA_ROOT_DIR - Set this variable to the root installation of CMocka 5 | # 6 | # Read-Only variables: 7 | # CMOCKA_FOUND - system has CMocka 8 | # CMOCKA_INCLUDE_DIR - the CMocka include directory 9 | # CMOCKA_LIBRARIES - Link these to use CMocka 10 | # CMOCKA_DEFINITIONS - Compiler switches required for using CMocka 11 | # 12 | #============================================================================= 13 | # Copyright (c) 2011-2012 Andreas Schneider 14 | # 15 | # Distributed under the OSI-approved BSD License (the "License"); 16 | # see accompanying file Copyright.txt for details. 17 | # 18 | # This software is distributed WITHOUT ANY WARRANTY; without even the 19 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 20 | # See the License for more information. 21 | #============================================================================= 22 | # 23 | 24 | set(_CMOCKA_ROOT_HINTS 25 | ) 26 | 27 | set(_CMOCKA_ROOT_PATHS 28 | "$ENV{PROGRAMFILES}/cmocka" 29 | ) 30 | 31 | find_path(CMOCKA_ROOT_DIR 32 | NAMES 33 | include/cmocka.h 34 | HINTS 35 | ${_CMOCKA_ROOT_HINTS} 36 | PATHS 37 | ${_CMOCKA_ROOT_PATHS} 38 | ) 39 | mark_as_advanced(CMOCKA_ROOT_DIR) 40 | 41 | find_path(CMOCKA_INCLUDE_DIR 42 | NAMES 43 | cmocka.h 44 | PATHS 45 | ${CMOCKA_ROOT_DIR}/include 46 | ) 47 | 48 | find_library(CMOCKA_LIBRARY 49 | NAMES 50 | cmocka 51 | PATHS 52 | ${CMOCKA_ROOT_DIR}/lib 53 | ) 54 | 55 | if (CMOCKA_LIBRARY) 56 | set(CMOCKA_LIBRARIES 57 | ${CMOCKA_LIBRARIES} 58 | ${CMOCKA_LIBRARY} 59 | ) 60 | endif (CMOCKA_LIBRARY) 61 | 62 | include(FindPackageHandleStandardArgs) 63 | find_package_handle_standard_args(Cmocka DEFAULT_MSG CMOCKA_LIBRARIES CMOCKA_INCLUDE_DIR) 64 | 65 | # show the CMOCKA_INCLUDE_DIR and CMOCKA_LIBRARIES variables only in the advanced view 66 | mark_as_advanced(CMOCKA_INCLUDE_DIR CMOCKA_LIBRARIES) 67 | -------------------------------------------------------------------------------- /cmake/FindSphinx.cmake: -------------------------------------------------------------------------------- 1 | # - This module looks for Sphinx 2 | # Find the Sphinx documentation generator 3 | # 4 | # This modules defines 5 | # SPHINX_EXECUTABLE 6 | # SPHINX_FOUND 7 | 8 | #============================================================================= 9 | # Copyright 2002-2009 Kitware, Inc. 10 | # Copyright 2009-2011 Peter Colberg 11 | # 12 | # Distributed under the OSI-approved BSD License (the "License"); 13 | # see accompanying file COPYING-CMAKE-SCRIPTS for details. 14 | # 15 | # This software is distributed WITHOUT ANY WARRANTY; without even the 16 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 | # See the License for more information. 18 | 19 | find_program(SPHINX_EXECUTABLE NAMES sphinx-build 20 | HINTS 21 | $ENV{SPHINX_DIR} 22 | PATH_SUFFIXES bin 23 | DOC "Sphinx documentation generator" 24 | ) 25 | 26 | include(FindPackageHandleStandardArgs) 27 | 28 | find_package_handle_standard_args(Sphinx DEFAULT_MSG 29 | SPHINX_EXECUTABLE 30 | ) 31 | 32 | mark_as_advanced( 33 | SPHINX_EXECUTABLE 34 | ) 35 | -------------------------------------------------------------------------------- /cmake/FindValgrind.cmake: -------------------------------------------------------------------------------- 1 | # Find Valgrind. 2 | # 3 | # This module defines: 4 | # VALGRIND_INCLUDE_DIR, where to find valgrind/memcheck.h, etc. 5 | # VALGRIND_PROGRAM, the valgrind executable. 6 | # VALGRIND_FOUND, If false, do not try to use valgrind. 7 | # 8 | # If you have valgrind installed in a non-standard place, you can define 9 | # VALGRIND_PREFIX to tell cmake where it is. 10 | 11 | find_path(VALGRIND_INCLUDE_DIR valgrind/memcheck.h 12 | /usr/include /usr/local/include ${VALGRIND_PREFIX}/include) 13 | find_program(VALGRIND_PROGRAM NAMES valgrind PATH 14 | /usr/bin /usr/local/bin ${VALGRIND_PREFIX}/bin) 15 | 16 | find_package_handle_standard_args(Valgrind DEFAULT_MSG 17 | VALGRIND_INCLUDE_DIR 18 | VALGRIND_PROGRAM) 19 | 20 | mark_as_advanced(VALGRIND_INCLUDE_DIR VALGRIND_PROGRAM) 21 | -------------------------------------------------------------------------------- /cmake/JoinPaths.cmake: -------------------------------------------------------------------------------- 1 | # This module provides function for joining paths 2 | # known from from most languages 3 | # 4 | # Original license: 5 | # SPDX-License-Identifier: (MIT OR CC0-1.0) 6 | # Explicit permission given to distribute this module under 7 | # the terms of the project as described in /LICENSE.rst. 8 | # Copyright 2020 Jan Tojnar 9 | # https://github.com/jtojnar/cmake-snips 10 | # 11 | # Modelled after Python’s os.path.join 12 | # https://docs.python.org/3.7/library/os.path.html#os.path.join 13 | # Windows not supported 14 | # Thanks to https://github.com/jtojnar/fmt author. 15 | function(join_paths joined_path first_path_segment) 16 | set(temp_path "${first_path_segment}") 17 | foreach(current_segment IN LISTS ARGN) 18 | if(NOT ("${current_segment}" STREQUAL "")) 19 | if(IS_ABSOLUTE "${current_segment}") 20 | set(temp_path "${current_segment}") 21 | else() 22 | set(temp_path "${temp_path}/${current_segment}") 23 | endif() 24 | endif() 25 | endforeach() 26 | set(${joined_path} "${temp_path}" PARENT_SCOPE) 27 | endfunction() 28 | -------------------------------------------------------------------------------- /docs/concepts/build.md: -------------------------------------------------------------------------------- 1 | # Build 2 | 3 | Libmodule supports multiple OSes: 4 | 5 | * Linux 6 | * freebsd 7 | * osx 8 | 9 | Non-portable code is compile-time-plugins based. 10 | On linux, libmodule's internal loop will use `epoll`, while on BSD and MacOS it will use `kqueue`. 11 | 12 | on linux, one can also enforce the usage of [`libkqueue`](https://github.com/mheily/libkqueue), a drop-in userspace replacement for kqueue; 13 | moreover, an experimental `liburing` backend is also available. 14 | 15 | Libmodule uses [`CMake`](https://cmake.org/) as a build tool. 16 | To build it, you just need to issue: 17 | ```shell 18 | mkdir build && cd build 19 | cmake -DCMAKE_BUILD_TYPE=Release .. 20 | ``` 21 | 22 | ## Generic Options 23 | 24 | Libmodule supports the following build switches: 25 | 26 | * `BUILD_TESTS` -> whether to build tests. Requires `cmocka` library. 27 | * `BUILD_SAMPLES` -> whether to build examples. 28 | * `BUILD_OOT_TEST` -> whether to build a small out of tree test, to check if libmodule can successfully get linked. The test is done during the `make install` target. 29 | 30 | ## Core library Options 31 | 32 | Libmodule core library supports the following build switches: 33 | 34 | * `WITH_FS` -> whether to enable a fuse FS layer. Requires `fuse3` library. 35 | * `WITH_LIBURING` (linux-only) -> whether to enable and use liburing poll plugin. 36 | * `WITH_LIBKQUEUE` (linux-only) -> whether to enable and use kqueue poll plugin using libkqueue library. 37 | -------------------------------------------------------------------------------- /docs/concepts/concepts.md: -------------------------------------------------------------------------------- 1 | # Concepts 2 | 3 | ## Naming Conventions 4 | 5 | Libmodule uses the following naming conventions for its public API: 6 | 7 | * `m_` is the prefix for all the library API 8 | * `m_foo_` APIs group together the same namespace API 9 | * struct types have the `_t` suffix 10 | * enum types **do not have** the `_t` suffix 11 | * `get`ters with no `set`ter, do not have the `get` prefix; for example: `m_ctx_userdata`. 12 | 13 | ## Logging 14 | 15 | Libmodule offers an internal logging facility, to enable verbose logging for the whole library. 16 | There are 4 log levels that can be enabled through `LIBMODULE_LOG` env variable: 17 | 18 | * "err" (**default**) 19 | * "warn" 20 | * "info" 21 | * "debug" 22 | 23 | Fine-grained control over logging contexts is enabled through the use of specific env variables; following are the supported contexts: 24 | 25 | * `LIBMODULE_LOG_MEM` 26 | * `LIBMODULE_LOG_STRUCTS` 27 | * `LIBMODULE_LOG_THPOOL` 28 | * `LIBMODULE_LOG_CORE` 29 | * `LIBMODULE_LOG_OTHER` 30 | 31 | Basically, you can set different logging level for each context; a default log level is set using the 32 | aforementioned `LIBMODULE_LOG` env variable (or **err** only by default). 33 | 34 | Moreover, you can specify an output file for the log, by passing `LIBMODULE_LOG_OUTPUT` env variable. 35 | By default, stdout/stderr are used. 36 | 37 | ## Memory 38 | 39 | ### Constructors 40 | 41 | Libmodule makes heavy usage of gcc `__attribute__((constructor))` (and destructor) to inizialize itself. 42 | Ctor order is specified in each namespace doc. 43 | 44 | ### Memhook 45 | 46 | Moreover, libmodule allows users to override default memhook used, by calling `m_set_memhook()`. 47 | This function must be called before any libmodule's allocation takes place, ie: before calling the first `m_ctx_register`. 48 | A memhook is just a wrapper around 3 main memory related functions: 49 | 50 | * `malloc` 51 | * `calloc` 52 | * `free` 53 | -------------------------------------------------------------------------------- /docs/concepts/ctx.md: -------------------------------------------------------------------------------- 1 | # Context 2 | 3 | A context can be seen as a collector for modules. You can loop on events from each context, and each context behaves independently from others. 4 | It is stored as a [thread specific object](https://linux.die.net/man/3/pthread_setspecific), created through `m_ctx_register` and deleted through `m_ctx_deregister`. 5 | This can be particularly useful when dealing with 2+ threads; each thread has its own module's context and thus its own events to be polled. 6 | Modules can only see and reach (through PubSub messaging) other modules from same context. 7 | A context is given a name at registration time. This is only useful for logging purposes. 8 | 9 | > NOTE: having multiple contexts with same name is allowed; given that each context is thread-specific, there will be no clash. 10 | > Of course, it's better to set different names. 11 | 12 | ## Loop 13 | 14 | Libmodule offers an internal loop, started with `m_ctx_loop()`; note that each context has its own loop. 15 | Moreover, you can even easily integrate it into your own loop: `m_ctx_fd()` will retrieve a pollable fd and POLLIN events will be raised whenever a new message is available. 16 | Remember that before starting your loop, `m_ctx_dispatch()` should be called, to dispatch initial "LoopStarted" messages to each module. 17 | Then, whenever POLLIN data is available on libmodule's fd, you only need to call `m_ctx_dispatch()` again. 18 | Finally, remember to `close()` libmodule's fd retrieved through `m_ctx_fd()`. 19 | 20 | ## Default main 21 | 22 | Libmodule offers a single-context looping main as a weak, thus overridable, symbol. 23 | This means that developers must not even create a main for simple applications. 24 | 25 | Default main is mostly useful in conjunction with `` API. 26 | It offers 2 functions that can be implemented by caller: 27 | 28 | * `m_ctx_pre_loop(m_ctx_t *c, int argc, char *argv[])` that is called before the ctx loop is started 29 | * `m_ctx_post_loop(m_ctx_t *c, int argc, char *argv[])` that is called after the loop stopped, right before leaving 30 | 31 | ## FS 32 | 33 | When built with `WITH_FS` enabled, ctx API will expose 2 additional functions: 34 | ```C 35 | int m_ctx_fs_set_root(m_ctx_t *c, const char *path); 36 | int m_ctx_fs_set_ops(m_ctx_t *c, const struct fuse_operations *ops); 37 | ``` 38 | By setting a context root path, a ctx will expose its structure as a fuse FS, where each module inside the context is a read-only file. 39 | one can then perform multiple read operations on the generated directory tree: 40 | 41 | * `open` and then `poll` module files to get notified whenever a message is received by the module 42 | * Read (`cat`) module files to get a nice overview of the module state (same as `m_mod_dump`) 43 | * Perform a couple of read IOCTLs on a module: 44 | * * `M_MOD_FS_STATE` to get a module state 45 | * * `M_MOD_FS_STATS` to get module stats 46 | 47 | The FS feature is specially useful to debug issues with module, 48 | because it allows a simple runtime inspection on a context and its modules state. 49 | -------------------------------------------------------------------------------- /docs/concepts/mod.md: -------------------------------------------------------------------------------- 1 | # Module 2 | 3 | A module is the core entity of libmodule: it is a single and indipendent logical unit that reacts to events. 4 | It can be seen as an Actor with the ability of managing non-PubSub events. 5 | It requires some callbacks that are used by libmodule to manage its life. 6 | 7 | ## Source 8 | 9 | Each module's listens to events by registering event sources. 10 | A source can be a timer, a topic (for PubSub communications between modules), a signal, a socket, etc etc. 11 | You can find the list of supported sources in ``. 12 | 13 | ### Source Priorities 14 | 15 | Users can also attach priorities to sources; 16 | these priorities are set as `m_src_flags` passed as source registration parameter. 17 | They are: 18 | 19 | * `M_SRC_PRIO_LOW` -> never notify events generated by the source alone; instead, notify them as soon as an higher prio event is received 20 | * `M_SRC_PRIO_NORM` (default if unspecified) -> notify events generated by the source only if either batch size or batch timeout are reached 21 | * `M_SRC_PRIO_HIGH` -> always notify events generated by the source immediately 22 | 23 | For `FD` sources, `M_SRC_PRIO_HIGH` is implicitly set, because you don't want to miss reading fd data, 24 | otherwise the ctx loop would ramp up cpu usage. 25 | 26 | ## Lifecycle 27 | 28 | ### Callbacks 29 | 30 | First of all, module lifecycle is automatically managed by libmodule; moreover, when using `` API, 31 | module registration/deregistration is completely automated and transparent to developer. 32 | This means that when using it, you will only have to declare a source file as a module and define needed functions. 33 | 34 | First function to be called is `m_on_boot()`; it is called right after libmodule gets linked. 35 | This function is useful to set a custom memhook for libmodule, through `m_set_memhook()` API. 36 | 37 | > **_EASY API_**: each module's `m_mod_on_boot()` function is called. At this stage, no module is registered yet. 38 | 39 | Finally, libmodule will register every module. 40 | Once a module is registered, it will be initially set to `M_MOD_IDLE` state. Idle means that the module is not started yet, thus it cannot receive any event. 41 | 42 | As soon as its context starts looping, `m_mod_on_eval()` function will be called on every idle module, trying to start it right away. 43 | That function will be called at each state machine change, for each idle module, until it returns true. 44 | 45 | As soon as `m_mod_on_eval()` returns true, a module is started. It means its `m_mod_on_start()` function is finally called and its state is set to M_MOD_RUNNING. 46 | When a module reaches RUNNING state, its context loop will finally receive events for its registered sources. 47 | 48 | Whenever an event triggers a module's wakeup, its `m_mod_on_evt()` callback can be called (depending on the event source priority) with a `m_queue_t` argument. 49 | 50 | Finally, when stopping a module, its `m_mod_on_stop()` function is called. 51 | Note that a module is automatically stopped during the process of module's deregistration. 52 | 53 | Thus, for Easy API, you should implement `m_mod_on_eval()` to return true when the module is ready to be started, 54 | then eventually register event sources in `m_mod_on_start()`, and manage events in `m_mod_on_evt()`. 55 | If you need to cleanup any data, manage it in `m_mod_on_stop()`. 56 | 57 | ### States 58 | 59 | As previously mentioned, a registered module, before being started, is in` M_MOD_IDLE` state. 60 | It means that it has never been started before; it won't receive any event thus its event callback will never be called. 61 | When module is started, thus reaching `M_MOD_RUNNING` state, all its registered sources will finally start to receive events. Sources registered while in `M_MOD_RUNNING` state are automatically polled. 62 | If a module is paused, reaching `M_MOD_PAUSED` state, it will stop polling on its event sources, but event sources will still be kept alive. Thus, as soon as module is resumed, all events received during paused state will trigger m_evt_cb. 63 | If a module gets stopped, reaching `M_MOD_STOPPED` state, it will destroy any registered source and it will be resetted to its initial state. 64 | If you instead wish to stop a module letting it manage any already-enqueued event, you need to send a _POISONPILL_ message to it, through `m_mod_poisonpill()` API. 65 | The difference between `M_MOD_IDLE` and `M_MOD_STOPPED` states is that idle modules will be evaluated to be started during context loop, while stopped modules won't. 66 | When a module is deregistered, it will reach a final `M_MOD_ZOMBIE` state. It means that the module is no more reachable neither usable, but it can still be referenced by any previously sent message (or any `m_mod_ref()`). 67 | After all module's ref count drops to 0 (ie: all sent messages are received by respective recipients and there are no pending `m_mod_unref()`) module will finally get destroyed and its memory freed. 68 | You can call only `m_mod_is()`, `m_mod_name()` and `m_mod_ctx()` on a zombie module. 69 | 70 | To summarize: 71 | 72 | * `m_mod_start()` needs to be called on an `M_MOD_IDLE` or `M_MOD_STOPPED` module. 73 | * `m_mod_pause()` needs to be called on a `M_MOD_RUNNING` module. 74 | * `m_mod_resume()` needs to be called on a `M_MOD_PAUSED` module. 75 | * `m_mod_stop()` needs to be called on a `M_MOD_RUNNING` or `M_MOD_PAUSED` module. 76 | -------------------------------------------------------------------------------- /docs/core/core.md: -------------------------------------------------------------------------------- 1 | # Libmodule core API 2 | 3 | Libmodule core API denotes the set of symbols exposed by `libmodule_core.so` library, whose headers are installed in `$includedir/module/`. 4 | 5 | It is made of multiple headers: 6 | 7 | * `mod.h` that contains module related API 8 | * `mod_easy.h` that contains an helper macro to build simplest applications, ie: single context, single module for each source file 9 | * `ctx.h` that contains context related API 10 | * `fs.h` (installed only if `WITH_FS` cmake option is enabled) that contains libmodule fuseFS related API 11 | * `cmn.h` that contains some common symbols, and cannot be manually included 12 | 13 | For the sake of readiness, function params where an output value will be stored, are marked with `OUT` (empty) macro. 14 | 15 | > **All the libmodule core API returns an errno-style negative error code, where left unspecified.** 16 | 17 | ## Memory 18 | 19 | ### Ref counted 20 | 21 | Ideally, all of the exposed pointers have their lifetime reference based. 22 | This means that you can call `m_mem_ref()` API to manage eg: `m_mod_t`, `m_ctx_t`, `m_evt_t` pointers, and so on. 23 | Normally, there is no such need because the library manages everything. 24 | But if you called `m_mod_ref()`, then you own a reference on that module and it's up to you to free the reference by calling `m_mem_unref()` on it. 25 | -------------------------------------------------------------------------------- /docs/core/ctx.md: -------------------------------------------------------------------------------- 1 | # Libmodule core/ctx API 2 | 3 | Ctx API denotes libmodule interface functions to manage contexts. 4 | It can be found under ``. 5 | 6 | > NOTE: there is no context handler visible to user, because the handler is basically the thread itself. 7 | > Trying to use the context API on a thread that has no context associated, will promptly return -EPIPE errno code. 8 | 9 | ## Types 10 | 11 | ```C 12 | typedef enum { 13 | M_CTX_NAME_DUP = 1 << 0, // Should ctx's name be strdupped 14 | M_CTX_NAME_AUTOFREE = 1 << 1, // Should ctx's name be autofreed 15 | M_CTX_PERSIST = 1 << 2, // Prevent ctx automatic destroying when there are no modules in it anymore. With this option, context is kept alive until m_ctx_deregister() is called. 16 | M_CTX_USERDATA_AUTOFREE = 1 << 3 // Automatically free ctx userdata upon deregister 17 | } m_ctx_flags; 18 | ``` 19 | > Bitmask of flags that can be passed to `m_ctx_register` function 20 | 21 | ```C 22 | typedef struct { 23 | double activity_freq; 24 | uint64_t total_idle_time; 25 | uint64_t total_looping_time; 26 | uint64_t recv_msgs; 27 | size_t num_modules; 28 | size_t running_modules; 29 | } m_ctx_stats_t; 30 | ``` 31 | > Returned stats about a context by `m_ctx_stats` function 32 | 33 | ```C 34 | typedef void (*m_log_cb)(const m_mod_t *ref, const char *fmt, va_list args); 35 | ``` 36 | > Log callback set through `m_ctx_set_logger` function 37 | 38 | ## Functions 39 | 40 | ```C 41 | int m_ctx_register(const char *ctx_name, m_ctx_flags flags, const void *userdata); 42 | ``` 43 | > Register a new ctx and associates it to current thread. 44 | 45 | **Params:** 46 | 47 | * `ctx_name`: name of the new ctx 48 | * `flags`: flags of the newly created ctx 49 | * `userdata`: user pointer stored in the ctx 50 | 51 | ```C 52 | int m_ctx_deregister(); 53 | ``` 54 | > Deregister the ctx associated with the current thread. 55 | > NOTE: this API cannot be called if the ctx is still looping. 56 | > Make sure to `m_ctx_quit` the loop before deregistering a context. 57 | > NOTE: unless a ctx is registered with `M_CTX_PERSIST` flag, it will get 58 | > automatically destroyed when there are no more modules registered in it. 59 | > NOTE: all of the modules in the context will be deregistered 60 | > when their context gets deregistered. 61 | 62 | ```C 63 | int m_ctx_set_logger(m_log_cb logger); 64 | ``` 65 | > Set a logger callback; otherwise, a default one is used. 66 | 67 | **Params:** 68 | 69 | * `logger`: new logger callback to be set 70 | 71 | ```C 72 | int m_ctx_loop(void); 73 | ``` 74 | > Loop a ctx in a blocking manner, until `m_ctx_quit` is called by any module. 75 | > NOTE: stopping a ctx is a blocking action: 76 | > all present events will be flushed to their modules, 77 | > and, in case any `M_SRC_TYPE_TASK` src is enabled, 78 | > its thread will be joined for a clean exit. 79 | 80 | **Returns:** `m_ctx_quit` quit_code. 81 | 82 | ```C 83 | int m_ctx_quit(uint8_t quit_code); 84 | ``` 85 | > Quit a ctx loop, returning given exit code. 86 | 87 | **Params:** 88 | 89 | * `quit_code`: quit value 90 | 91 | ```C 92 | int m_ctx_fd(void); 93 | ``` 94 | > Retrieve ctx pollable file descriptor. 95 | 96 | **Returns:** ctx fd. 97 | 98 | ```C 99 | int m_ctx_dispatch(void); 100 | ``` 101 | > Dispatch events from the context. 102 | > Useful when ctx fd is embedded in another loop. 103 | > First time it gets called, it will start the loop; 104 | > then, consecutive calls will dispatch ctx events. 105 | > Finally, after `m_ctx_quit` has been called, 106 | > it will notify the ctx to stop. 107 | > NOTE: stopping a ctx is a blocking action: 108 | > all present events will be flushed to their modules, 109 | > and, in case any `M_SRC_TYPE_TASK` src is enabled, 110 | > it will join its thread. 111 | 112 | **Returns:** first time it is called: errno-style negative error code. 113 | Subsequent calls: number of messages dispatched. 114 | Last time (after a `m_ctx_quit` call): errno-style negative error code. 115 | 116 | ```C 117 | int m_ctx_dump(void); 118 | ``` 119 | > Dump a json of the current ctx state. 120 | 121 | ```C 122 | int m_ctx_stats(m_ctx_stats_t *stats); 123 | ``` 124 | > Retrieve stats about current ctx state. 125 | 126 | ```C 127 | const char *m_ctx_name(void); 128 | ``` 129 | > Retrieve ctx name. 130 | 131 | ```C 132 | const void *m_ctx_userdata(void); 133 | ``` 134 | > Retrieve ctx userdata as set at registration time. 135 | 136 | ```C 137 | ssize_t m_ctx_len(void); 138 | ``` 139 | > Retrieve number of registered modules. 140 | 141 | ```C 142 | int m_ctx_finalize(void); 143 | ``` 144 | > Set the context as finalized, denying any subsequent module registeration. 145 | 146 | ```C 147 | int m_ctx_set_tick(uint64_t ns); 148 | ``` 149 | > Set a context tick. You can subscribe modules to `M_PS_CTX_TICK` system topic to receive tick events. 150 | 151 | **Params:** 152 | 153 | * `ns`: nanoseconds for the tick 154 | 155 | ### Only when built with `WITH_FS` build option 156 | 157 | ```C 158 | int m_ctx_fs_set_root(const char *path); 159 | ``` 160 | > Set the context FS root. Must be set before the ctx loop is started. 161 | 162 | **Params:** 163 | 164 | * `path`: FS root path. NULL to disable FUSE fs. 165 | 166 | ```C 167 | int m_ctx_fs_set_ops(const struct fuse_operations *ops); 168 | ``` 169 | > Set specified FUSE operations to context. Must be set before the ctx loop is started. 170 | > NOTE: module files will always be created readonly. 171 | 172 | **Params:** 173 | 174 | * `ops`: fuse operations. NULL to reset default ops. 175 | -------------------------------------------------------------------------------- /docs/core/mod.md: -------------------------------------------------------------------------------- 1 | # Libmodule core/mod API 2 | 3 | Mod API denotes libmodule interface functions to manage modules. 4 | It can be found in `` header. 5 | 6 | > **All the mod API expects a non-NULL mod handler**, except for ... functions. 7 | 8 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Home of Libmodule Documentation 2 | 3 | Libmodule offers a small and simple C implementation of an actor library that aims to let developers easily create modular C projects in a way which is both simple and elegant. 4 | 5 | --- 6 | 7 | Module is the core entity of libmodule architecture. 8 | A module is an Actor that can listen on non-pubsub events too. 9 | 10 | Here is a short example of a module: 11 | ```C 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | M_MOD("Pippo"); 19 | 20 | static bool m_mod_on_eval(mod_t *mod) { 21 | /* Should module be started? */ 22 | return true; 23 | } 24 | 25 | static bool m_mod_on_start(mod_t *mod) { 26 | /* Register STDIN fd */ 27 | m_mod_src_register(mod, STDIN_FILENO, 0, NULL); 28 | return true; 29 | } 30 | 31 | static void m_mod_on_stop(mod_t *mod) { 32 | 33 | } 34 | 35 | static void m_mod_on_evt(m_mod_t *mod, const m_queue_t *const evts) { 36 | m_itr_foreach(evts, { 37 | m_evt_t *msg = m_itr_get(m_itr); 38 | switch (msg->type) { 39 | case M_SRC_TYPE_FD: { 40 | char c; 41 | read(msg->fd_evt->fd, &c, sizeof(char)); 42 | switch (tolower(c)) { 43 | case 'q': 44 | m_mod_log(mod, "Leaving...\n"); 45 | m_mod_tell(mod, mod, "ByeBye", 0); 46 | break; 47 | default: 48 | if (c != ' ' && c != '\n') { 49 | m_mod_log(mod, "Pressed '%c'\n", c); 50 | } 51 | break; 52 | } 53 | break; 54 | } 55 | case M_SRC_TYPE_PS: 56 | if (strcmp((char *)msg->ps_evt->data, "ByeBye") == 0) { 57 | m_mod_log("Bye\n"): 58 | m_ctx_quit(0); 59 | } 60 | break; 61 | } 62 | } 63 | } 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/mem/mem.md: -------------------------------------------------------------------------------- 1 | # Libmodule mem API 2 | 3 | Mem API denotes symbols exposed by `libmodule_mem.so`, that refer to the memory-ref'd concept. 4 | It can be found in `` header. 5 | 6 | ## API 7 | 8 | ```C 9 | typedef void (*m_ref_dtor)(void *); 10 | 11 | void *m_mem_new(size_t size, m_ref_dtor dtor); 12 | void *m_mem_ref(void *src); 13 | void *m_mem_unref(void *src); 14 | void m_mem_unrefp(void **src); 15 | size_t m_mem_size(void *src); 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/structs/bst.md: -------------------------------------------------------------------------------- 1 | # BST 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /docs/structs/list.md: -------------------------------------------------------------------------------- 1 | # List 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /docs/structs/map.md: -------------------------------------------------------------------------------- 1 | # Map 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /docs/structs/queue.md: -------------------------------------------------------------------------------- 1 | # Queue 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /docs/structs/stack.md: -------------------------------------------------------------------------------- 1 | # Stack 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /docs/structs/structs.md: -------------------------------------------------------------------------------- 1 | # Data structures 2 | 3 | Structs API denotes symbols exposed by `libmodule_structs.so`, that expose multiple data structures. 4 | It can be found in `$includedir/module/structs/`. 5 | It is made up of multiple separate headers: 6 | * `` 7 | * `` 8 | * `` 9 | * `` 10 | * `` 11 | * `` 12 | -------------------------------------------------------------------------------- /docs/thpool/thpool.md: -------------------------------------------------------------------------------- 1 | # Thpool 2 | 3 | Thpool API denotes symbols exposed by `libmodule_thpool.so`, that refer to the thread pool library. 4 | It can be found in `` header. 5 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Libmodule Documentation 2 | site_description: C simple and elegant implementation of an Actor library 3 | site_author: Federico Di Pierro 4 | repo_url: https://github.com/FedeDP/libmodule/ 5 | 6 | nav: 7 | - Home: index.md 8 | - Concepts: 9 | - concepts/concepts.md 10 | - Build: concepts/build.md 11 | - Module: concepts/mod.md 12 | - Context: concepts/ctx.md 13 | - Core API: 14 | - core/core.md 15 | - Module API: core/mod.md 16 | - Context API: core/ctx.md 17 | - Data Structures API: 18 | - structs/structs.md 19 | - Map: structs/map.md 20 | - List: structs/list.md 21 | - Queue: structs/queue.md 22 | - Stack: structs/stack.md 23 | - Bst: structs/bst.md 24 | - Reference counted memory: mem/mem.md 25 | - Thread Pool API: thpool/thpool.md 26 | 27 | theme: readthedocs 28 | 29 | markdown_extensions: 30 | - toc: 31 | permalink: "#" 32 | - sane_lists 33 | - smarty 34 | 35 | plugins: 36 | - search 37 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") 4 | 5 | find_package(CMocka REQUIRED) 6 | 7 | file(GLOB SOURCES *.c) 8 | 9 | add_executable(ModuleTest ${SOURCES}) 10 | add_dependencies(ModuleTest ModuleTest) 11 | add_test(ModuleTest ModuleTest) 12 | 13 | option(WITH_VALGRIND "check tests with valgrind" ON) 14 | if(WITH_VALGRIND) 15 | find_package(Valgrind) 16 | if(VALGRIND_FOUND) 17 | message(STATUS "Tests will be checked with valgrind.") 18 | add_test(ModuleTest_valgrind valgrind 19 | --error-exitcode=1 --read-var-info=yes 20 | --leak-check=full --show-leak-kinds=all 21 | ./ModuleTest) 22 | else() 23 | message(STATUS "Valgrind unavailable. Tests won't be checked with it.") 24 | endif() 25 | endif() 26 | 27 | target_include_directories(ModuleTest PRIVATE ${CMOCKA_INCLUDE_DIR} ${PROJECT_SOURCE_DIR}/Lib/core/public ${PROJECT_SOURCE_DIR}/Lib/structs/public ${PROJECT_SOURCE_DIR}/Lib/thpool/public ${PROJECT_SOURCE_DIR}/Lib/mem/public) 28 | target_link_libraries(ModuleTest ${CMOCKA_LIBRARIES} ${PROJECT_NAME}_core) 29 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Libmodule Tests 2 | 3 | Libmodule is unit-tested through cmocka; here you can find all tests. 4 | 5 | * test_module.c tests module.h API 6 | * test_modules.c tests modules.h API 7 | 8 | These tests are automatically executed and valgrind tested on travis ci after every commit. 9 | 10 | ## Build and test 11 | 12 | To build tests, you need to issue a: 13 | 14 | $ cmake -DBUILD_TESTS=true ../ 15 | 16 | To run them, you need cmocka and valgrind, then, from libmodule/build folder, issue: 17 | 18 | $ ctest -V 19 | -------------------------------------------------------------------------------- /tests/test_bst.c: -------------------------------------------------------------------------------- 1 | #include "test_bst.h" 2 | #include 3 | 4 | static m_bst_t *my_t; 5 | static int arr[100]; 6 | 7 | static int int_cmp(void *userdata, void *node_data) { 8 | int a = *((int *)userdata); 9 | int b = *((int *)node_data); 10 | return (a - b); 11 | } 12 | 13 | static int traverse_pre_cb(void *userptr, void *node_data) { 14 | static int counter = 0; 15 | int val = *((int *) node_data); 16 | printf("%d\n", val); 17 | if (++counter == 5) { 18 | return 5; // check that 5 is not forwarded but traverse is stopped 19 | } 20 | return 0; 21 | } 22 | 23 | static int traverse_post_cb(void *userptr, void *node_data) { 24 | int val = *((int *) node_data); 25 | printf("%d\n", val); 26 | return -1; // check that -1 is forwarded 27 | } 28 | 29 | static int traverse_in_cb(void *userptr, void *node_data) { 30 | int val = *((int *) node_data); 31 | printf("%d\n", val); 32 | return 0; 33 | } 34 | 35 | void test_bst_insert(void **state) { 36 | (void) state; /* unused */ 37 | 38 | int ret = m_bst_insert(my_t, &arr[0]); 39 | assert_false(ret == 0); 40 | 41 | my_t = m_bst_new(int_cmp, NULL); 42 | 43 | /* NULL value */ 44 | ret = m_bst_insert(my_t, NULL); 45 | assert_false(ret == 0); 46 | 47 | srand(time(NULL)); 48 | for (int i = 0; i < 100; i++) { 49 | do { 50 | arr[i] = rand() % 10000; 51 | } 52 | while (m_bst_insert(my_t, &arr[i]) != 0); 53 | } 54 | } 55 | 56 | void test_bst_length(void **state) { 57 | (void) state; /* unused */ 58 | 59 | int len = m_bst_len(NULL); 60 | assert_false(len > 0); 61 | 62 | len = m_bst_len(my_t); 63 | assert_int_equal(len, 100); 64 | } 65 | 66 | void test_bst_find(void **state) { 67 | (void) state; /* unused */ 68 | 69 | void *data = m_bst_find(NULL, NULL); 70 | assert_null(data); 71 | 72 | data = m_bst_find(my_t, NULL); 73 | assert_null(data); 74 | 75 | int c = -1; 76 | data = m_bst_find(my_t, &c); 77 | assert_null(data); 78 | 79 | data = m_bst_find(my_t, &arr[2]); 80 | assert_non_null(data); 81 | assert_ptr_equal(data, &arr[2]); 82 | 83 | c = arr[1]; 84 | data = m_bst_find(my_t, &c); 85 | assert_non_null(data); 86 | assert_ptr_equal(data, &arr[1]); 87 | } 88 | 89 | void test_bst_remove(void **state) { 90 | (void) state; /* unused */ 91 | 92 | int ret = m_bst_remove(NULL, NULL); 93 | assert_false(ret == 0); 94 | 95 | ret = m_bst_remove(my_t, NULL); 96 | assert_false(ret == 0); 97 | 98 | ret = m_bst_remove(my_t, &arr[5]); 99 | assert_int_equal(ret, 0); 100 | 101 | ret = m_bst_remove(my_t, &arr[25]); 102 | assert_int_equal(ret, 0); 103 | 104 | ret = m_bst_remove(my_t, &arr[45]); 105 | assert_int_equal(ret, 0); 106 | 107 | ret = m_bst_remove(my_t, &arr[65]); 108 | assert_int_equal(ret, 0); 109 | 110 | ret = m_bst_remove(my_t, &arr[85]); 111 | assert_int_equal(ret, 0); 112 | 113 | size_t len = m_bst_len(my_t); 114 | assert_int_equal(len, 95); 115 | } 116 | 117 | void test_bst_iterator(void **state) { 118 | (void) state; /* unused */ 119 | 120 | /* NULL list */ 121 | m_bst_itr_t *it = m_bst_itr_new(NULL); 122 | assert_null(it); 123 | 124 | int count = m_bst_len(my_t); 125 | m_itr_foreach(my_t, { 126 | int *val = m_itr_get(m_itr); 127 | printf("%d\n", *val); 128 | if (rand() % 2 == 1) { 129 | m_itr_rm(m_itr); 130 | } 131 | count--; 132 | }); 133 | assert_int_equal(count, 0); 134 | } 135 | 136 | void test_bst_traverse(void **state) { 137 | (void) state; /* unused */ 138 | 139 | int ret = m_bst_traverse(NULL, M_BST_PRE, traverse_pre_cb, NULL); 140 | assert_false(ret == 0); 141 | 142 | ret = m_bst_traverse(my_t, -1, traverse_pre_cb, NULL); 143 | assert_false(ret == 0); 144 | 145 | ret = m_bst_traverse(my_t, -1, NULL, NULL); 146 | assert_false(ret == 0); 147 | 148 | printf("PREORDER (only first 5):\n"); 149 | ret = m_bst_traverse(my_t, M_BST_PRE, traverse_pre_cb, NULL); 150 | assert_int_equal(ret, 0); 151 | 152 | printf("POSTORDER (only first):\n"); 153 | ret = m_bst_traverse(my_t, M_BST_POST, traverse_post_cb, NULL); 154 | assert_int_equal(ret, -1); 155 | 156 | printf("INORDER:\n"); 157 | ret = m_bst_traverse(my_t, M_BST_IN, traverse_in_cb, NULL); 158 | assert_int_equal(ret, 0); 159 | } 160 | 161 | void test_bst_clear(void **state) { 162 | (void) state; /* unused */ 163 | 164 | int ret = m_bst_clear(NULL); 165 | assert_false(ret == 0); 166 | 167 | ret = m_bst_clear(my_t); 168 | assert_true(ret == 0); 169 | 170 | int len = m_bst_len(my_t); 171 | assert_int_equal(len, 0); 172 | } 173 | 174 | void test_bst_free(void **state) { 175 | (void) state; /* unused */ 176 | 177 | int ret = m_bst_free(NULL); 178 | assert_false(ret == 0); 179 | 180 | ret = m_bst_free(&my_t); 181 | assert_true(ret == 0); 182 | assert_null(my_t); 183 | } 184 | -------------------------------------------------------------------------------- /tests/test_bst.h: -------------------------------------------------------------------------------- 1 | #include "test_commons.h" 2 | #include 3 | #include 4 | #include 5 | 6 | void test_bst_insert(void **state); 7 | void test_bst_length(void **state); 8 | void test_bst_find(void **state); 9 | void test_bst_remove(void **state); 10 | void test_bst_iterator(void **state); 11 | void test_bst_traverse(void **state); 12 | void test_bst_clear(void **state); 13 | void test_bst_free(void **state); 14 | -------------------------------------------------------------------------------- /tests/test_commons.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | extern m_mod_t *test_mod; 11 | -------------------------------------------------------------------------------- /tests/test_ctx.c: -------------------------------------------------------------------------------- 1 | #include "test_ctx.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define CTX "testCtx" 8 | 9 | static void logger(const m_mod_t *self, const char *fmt, va_list args) { 10 | const char *name = NULL; 11 | const char *context = NULL; 12 | if (self) { 13 | name = m_mod_name(self); 14 | context = m_ctx_name(); 15 | } 16 | printf("%s@%s:\t* ", name ? name : "null", context ? context : "null"); 17 | vprintf(fmt, args); 18 | } 19 | 20 | void test_ctx_register_NULL_name(void **state) { 21 | (void) state; /* unused */ 22 | 23 | int ret = m_ctx_register(NULL, 0, NULL); 24 | assert_false(ret == 0); 25 | } 26 | 27 | void test_ctx_register(void **state) { 28 | (void) state; /* unused */ 29 | 30 | int ret = m_ctx_register(CTX, M_CTX_PERSIST, NULL); 31 | assert_true(ret == 0); 32 | } 33 | 34 | void test_ctx_deregister(void **state) { 35 | (void) state; /* unused */ 36 | 37 | int ret = m_ctx_deregister(); 38 | assert_int_equal(ret, 0); 39 | } 40 | 41 | void test_ctx_set_logger_NULL_logger(void **state) { 42 | (void) state; /* unused */ 43 | 44 | int ret = m_ctx_set_logger(NULL); 45 | assert_false(ret == 0); 46 | } 47 | 48 | void test_ctx_set_logger(void **state) { 49 | (void) state; /* unused */ 50 | 51 | int ret = m_ctx_set_logger(logger); 52 | assert_true(ret == 0); 53 | } 54 | 55 | void test_ctx_dump(void **state) { 56 | (void) state; /* unused */ 57 | 58 | int ret = m_ctx_dump(); 59 | assert_true(ret == 0); 60 | } 61 | 62 | void test_ctx_quit_no_loop(void **state) { 63 | (void) state; /* unused */ 64 | 65 | int ret = m_ctx_quit(0); 66 | assert_false(ret == 0); 67 | } 68 | 69 | void test_ctx_loop(void **state) { 70 | (void) state; /* unused */ 71 | 72 | int ret = m_ctx_loop(); 73 | assert_true(ret == 0); 74 | } 75 | 76 | static void recv_noop(m_mod_t *mod, const m_queue_t *const evts) { 77 | 78 | } 79 | 80 | void test_ctx_dispatch(void **state) { 81 | (void) state; /* unused */ 82 | 83 | int ret = m_mod_src_register(test_mod, M_PS_CTX_STARTED, M_SRC_ONESHOT, NULL); 84 | assert_true(ret == 0); 85 | 86 | ret = m_mod_become(test_mod, recv_noop); 87 | assert_true(ret == 0); 88 | 89 | ret = m_ctx_dispatch(); 90 | assert_true(ret == 0); // loop started 91 | 92 | ret = m_ctx_dispatch(); 93 | assert_int_equal(ret, 1); // number of messages dispatched: M_PS_CTX_STARTED. 94 | 95 | ret = m_ctx_quit(150); 96 | assert_true(ret == 0); 97 | 98 | ret = m_ctx_dispatch(); 99 | assert_int_equal(ret, 150); // loop stopped with exit code 150 100 | 101 | ret = m_mod_unbecome(test_mod); 102 | assert_true(ret == 0); 103 | } 104 | 105 | static void my_recv(m_mod_t *mod, const m_queue_t *const evts) { 106 | m_itr_foreach(evts, { 107 | m_evt_t *msg = m_itr_get(m_itr); 108 | if (msg->type == M_SRC_TYPE_PS && msg->ps_evt->topic && strcmp(msg->ps_evt->topic, M_PS_CTX_STARTED) == 0) { 109 | m_mod_deregister(&mod); 110 | } 111 | }); 112 | } 113 | 114 | void test_ctx_mod_deregister_during_loop(void **state) { 115 | (void) state; /* unused */ 116 | 117 | int ret = m_ctx_register("test", 0, NULL); 118 | assert_true(ret == 0); 119 | 120 | m_mod_hook_t hook = { .on_evt = my_recv }; 121 | m_mod_t *mod = NULL; 122 | ret = m_mod_register("testName", &mod, &hook, 0, NULL); 123 | assert_true(ret == 0); 124 | assert_non_null(mod); 125 | assert_true(m_mod_is(mod, M_MOD_IDLE)); 126 | 127 | /* Register the module to the desired system message */ 128 | m_mod_src_register(mod, M_PS_CTX_STARTED, M_SRC_ONESHOT, NULL); 129 | 130 | ret = m_ctx_loop(); 131 | assert_int_equal(ret, 0); 132 | } 133 | -------------------------------------------------------------------------------- /tests/test_ctx.h: -------------------------------------------------------------------------------- 1 | #include "test_commons.h" 2 | 3 | void test_ctx_register_NULL_name(void **state); 4 | void test_ctx_register(void **state); 5 | void test_ctx_deregister(void **state); 6 | void test_ctx_set_logger_NULL_logger(void **state); 7 | void test_ctx_set_logger(void **state); 8 | void test_ctx_dump(void **state); 9 | void test_ctx_quit_no_loop(void **state); 10 | void test_ctx_loop(void **state); 11 | void test_ctx_dispatch(void **state); 12 | void test_ctx_mod_deregister_during_loop(void **state); 13 | -------------------------------------------------------------------------------- /tests/test_evt_ref.c: -------------------------------------------------------------------------------- 1 | #include "test_evt_ref.h" 2 | #include 3 | #include 4 | #include 5 | 6 | static void my_recv(m_mod_t *mod, const m_queue_t *const evts); 7 | 8 | static m_mod_t *mod = NULL; 9 | static m_evt_t *ref; 10 | 11 | void test_evt_ref(void **state) { 12 | (void) state; /* unused */ 13 | 14 | int ret = m_ctx_register("evt_ref", 0, NULL); 15 | assert_true(ret == 0); 16 | 17 | m_mod_hook_t hook = { .on_evt = my_recv }; 18 | ret = m_mod_register("testName", &mod, &hook, 0, NULL); 19 | assert_true(ret == 0); 20 | assert_non_null(mod); 21 | assert_true(m_mod_is(mod, M_MOD_IDLE)); 22 | 23 | m_mod_start(mod); 24 | m_mod_ps_tell(mod, mod, "Hello World", 0); 25 | 26 | m_ctx_loop(); 27 | 28 | /* Test that ref event is not nil */ 29 | assert_non_null(ref); 30 | ref = m_mem_unref(ref); 31 | assert_null(ref); 32 | 33 | ret = m_mod_deregister(&mod); 34 | assert_int_equal(ret, 0); 35 | } 36 | 37 | static void my_recv(m_mod_t *mod, const m_queue_t *const evts) { 38 | m_itr_foreach(evts, { 39 | m_evt_t *msg = m_itr_get(m_itr); 40 | if (msg->type == M_SRC_TYPE_PS) { 41 | 42 | ref = m_mem_ref((void *)msg); 43 | m_ctx_quit(0); 44 | } 45 | }); 46 | } 47 | 48 | -------------------------------------------------------------------------------- /tests/test_evt_ref.h: -------------------------------------------------------------------------------- 1 | #include "test_commons.h" 2 | 3 | void test_evt_ref(void **state); 4 | -------------------------------------------------------------------------------- /tests/test_list.c: -------------------------------------------------------------------------------- 1 | #include "test_list.h" 2 | #include 3 | #include 4 | 5 | static m_list_t *my_l; 6 | static int val1 = 1; 7 | static int val2 = 2; 8 | static int val3 = 3; 9 | 10 | void test_list_insert(void **state) { 11 | (void) state; /* unused */ 12 | 13 | /* NULL map */ 14 | int ret = m_list_insert(my_l, &val1); 15 | assert_false(ret == 0); 16 | 17 | my_l = m_list_new(NULL, NULL); 18 | 19 | /* NULL value */ 20 | ret = m_list_insert(my_l, NULL); 21 | assert_false(ret == 0); 22 | 23 | ret = m_list_insert(my_l, &val1); 24 | assert_true(ret == 0); 25 | 26 | ret = m_list_insert(my_l, &val2); 27 | assert_true(ret == 0); 28 | 29 | ret = m_list_insert(my_l, &val3); 30 | assert_true(ret == 0); 31 | } 32 | 33 | void test_list_length(void **state) { 34 | (void) state; /* unused */ 35 | 36 | int len = m_list_len(NULL); 37 | assert_false(len > 0); 38 | 39 | len = m_list_len(my_l); 40 | assert_int_equal(len, 3); 41 | } 42 | 43 | void test_list_iterator(void **state) { 44 | (void) state; /* unused */ 45 | 46 | /* NULL list */ 47 | m_list_itr_t *itr = m_list_itr_new(NULL); 48 | assert_null(itr); 49 | 50 | itr = m_list_itr_new(my_l); 51 | assert_non_null(itr); 52 | 53 | int count = m_list_len(my_l); 54 | 55 | while (count) { 56 | count--; 57 | printf("%p\n", m_itr_get(itr)); 58 | 59 | /* Insert a node */ 60 | int ret = m_list_itr_insert(itr, &val1); 61 | assert_true(ret == 0); 62 | 63 | /* Remove previously inserted node */ 64 | ret = m_itr_rm(itr); 65 | assert_true(ret == 0); 66 | 67 | m_itr_next(&itr); 68 | } 69 | 70 | assert_int_equal(count, 0); 71 | assert_null(itr); 72 | } 73 | 74 | void test_list_find(void **state) { 75 | void *data = m_list_find(NULL, NULL); 76 | assert_null(data); 77 | 78 | data = m_list_find(my_l, NULL); 79 | assert_null(data); 80 | 81 | int c = 0; 82 | data = m_list_find(my_l, &c); 83 | assert_null(data); 84 | 85 | data = m_list_find(my_l, &val2); 86 | assert_non_null(data); 87 | assert_ptr_equal(data, &val2); 88 | } 89 | 90 | void test_list_remove(void **state) { 91 | (void) state; /* unused */ 92 | 93 | int ret = m_list_remove(NULL, NULL); 94 | assert_false(ret == 0); 95 | 96 | ret = m_list_remove(my_l, NULL); 97 | assert_false(ret == 0); 98 | 99 | ret = m_list_remove(my_l, &val1); 100 | assert_true(ret == 0); 101 | 102 | ret = m_list_remove(my_l, &val2); 103 | assert_true(ret == 0); 104 | 105 | int len = m_list_len(my_l); 106 | assert_int_equal(len, 1); // one element left, ie: val3 107 | } 108 | 109 | void test_list_clear(void **state) { 110 | (void) state; /* unused */ 111 | 112 | int ret = m_list_clear(NULL); 113 | assert_false(ret == 0); 114 | 115 | ret = m_list_clear(my_l); 116 | assert_true(ret == 0); 117 | 118 | int len = m_list_len(my_l); 119 | assert_int_equal(len, 0); 120 | } 121 | 122 | void test_list_free(void **state) { 123 | (void) state; /* unused */ 124 | 125 | int ret = m_list_free(NULL); 126 | assert_false(ret == 0); 127 | 128 | ret = m_list_free(&my_l); 129 | assert_true(ret == 0); 130 | assert_null(my_l); 131 | } 132 | 133 | static int int_match(void *my_data, void *list_data) { 134 | int a = *((int *)my_data); 135 | int b = *((int *)list_data); 136 | return !(a == b); 137 | } 138 | 139 | void test_list_int(void **state) { 140 | int ret; 141 | my_l = m_list_new(int_match, free); 142 | for (int i = 0; i < 10; i++) { 143 | int *p = malloc(sizeof(int)); 144 | *p = i; 145 | ret = m_list_insert(my_l, p); 146 | assert_true(ret == 0); 147 | } 148 | int len = m_list_len(my_l); 149 | assert_int_equal(len, 10); 150 | 151 | int val = 5; 152 | int *data = m_list_find(my_l, &val); 153 | assert_non_null(data); 154 | assert_int_equal(*data, 5); 155 | 156 | val = 7; 157 | ret = m_list_remove(my_l, &val); 158 | assert_int_equal(ret, 0); 159 | len = m_list_len(my_l); 160 | assert_int_equal(len, 9); 161 | 162 | val = 9; 163 | ret = m_list_remove(my_l, &val); 164 | assert_int_equal(ret, 0); 165 | len = m_list_len(my_l); 166 | assert_int_equal(len, 8); 167 | 168 | val = 10; 169 | ret = m_list_remove(my_l, &val); 170 | assert_false(ret == 0); // nonexistent! 171 | len = m_list_len(my_l); 172 | assert_int_equal(len, 8); 173 | 174 | ret = m_list_free(&my_l); 175 | assert_true(ret == 0); 176 | assert_null(my_l); 177 | } 178 | -------------------------------------------------------------------------------- /tests/test_list.h: -------------------------------------------------------------------------------- 1 | #include "test_commons.h" 2 | 3 | void test_list_insert(void **state); 4 | void test_list_length(void **state); 5 | void test_list_iterator(void **state); 6 | void test_list_find(void **state); 7 | void test_list_remove(void **state); 8 | void test_list_clear(void **state); 9 | void test_list_free(void **state); 10 | void test_list_int(void **state); -------------------------------------------------------------------------------- /tests/test_map.c: -------------------------------------------------------------------------------- 1 | #include "test_map.h" 2 | #include 3 | #include 4 | 5 | static m_map_t *my_map; 6 | static int val = 5; 7 | static int updated_val = 45; 8 | static int count; 9 | 10 | void test_map_put(void **state) { 11 | (void) state; /* unused */ 12 | 13 | /* NULL map */ 14 | int ret = m_map_put(my_map, "key", &val); 15 | assert_false(ret == 0); 16 | 17 | my_map = m_map_new(M_MAP_VAL_ALLOW_UPDATE, NULL); 18 | 19 | /* NULL value */ 20 | ret = m_map_put(my_map, "key", NULL); 21 | assert_false(ret == 0); 22 | 23 | /* NULL key */ 24 | ret = m_map_put(my_map, NULL, &val); 25 | assert_false(ret == 0); 26 | 27 | ret = m_map_put(my_map, "key", &val); 28 | assert_true(ret == 0); 29 | assert_true(m_map_contains(my_map, "key")); 30 | assert_int_equal(m_map_len(my_map), 1); 31 | 32 | /* Update val; check that map size was not increased */ 33 | ret = m_map_put(my_map, "key", &updated_val); 34 | assert_true(ret == 0); 35 | assert_int_equal(m_map_len(my_map), 1); 36 | 37 | /* Check that value was updated - M_MAP_VAL_ALLOW_UPDATE flag was passed */ 38 | int *v = m_map_get(my_map, "key"); 39 | assert_non_null(v); 40 | assert_int_equal(*v, updated_val); 41 | 42 | ret = m_map_put(my_map, "key2", &val); 43 | assert_true(ret == 0); 44 | assert_true(m_map_contains(my_map, "key2")); 45 | } 46 | 47 | void test_map_get(void **state) { 48 | (void) state; /* unused */ 49 | 50 | /* NULL map */ 51 | int *value = m_map_get(NULL, "key"); 52 | assert_null(value); 53 | 54 | /* NULL key */ 55 | value = m_map_get(my_map, NULL); 56 | assert_null(value); 57 | 58 | /* Unhexistent key */ 59 | value = m_map_get(my_map, "keykey"); 60 | assert_null(value); 61 | 62 | value = m_map_get(my_map, "key"); 63 | assert_non_null(value); 64 | assert_int_equal(*value, updated_val); 65 | } 66 | 67 | void test_map_length(void **state) { 68 | (void) state; /* unused */ 69 | 70 | int len = m_map_len(NULL); 71 | assert_false(len >= 0); 72 | 73 | len = m_map_len(my_map); 74 | assert_int_equal(len, 2); 75 | } 76 | 77 | void test_map_iterator(void **state) { 78 | (void) state; /* unused */ 79 | 80 | /* NULL map */ 81 | m_map_itr_t *itr = m_map_itr_new(NULL); 82 | assert_null(itr); 83 | 84 | itr = m_itr_new(my_map); 85 | assert_non_null(itr); 86 | 87 | count = m_map_len(my_map); 88 | while (itr) { 89 | count--; 90 | printf("%s -> %p\n", m_map_itr_get_key(itr), m_map_itr_get_data(itr)); 91 | m_itr_next(&itr); 92 | } 93 | assert_int_equal(count, 0); 94 | assert_null(itr); 95 | } 96 | 97 | int iterate_cb(void *userptr, const char *key, void *data) { 98 | count++; 99 | return 0; 100 | } 101 | 102 | void test_map_iterate(void **state) { 103 | (void) state; /* unused */ 104 | 105 | /* NULL map */ 106 | int ret = m_map_iterate(NULL, iterate_cb, NULL); 107 | assert_false(ret == 0); 108 | 109 | /* NULL cb */ 110 | ret = m_map_iterate(my_map, NULL, NULL); 111 | assert_false(ret == 0); 112 | 113 | ret = m_map_iterate(my_map, iterate_cb, NULL); 114 | assert_true(ret == 0); 115 | assert_int_equal(count, m_map_len(my_map)); 116 | } 117 | 118 | void test_map_remove(void **state) { 119 | (void) state; /* unused */ 120 | 121 | /* NULL map */ 122 | int ret = m_map_remove(NULL, "key"); 123 | assert_false(ret == 0); 124 | 125 | /* NULL key */ 126 | ret = m_map_remove(my_map, NULL); 127 | assert_false(ret == 0); 128 | 129 | /* NULL key */ 130 | ret = m_map_remove(my_map, "key"); 131 | assert_true(ret == 0); 132 | 133 | int len = m_map_len(my_map); 134 | assert_int_equal(len, 1); // "key2" still inside 135 | } 136 | 137 | void test_map_clear(void **state) { 138 | (void) state; /* unused */ 139 | 140 | /* NULL map */ 141 | int ret = m_map_clear(NULL); 142 | assert_false(ret == 0); 143 | 144 | ret = m_map_clear(my_map); 145 | assert_true(ret == 0); 146 | 147 | int len = m_map_len(my_map); 148 | assert_int_equal(len, 0); 149 | } 150 | 151 | void test_map_free(void **state) { 152 | (void) state; /* unused */ 153 | 154 | /* NULL map */ 155 | int ret = m_map_free(NULL); 156 | assert_false(ret == 0); 157 | 158 | ret = m_map_free(&my_map); 159 | assert_true(ret == 0); 160 | assert_null(my_map); 161 | } 162 | 163 | void test_map_stress(void **state) { 164 | (void) state; /* unused */ 165 | 166 | clock_t begin_tell = clock(); 167 | 168 | int ret; 169 | my_map = m_map_new(M_MAP_KEY_DUP, NULL); 170 | const int size = 1000000; 171 | for (int i = 0; i < size; i++) { 172 | char key[20]; 173 | snprintf(key, sizeof(key), "key%d", i); 174 | ret = m_map_put(my_map, key, "top"); 175 | assert_true(ret == 0); 176 | assert_true(m_map_contains(my_map, key)); 177 | } 178 | assert_int_equal(m_map_len(my_map), size); 179 | 180 | count = 0; 181 | ret = m_map_iterate(my_map, iterate_cb, NULL); 182 | assert_true(ret == 0); 183 | assert_int_equal(count, m_map_len(my_map)); 184 | 185 | ret = m_map_free(&my_map); 186 | assert_true(ret == 0); 187 | assert_null(my_map); 188 | 189 | clock_t end_tell = clock(); 190 | double time_spent = (double)(end_tell - begin_tell); 191 | printf("Map stress test took %.2lf us\n", time_spent); 192 | } 193 | -------------------------------------------------------------------------------- /tests/test_map.h: -------------------------------------------------------------------------------- 1 | #include "test_commons.h" 2 | 3 | void test_map_put(void **state); 4 | void test_map_get(void **state); 5 | void test_map_length(void **state); 6 | void test_map_iterator(void **state); 7 | void test_map_iterate(void **state); 8 | void test_map_remove(void **state); 9 | void test_map_clear(void **state); 10 | void test_map_free(void **state); 11 | void test_map_stress(void **state); 12 | -------------------------------------------------------------------------------- /tests/test_mem.c: -------------------------------------------------------------------------------- 1 | #include "test_mem.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void test_mem(void **state) { 9 | (void) state; /* unused */ 10 | 11 | srand((unsigned int)time(0)); 12 | /* 13 | * Check that allocating different block sizes 14 | * works fine without any alignment issue. 15 | */ 16 | for (int i = 0; i < alignof(max_align_t); i++) { 17 | int mx = rand() % 20; 18 | void *data = m_mem_new(mx * i, NULL); 19 | assert_non_null(data); 20 | m_mem_unrefp(&data); 21 | assert_null(data); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/test_mem.h: -------------------------------------------------------------------------------- 1 | #include "test_commons.h" 2 | 3 | void test_mem(void **state); -------------------------------------------------------------------------------- /tests/test_mod.h: -------------------------------------------------------------------------------- 1 | #include "test_commons.h" 2 | 3 | void test_mod_register_NULL_name(void **state); 4 | void test_mod_register(void **state); 5 | void test_mod_register_already_registered(void **state); 6 | void test_mod_register_same_name(void **state); 7 | void test_mod_deregister_NULL_self(void **state); 8 | void test_mod_deregister(void **state); 9 | void test_mod_false_init(void **state); 10 | void test_mod_pause_NULL_self(void **state); 11 | void test_mod_pause(void **state); 12 | void test_mod_resume_NULL_self(void **state); 13 | void test_mod_resume(void **state); 14 | void test_mod_stop_NULL_self(void **state); 15 | void test_mod_stop(void **state); 16 | void test_mod_start_NULL_self(void **state); 17 | void test_mod_start(void **state); 18 | void test_mod_log_NULL_self(void **state); 19 | void test_mod_log(void **state); 20 | void test_mod_dump_NULL_self(void **state); 21 | void test_mod_dump(void **state); 22 | void test_mod_become_NULL_self(void **state); 23 | void test_mod_become_NULL_func(void **state); 24 | void test_mod_become(void **state); 25 | void test_mod_unbecome_NULL_self(void **state); 26 | void test_mod_unbecome(void **state); 27 | void test_mod_add_wrong_fd(void **state); 28 | void test_mod_add_fd_NULL_self(void **state); 29 | void test_mod_add_fd(void **state); 30 | void test_mod_rm_wrong_fd(void **state); 31 | void test_mod_rm_wrong_fd_2(void **state); 32 | void test_mod_rm_fd_NULL_self(void **state); 33 | void test_mod_rm_fd(void **state); 34 | void test_mod_subscribe_NULL_topic(void **state); 35 | void test_mod_subscribe_NULL_self(void **state); 36 | void test_mod_subscribe(void **state); 37 | void test_mod_ref_NULL_name(void **state); 38 | void test_mod_ref_unexhistent_name(void **state); 39 | void test_mod_ref(void **state); 40 | void test_mod_unref_NULL_ref(void **state); 41 | void test_mod_unref(void **state); 42 | void test_mod_tell_NULL_recipient(void **state); 43 | void test_mod_tell_NULL_self(void **state); 44 | void test_mod_tell_NULL_msg(void **state); 45 | void test_mod_tell(void **state); 46 | void test_mod_publish_NULL_self(void **state); 47 | void test_mod_publish_NULL_msg(void **state); 48 | void test_mod_publish(void **state); 49 | void test_mod_broadcast_NULL_self(void **state); 50 | void test_mod_broadcast_NULL_msg(void **state); 51 | void test_mod_broadcast(void **state); 52 | -------------------------------------------------------------------------------- /tests/test_perf.c: -------------------------------------------------------------------------------- 1 | #include "test_perf.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define MAX_LEN 5000 7 | 8 | static void my_recv(m_mod_t *mod, const m_queue_t *const evts); 9 | 10 | static m_mod_t *mod; 11 | static int ctr; 12 | 13 | void test_poll_perf(void **state) { 14 | (void) state; /* unused */ 15 | 16 | int ret = m_ctx_register("perf", 0, NULL); 17 | assert_true(ret == 0); 18 | 19 | m_src_thresh_t thresh = { .activity_freq = 10.0 }; 20 | m_src_thresh_t alarm = {0}; 21 | 22 | m_mod_hook_t hook = { .on_evt = my_recv }; 23 | ret = m_mod_register("testName", &mod, &hook, 0, NULL); 24 | assert_true(ret == 0); 25 | assert_non_null(mod); 26 | assert_true(m_mod_is(mod, M_MOD_IDLE)); 27 | 28 | ret = m_mod_src_register(mod, &thresh, 0, &alarm); 29 | assert_int_equal(ret, 0); 30 | 31 | // test that it can be deregistered 32 | ret = m_mod_src_deregister(mod, &thresh); 33 | assert_int_equal(ret, 0); 34 | 35 | ret = m_mod_src_register(mod, &thresh, 0, &alarm); 36 | assert_int_equal(ret, 0); 37 | 38 | m_mod_start(mod); 39 | 40 | clock_t begin_tell = clock(); 41 | for (int i = 0; i < MAX_LEN; i++) { 42 | m_mod_ps_tell(mod, mod, "Hello World", 0); 43 | } 44 | clock_t end_tell = clock(); 45 | double time_spent = (double)(end_tell - begin_tell); 46 | printf("Messages feeding took %.2lf us\n", time_spent); 47 | 48 | m_ctx_loop(); 49 | 50 | clock_t end_recv = clock(); 51 | time_spent = (double)(end_recv - end_tell); 52 | printf("Messages fetching took %.2lf us\n", time_spent); 53 | 54 | assert_true(alarm.activity_freq > 0); 55 | printf("Was warned as activity was beyond thresh: %.2lf > %.2lf.\n", alarm.activity_freq, thresh.activity_freq); 56 | 57 | ret = m_mod_deregister(&mod); 58 | assert_int_equal(ret, 0); 59 | } 60 | 61 | static void my_recv(m_mod_t *mod, const m_queue_t *const evts) { 62 | m_itr_foreach(evts, { 63 | m_evt_t *msg = m_itr_get(m_itr); 64 | if (msg->type == M_SRC_TYPE_PS && ++ctr == MAX_LEN) { 65 | m_ctx_quit(0); 66 | } else if (msg->type == M_SRC_TYPE_THRESH) { 67 | m_src_thresh_t *alarm = (m_src_thresh_t *)msg->userdata; 68 | alarm->activity_freq = msg->thresh_evt->activity_freq; 69 | alarm->inactive_ms = msg->thresh_evt->inactive_ms; 70 | } 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /tests/test_perf.h: -------------------------------------------------------------------------------- 1 | #include "test_commons.h" 2 | 3 | void test_poll_perf(void **state); 4 | -------------------------------------------------------------------------------- /tests/test_queue.c: -------------------------------------------------------------------------------- 1 | #include "test_queue.h" 2 | #include 3 | 4 | static m_queue_t *my_q; 5 | static int val1 = 1; 6 | static int val2 = 2; 7 | static char val3[] = "Hello World"; 8 | 9 | void test_queue_enqueue(void **state) { 10 | (void) state; /* unused */ 11 | 12 | /* NULL map */ 13 | int ret = m_queue_enqueue(my_q, &val1); 14 | assert_false(ret == 0); 15 | 16 | my_q = m_queue_new(NULL); 17 | 18 | /* NULL value */ 19 | ret = m_queue_enqueue(my_q, NULL); 20 | assert_false(ret == 0); 21 | 22 | ret = m_queue_enqueue(my_q, &val1); 23 | assert_true(ret == 0); 24 | 25 | ret = m_queue_enqueue(my_q, &val2); 26 | assert_true(ret == 0); 27 | 28 | ret = m_queue_enqueue(my_q, val3); 29 | assert_true(ret == 0); 30 | } 31 | 32 | void test_queue_peek(void **state) { 33 | (void) state; /* unused */ 34 | 35 | void *v = m_queue_peek(NULL); 36 | assert_null(v); 37 | 38 | v = m_queue_peek(my_q); 39 | assert_non_null(v); 40 | assert_int_equal(*(int *)v, 1); 41 | } 42 | 43 | void test_queue_length(void **state) { 44 | (void) state; /* unused */ 45 | 46 | int len = m_queue_len(NULL); 47 | assert_false(len > 0); 48 | 49 | len = m_queue_len(my_q); 50 | assert_int_equal(len, 3); 51 | } 52 | 53 | void test_queue_iterator(void **state) { 54 | (void) state; /* unused */ 55 | 56 | /* NULL queue */ 57 | m_queue_itr_t *itr = m_queue_itr_new(NULL); 58 | assert_null(itr); 59 | 60 | itr = m_itr_new(my_q); 61 | assert_non_null(itr); 62 | 63 | int count = m_queue_len(my_q); 64 | while (itr) { 65 | printf("%p\n", m_itr_get(itr)); 66 | if (count % 2 == 0) { 67 | m_itr_rm(itr); 68 | } 69 | m_itr_next(&itr); 70 | count--; 71 | } 72 | 73 | assert_int_equal(count, 0); 74 | assert_null(itr); 75 | } 76 | 77 | void test_queue_dequeue(void **state) { 78 | (void) state; /* unused */ 79 | 80 | void *ptr = m_queue_dequeue(NULL); 81 | assert_null(ptr); 82 | 83 | ptr = m_queue_dequeue(my_q); 84 | assert_non_null(ptr); 85 | assert_int_equal(*(int *)ptr, 1); 86 | 87 | int len = m_queue_len(my_q); 88 | assert_int_equal(len, 1); // one element left 89 | } 90 | 91 | void test_queue_clear(void **state) { 92 | (void) state; /* unused */ 93 | 94 | int ret = m_queue_clear(NULL); 95 | assert_false(ret == 0); 96 | 97 | ret = m_queue_clear(my_q); 98 | assert_true(ret == 0); 99 | 100 | int len = m_queue_len(my_q); 101 | assert_int_equal(len, 0); 102 | 103 | // Push some new values 104 | /* NULL value */ 105 | ret = m_queue_enqueue(my_q, NULL); 106 | assert_false(ret == 0); 107 | 108 | ret = m_queue_enqueue(my_q, &val1); 109 | assert_true(ret == 0); 110 | 111 | ret = m_queue_enqueue(my_q, &val2); 112 | assert_true(ret == 0); 113 | 114 | ret = m_queue_enqueue(my_q, val3); 115 | assert_true(ret == 0); 116 | } 117 | 118 | void test_queue_free(void **state) { 119 | (void) state; /* unused */ 120 | 121 | int ret = m_queue_free(NULL); 122 | assert_false(ret == 0); 123 | 124 | ret = m_queue_free(&my_q); 125 | assert_true(ret == 0); 126 | assert_null(my_q); 127 | } 128 | 129 | -------------------------------------------------------------------------------- /tests/test_queue.h: -------------------------------------------------------------------------------- 1 | #include "test_commons.h" 2 | 3 | void test_queue_enqueue(void **state); 4 | void test_queue_peek(void **state); 5 | void test_queue_length(void **state); 6 | void test_queue_iterator(void **state); 7 | void test_queue_dequeue(void **state); 8 | void test_queue_clear(void **state); 9 | void test_queue_free(void **state); 10 | -------------------------------------------------------------------------------- /tests/test_stack.c: -------------------------------------------------------------------------------- 1 | #include "test_stack.h" 2 | #include 3 | 4 | static m_stack_t *my_st; 5 | static int val1 = 1; 6 | static int val2 = 2; 7 | static char val3[] = "Hello World"; 8 | 9 | void test_stack_push(void **state) { 10 | (void) state; /* unused */ 11 | 12 | /* NULL map */ 13 | int ret = m_stack_push(my_st, &val1); 14 | assert_false(ret == 0); 15 | 16 | my_st = m_stack_new(NULL); 17 | 18 | /* NULL value */ 19 | ret = m_stack_push(my_st, NULL); 20 | assert_false(ret == 0); 21 | 22 | ret = m_stack_push(my_st, &val1); 23 | assert_true(ret == 0); 24 | 25 | ret = m_stack_push(my_st, &val2); 26 | assert_true(ret == 0); 27 | 28 | ret = m_stack_push(my_st, val3); 29 | assert_true(ret == 0); 30 | } 31 | 32 | void test_stack_peek(void **state) { 33 | (void) state; /* unused */ 34 | 35 | char *v = m_stack_peek(NULL); 36 | assert_null(v); 37 | 38 | v = m_stack_peek(my_st); 39 | assert_non_null(v); 40 | assert_string_equal(v, "Hello World"); 41 | } 42 | 43 | void test_stack_length(void **state) { 44 | (void) state; /* unused */ 45 | 46 | int len = m_stack_len(NULL); 47 | assert_false(len > 0); 48 | 49 | len = m_stack_len(my_st); 50 | assert_int_equal(len, 3); 51 | } 52 | 53 | void test_stack_iterator(void **state) { 54 | (void) state; /* unused */ 55 | 56 | /* NULL m_stack */ 57 | m_stack_itr_t *itr = m_stack_itr_new(NULL); 58 | assert_null(itr); 59 | 60 | itr = m_itr_new(my_st); 61 | assert_non_null(itr); 62 | 63 | int count = m_stack_len(my_st); 64 | while (itr) { 65 | printf("%p\n", m_itr_get(itr)); 66 | if (count % 2 == 0) { 67 | m_itr_rm(itr); 68 | } 69 | m_itr_next(&itr); 70 | count--; 71 | 72 | } 73 | 74 | assert_int_equal(count, 0); 75 | assert_null(itr); 76 | } 77 | 78 | void test_stack_pop(void **state) { 79 | (void) state; /* unused */ 80 | 81 | void *ptr = m_stack_pop(NULL); 82 | assert_null(ptr); 83 | 84 | ptr = m_stack_pop(my_st); 85 | assert_non_null(ptr); 86 | assert_string_equal(ptr, "Hello World"); 87 | 88 | int len = m_stack_len(my_st); 89 | assert_int_equal(len, 1); // one element left 90 | } 91 | 92 | void test_stack_clear(void **state) { 93 | (void) state; /* unused */ 94 | 95 | int ret = m_stack_clear(NULL); 96 | assert_false(ret == 0); 97 | 98 | ret = m_stack_clear(my_st); 99 | assert_true(ret == 0); 100 | 101 | int len = m_stack_len(my_st); 102 | assert_int_equal(len, 0); 103 | } 104 | 105 | void test_stack_free(void **state) { 106 | (void) state; /* unused */ 107 | 108 | int ret = m_stack_free(NULL); 109 | assert_false(ret == 0); 110 | 111 | ret = m_stack_free(&my_st); 112 | assert_true(ret == 0); 113 | assert_null(my_st); 114 | } 115 | -------------------------------------------------------------------------------- /tests/test_stack.h: -------------------------------------------------------------------------------- 1 | #include "test_commons.h" 2 | 3 | void test_stack_push(void **state); 4 | void test_stack_peek(void **state); 5 | void test_stack_length(void **state); 6 | void test_stack_iterator(void **state); 7 | void test_stack_pop(void **state); 8 | void test_stack_free(void **state); 9 | -------------------------------------------------------------------------------- /tests/test_thpool.c: -------------------------------------------------------------------------------- 1 | #define _DEFAULT_SOURCE 2 | 3 | #include "test_thpool.h" 4 | #include "module/thpool/thpool.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define NUM_THREADS 8 11 | #define NUM_JOBS 64 12 | 13 | static void *inc(void *udata); 14 | static void *inc_weird(void *udata); 15 | 16 | static atomic_int ctr; 17 | 18 | void test_thpool(void **state) { 19 | (void) state; /* unused */ 20 | 21 | m_thpool_t *pool = m_thpool_new(NUM_THREADS, 0); 22 | assert_non_null(pool); 23 | 24 | for (int i = 0; i < NUM_JOBS; i++) { 25 | int ret = m_thpool_add(pool, NULL, NULL); 26 | assert_int_not_equal(ret, 0); 27 | 28 | ret = m_thpool_add(NULL, inc, NULL); 29 | assert_int_not_equal(ret, 0); 30 | 31 | ret = m_thpool_add(pool, inc, NULL); 32 | assert_int_equal(ret, 0); 33 | } 34 | 35 | /* wait any enqueued job */ 36 | int ret = m_thpool_free(&pool, true); 37 | assert_int_equal(ret, 0); 38 | 39 | assert_int_equal(ctr, NUM_JOBS); 40 | } 41 | 42 | void test_thpool_lazy(void **state) { 43 | (void) state; /* unused */ 44 | 45 | ctr = 0; 46 | 47 | m_thpool_t *pool = m_thpool_new(NUM_THREADS, M_THPOOL_LAZY); 48 | assert_non_null(pool); 49 | for (int i = 0; i < NUM_JOBS; i++) { 50 | int ret = m_thpool_add(pool, inc, NULL); 51 | assert_int_equal(ret, 0); 52 | } 53 | 54 | int ret = m_thpool_free(&pool, true); 55 | assert_int_equal(ret, 0); 56 | 57 | assert_int_equal(ctr, NUM_JOBS); 58 | } 59 | 60 | void test_thpool_weird_conditions(void **state) { 61 | (void) state; /* unused */ 62 | 63 | ctr = 0; 64 | 65 | m_thpool_t *pool = m_thpool_new(0, 0); 66 | assert_null(pool); 67 | 68 | /** First Test: 69 | * create a thpool and free it immediately, without adding any job. 70 | */ 71 | 72 | pool = m_thpool_new(NUM_THREADS, 0); 73 | assert_non_null(pool); 74 | 75 | /* No jobs... */ 76 | int ret = m_thpool_free(&pool, false); 77 | assert_int_equal(ret, 0); 78 | 79 | /** Second Test: 80 | * create a thpool then add NUM_JOBS tasks that each sleep 1s; 81 | * finally, free the thpool without awaiting all jobs (just the running ones) 82 | * Test that only NUM_THREADS jobs were actually executed (ie: 1 for each thread) 83 | */ 84 | 85 | pool = m_thpool_new(NUM_THREADS, 0); 86 | assert_non_null(pool); 87 | 88 | for (int i = 0; i < NUM_JOBS; i++) { 89 | ret = m_thpool_add(pool, inc_weird, NULL); 90 | assert_int_equal(ret, 0); 91 | } 92 | 93 | /* 94 | * Give some time to actually allow threads 95 | * to receive their tasks before the thpool is freed 96 | */ 97 | usleep(250000); 98 | 99 | /* 100 | * We won't wait all jobs, just first NUM_THREADS (1 for each thread) 101 | */ 102 | ret = m_thpool_free(&pool, false); 103 | assert_int_equal(ret, 0); 104 | 105 | assert_int_equal(ctr, NUM_THREADS); 106 | } 107 | 108 | static void *inc(void *udata) { 109 | ctr++; 110 | return NULL; 111 | } 112 | 113 | static void *inc_weird(void *udata) { 114 | sleep(1); 115 | ctr++; 116 | return NULL; 117 | } 118 | -------------------------------------------------------------------------------- /tests/test_thpool.h: -------------------------------------------------------------------------------- 1 | #include "test_commons.h" 2 | 3 | void test_thpool(void **state); 4 | void test_thpool_lazy(void **state); 5 | void test_thpool_weird_conditions(void **state); --------------------------------------------------------------------------------