diff --git a/.github/workflows/kernel_tests.yaml b/.github/workflows/kernel_tests.yaml new file mode 100644 index 0000000000..952bdc2e3b --- /dev/null +++ b/.github/workflows/kernel_tests.yaml @@ -0,0 +1,98 @@ +name: Test drivers against a matrix of kernels/distros + +on: + workflow_dispatch: + inputs: + libsversion: + description: libs version to be tested + type: string + required: false + default: master + push: + branches: + - master + tags: + - '[0-9]+.[0-9]+.[0-9]+\+driver' + +concurrency: + group: kernel-tests + cancel-in-progress: true + +jobs: + test-kernels: + strategy: + fail-fast: false + matrix: + architecture: [X64, ARM64] + runs-on: [ "self-hosted", "linux", "${{matrix.architecture}}" ] + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + repository: falcosecurity/kernel-testing + ref: v0.2.3 + + - name: Generate vars yaml + working-directory: ./ansible-playbooks + run: | + LIBS_V=${{ github.event.inputs.libsversion }} + LIBS_VERSION=${LIBS_V:-${{ github.ref_name }}} + cat > vars.yml < release-body.md + + - name: Release + uses: softprops/action-gh-release@v1 + with: + body_path: ./release-body.md + append_body: true + tag_name: ${{ github.event.workflow_run.head_branch }} diff --git a/README.md b/README.md index 6e49031ac5..9c27c14012 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,10 @@ [![CI Build](https://github.com/falcosecurity/libs/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/falcosecurity/libs/actions/workflows/ci.yml) [![Architectures](https://img.shields.io/badge/ARCHS-x86__64%7Caarch64%7Cs390x-blueviolet)](#drivers-officially-supported-architectures) [![Drivers](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/FedeDP/1cbc5d42edf8e3a02fb75e76625f1072/raw/kernel.json)](https://github.com/falcosecurity/libs/actions/workflows/latest-kernel.yml) +[![Kernel Tests](https://github.com/falcosecurity/libs/actions/workflows/kernel_tests.yaml/badge.svg)](https://github.com/falcosecurity/libs/actions/workflows/kernel_tests.yaml) +[![Github Pages](https://github.com/falcosecurity/libs/actions/workflows/pages.yml/badge.svg)](https://falcosecurity.github.io/libs/) -As per the [OSS Libraries Contribution Plan](https://github.com/falcosecurity/falco/blob/master/proposals/20210119-libraries-contribution.md), this repository has been chosen to be the new home for **libsinsp**, **libscap**, the **kernel module** and the **eBPF probe** sources. -Refer to https://falco.org/blog/contribution-drivers-kmod-ebpf-libraries/ for more information. +This repository contains **libsinsp**, **libscap**, the **kernel module** and the **eBPF probes** sources. These components are at the foundation of [Falco](https://github.com/falcosecurity/falco) and other projects that work with the same kind of data. @@ -40,6 +41,8 @@ plus chisels related code and common utilities. external dependencies, plus the libscap and libsinsp ones; consumers (like Falco) use those modules to build the libs in their projects. + For an overview of the event sources that are implemented by the libs see also the official [Falco documentation](https://falco.org/docs/event-sources/). + ## Versioning This project uses two different versioning schemes for the _libs_ and _driver_ components. In particular, the _driver_ versions are suffixed with `+driver` to distinguish them from the _libs_ ones. Both adhere to the [Semantic Versioning 2.0.0](https://semver.org/). You can find more detail about how we version those components in our [release process documentation](./release.md). @@ -56,7 +59,7 @@ Right now our drivers officially support the following architectures: | **aarch64** | >= [3.16](https://github.com/torvalds/linux/commit/055b1212d141f1f398fca548f8147787c0b6253f) | >= 4.17 | >= 5.8 | _STABLE_ | | **s390x** | >= 2.6 | >= [5.5](https://github.com/torvalds/linux/commit/6ae08ae3dea) | >= 5.8 | _EXPERIMENTAL_ | -**For a list of supported syscalls through specific events, please refer to [_report_](./driver/report.md).** +**For a list of supported syscalls through specific events, please refer to [_report_](./docs/report.md).** > **NOTE:** while we strive to achieve maximum compatibility, we cannot assure that drivers correctly build against a new kernel version minutes after it gets released, since we might need to make some adjustments. > To get properly notified whenever drivers stop building, we have a [CI workflow](.github/workflows/latest-kernel.yml) that tests the build against the [latest mainline kernel](https://www.kernel.org/) (RC too!) diff --git a/cmake/modules/CompilerFlags.cmake b/cmake/modules/CompilerFlags.cmake index dc9480c6e2..fdcb7fa6bf 100644 --- a/cmake/modules/CompilerFlags.cmake +++ b/cmake/modules/CompilerFlags.cmake @@ -1,6 +1,6 @@ option(BUILD_WARNINGS_AS_ERRORS "Enable building with -Wextra -Werror flags") -option(ENABLE_PIC "Build position independent libraries and executables" OFF) +option(ENABLE_PIC "Build position independent libraries and executables" ${BUILD_SHARED_LIBS}) if(ENABLE_PIC) set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() diff --git a/cmake/modules/libbpf.cmake b/cmake/modules/libbpf.cmake index ce7ceb0e01..9511738134 100644 --- a/cmake/modules/libbpf.cmake +++ b/cmake/modules/libbpf.cmake @@ -26,7 +26,7 @@ else() URL_HASH "SHA256=32b0c41eabfbbe8e0c8aea784d7495387ff9171b5a338480a8fbaceb9da8d5e5" CONFIGURE_COMMAND mkdir -p build root - BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} BUILD_STATIC_ONLY=y OBJDIR=${LIBBPF_BUILD_DIR}/build DESTDIR=${LIBBPF_BUILD_DIR}/root NO_PKG_CONFIG=1 "EXTRA_CFLAGS=-I${LIBELF_INCLUDE} -I${ZLIB_INCLUDE}" "LDFLAGS=-Wl,-Bstatic" "EXTRA_LDFLAGS=-L${LIBELF_SRC}/libelf/libelf -L${ZLIB_SRC}" -C ${LIBBPF_SRC}/libbpf/src install install_uapi_headers + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} BUILD_STATIC_ONLY=y OBJDIR=${LIBBPF_BUILD_DIR}/build DESTDIR=${LIBBPF_BUILD_DIR}/root NO_PKG_CONFIG=1 "EXTRA_CFLAGS=-fPIC -I${LIBELF_INCLUDE} -I${ZLIB_INCLUDE}" "LDFLAGS=-Wl,-Bstatic" "EXTRA_LDFLAGS=-L${LIBELF_SRC}/libelf/libelf -L${ZLIB_SRC}" -C ${LIBBPF_SRC}/libbpf/src install install_uapi_headers INSTALL_COMMAND "" UPDATE_COMMAND "" ) diff --git a/cmake/modules/libelf.cmake b/cmake/modules/libelf.cmake index 33a4cf8d4f..b567366929 100644 --- a/cmake/modules/libelf.cmake +++ b/cmake/modules/libelf.cmake @@ -7,7 +7,12 @@ if(LIBELF_INCLUDE) # we already have LIBELF elseif(NOT USE_BUNDLED_LIBELF) find_path(LIBELF_INCLUDE elf.h PATH_SUFFIXES elf) - find_library(LIBELF_LIB NAMES libelf.a libelf.so) + if(BUILD_SHARED_LIBS) + set(LIBELF_LIB_SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX}) + else() + set(LIBELF_LIB_SUFFIX ${CMAKE_STATIC_LIBRARY_SUFFIX}) + endif() + find_library(LIBELF_LIB NAMES libelf${LIBELF_LIB_SUFFIX}) if(LIBELF_LIB) message(STATUS "Found LIBELF: include: ${LIBELF_INCLUDE}, lib: ${LIBELF_LIB}") else() diff --git a/cmake/modules/libscap.cmake b/cmake/modules/libscap.cmake index a87dcd5b0e..ca2207cca2 100644 --- a/cmake/modules/libscap.cmake +++ b/cmake/modules/libscap.cmake @@ -53,37 +53,51 @@ endfunction() add_subdirectory(${LIBSCAP_DIR}/userspace/libscap ${PROJECT_BINARY_DIR}/libscap) -# We can switch to using the MANUALLY_ADDED_DEPENDENCIES when our minimum -# CMake version is 3.8 or later. -set(LIBSCAP_LIBS - scap - scap_engine_noop - scap_engine_source_plugin - scap_error - scap_event_schema) - -set(libscap_conditional_libs - driver_event_schema - pman - scap_engine_bpf - scap_engine_gvisor - scap_engine_kmod - scap_engine_modern_bpf - scap_engine_nodriver - scap_engine_savefile - scap_engine_test_input - scap_engine_udig - scap_engine_util - scap_platform - scap_platform_util) - -foreach(libscap_conditional_lib ${libscap_conditional_libs}) - if(TARGET ${libscap_conditional_lib}) - list(APPEND LIBSCAP_LIBS ${libscap_conditional_lib}) +set(LIBSCAP_INSTALL_LIBS) + +# All of the targets in userspace/libscap +get_directory_property(libscap_subdirs DIRECTORY ${LIBSCAP_DIR}/userspace/libscap SUBDIRECTORIES) +set(libscap_subdir_targets) +foreach(libscap_subdir ${LIBSCAP_DIR}/userspace/libscap ${libscap_subdirs}) + get_directory_property(subdir_targets DIRECTORY ${libscap_subdir} BUILDSYSTEM_TARGETS) + list(APPEND libscap_subdir_targets ${subdir_targets}) +endforeach() + +set(install_lib_type STATIC_LIBRARY) +if (BUILD_SHARED_LIBS) + set(install_lib_type SHARED_LIBRARY) +endif() + +# Installation targets only +foreach(libscap_subdir_target ${libscap_subdir_targets}) + get_target_property(cl_target_type ${libscap_subdir_target} TYPE) + if (${cl_target_type} STREQUAL ${install_lib_type}) + list(APPEND LIBSCAP_INSTALL_LIBS ${libscap_subdir_target}) endif() endforeach() -install(TARGETS ${LIBSCAP_LIBS} +# Installation targets and their dependencies +set(libscap_link_libraries) +foreach(libscap_install_lib ${LIBSCAP_INSTALL_LIBS}) + list(APPEND libscap_link_libraries ${libscap_install_lib}) + get_target_property(install_lib_link_libraries ${libscap_install_lib} LINK_LIBRARIES) + foreach (install_lib_link_library ${install_lib_link_libraries}) + if (NOT ${install_lib_link_library} IN_LIST libscap_subdir_targets) + list(APPEND libscap_link_libraries ${install_lib_link_library}) + endif() + endforeach() +endforeach() +list(REMOVE_DUPLICATES libscap_link_libraries) + +set(libscap_link_flags) +foreach(libscap_link_library ${libscap_link_libraries}) + list(APPEND libscap_link_flags "-l${libscap_link_library}") +endforeach() + +string(REPLACE ";" " " LIBSCAP_LINK_LIBRARIES_FLAGS "${libscap_link_flags}") +configure_file(${LIBSCAP_DIR}/userspace/libscap/libscap.pc.in ${PROJECT_BINARY_DIR}/libscap/libscap.pc @ONLY) + +install(TARGETS ${LIBSCAP_INSTALL_LIBS} ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}/${LIBS_PACKAGE_NAME}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}/${LIBS_PACKAGE_NAME}" RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000000..a04eb87b6e --- /dev/null +++ b/docs/index.md @@ -0,0 +1,10 @@ +# Falcosecurity drivers + +## Falco drivers kernel testing matrixes + +Here you can find kernel testing support matrixes for [Falco](https://falco.org/) drivers. +For more info, make sure to read the [driver kernel testing framework proposal](https://github.com/falcosecurity/libs/blob/master/proposals/20230530-driver-kernel-testing-framework.md). + +## Syscalls Report + +You can also find the list of supported syscalls by our drivers, be it through specific filler or generic. diff --git a/docs/matrix.md b/docs/matrix.md new file mode 100644 index 0000000000..348d0c36b9 --- /dev/null +++ b/docs/matrix.md @@ -0,0 +1,25 @@ +# Home of Falco drivers kernel testing matrixes + +Basically, we use Ansible playbooks to spawn Firecracker microvms where we can test: + +* kmod and ebpf drivers build +* scap-open run with {kmod,ebpf,modern-bpf} + +The modern-bpf driver-enabled scap-open is built using the exactly same process used by [Falco release pipeline](https://github.com/falcosecurity/falco/blob/master/.github/workflows/reusable_build_packages.yaml#L15): + +* the modern bpf skeleton is built on a Fedora machine +* scap-open with embedded modern-bpf skeleton is built on a centos7 machine to allow largest possible support (old glibc version) +* scap-open binary is copied to each spawned vm + +## Supported Archs + +For now, supported architectures are: + +* AMD64 +* ARM64 + +## Glossary + +* 🟢 -> means that the test was successful +* 🟡 -> means that the test was skipped; you can click the symbol to reach the test section and checkout why the test was skipped. +* ❌ -> means that the test failed; you can click the symbol to reach the test section and checkout why the test failed. diff --git a/driver/report.md b/docs/report.md similarity index 97% rename from driver/report.md rename to docs/report.md index af62a98dad..ecd941fbac 100644 --- a/driver/report.md +++ b/docs/report.md @@ -1,10 +1,3 @@ -# Supported Syscalls - -This table represents the syscalls supported by our drivers. - -🟢 means that the syscall is fully instrumented so its parameters are available to userspace. -🟡 means that the syscall is not fully instrumented so the userspace is just notified when the syscall happens but no parameters are available. - | SYSCALL | SUPPORTED | |-------------------------|-----------| | _sysctl | 🟡 | diff --git a/docs/syscalls.md b/docs/syscalls.md new file mode 100644 index 0000000000..206dbf6de0 --- /dev/null +++ b/docs/syscalls.md @@ -0,0 +1,10 @@ +# Home of Falco drivers syscalls report + +Thanks to our [syscalls-bumper](https://github.com/falcosecurity/syscalls-bumper) project, we are able to always support latest syscalls added to linux kernel. +Support for new syscalls is initially automatically added by the tool as generic events; when needed, a generic event can be made "specific", +by creating a whole new event to track it. + +## Glossary + +* 🟢 -> means that the syscall is implemented as a specific event +* 🟡 -> means that the syscall is implemented as a generic event diff --git a/driver/modern_bpf/CMakeLists.txt b/driver/modern_bpf/CMakeLists.txt index cfccfac7f2..fa1f88fa66 100644 --- a/driver/modern_bpf/CMakeLists.txt +++ b/driver/modern_bpf/CMakeLists.txt @@ -238,9 +238,6 @@ foreach(BPF_C_FILE ${BPF_C_FILES}) set(BPF_O_FILE ${CMAKE_CURRENT_BINARY_DIR}/${file_stem}.bpf.o) -## TODO: we need to clean this! -## Please note: that the `libbpf` target exists only if we use `USE_BUNDLED_LIBBPF` -if(USE_BUNDLED_LIBBPF) add_custom_command( OUTPUT ${BPF_O_FILE} COMMAND ${MODERN_CLANG_EXE} ${CLANG_FLAGS} ${CLANG_SYSTEM_INCLUDES} -c ${BPF_C_FILE} -o ${BPF_O_FILE} @@ -249,15 +246,6 @@ if(USE_BUNDLED_LIBBPF) DEPENDS ${BPF_C_FILE} ${BPF_H_FILES} COMMENT "${MODERN_BPF_LOG_PREFIX} Building BPF object: ${BPF_O_FILE}" ) -else() - add_custom_command( - OUTPUT ${BPF_O_FILE} - COMMAND ${MODERN_CLANG_EXE} ${CLANG_FLAGS} ${CLANG_SYSTEM_INCLUDES} -c ${BPF_C_FILE} -o ${BPF_O_FILE} - VERBATIM - DEPENDS ${BPF_C_FILE} ${BPF_H_FILES} - COMMENT "${MODERN_BPF_LOG_PREFIX} Building BPF object: ${BPF_O_FILE}" - ) -endif() list(APPEND BPF_OBJECT_FILES ${BPF_O_FILE}) endforeach() diff --git a/driver/modern_bpf/programs/attached/events/signal_deliver.bpf.c b/driver/modern_bpf/programs/attached/events/signal_deliver.bpf.c index 1b7da84dbd..3c28415194 100644 --- a/driver/modern_bpf/programs/attached/events/signal_deliver.bpf.c +++ b/driver/modern_bpf/programs/attached/events/signal_deliver.bpf.c @@ -33,43 +33,46 @@ int BPF_PROG(signal_deliver, /* Try to find the source pid */ pid_t spid = 0; - switch(sig) + if(info != NULL) { - case SIGKILL: - spid = info->_sifields._kill._pid; - break; - - case SIGTERM: - case SIGHUP: - case SIGINT: - case SIGTSTP: - case SIGQUIT: - { - int si_code = info->si_code; - if(si_code == SI_USER || - si_code == SI_QUEUE || - si_code <= 0) + switch(sig) { - /* This is equivalent to `info->si_pid` where - * `si_pid` is a macro `_sifields._kill._pid` - */ + case SIGKILL: spid = info->_sifields._kill._pid; + break; + + case SIGTERM: + case SIGHUP: + case SIGINT: + case SIGTSTP: + case SIGQUIT: + { + int si_code = info->si_code; + if(si_code == SI_USER || + si_code == SI_QUEUE || + si_code <= 0) + { + /* This is equivalent to `info->si_pid` where + * `si_pid` is a macro `_sifields._kill._pid` + */ + spid = info->_sifields._kill._pid; + } + break; } - break; - } - case SIGCHLD: - spid = info->_sifields._sigchld._pid; - break; + case SIGCHLD: + spid = info->_sifields._sigchld._pid; + break; - default: - spid = 0; - break; - } + default: + spid = 0; + break; + } - if(sig >= SIGRTMIN && sig <= SIGRTMAX) - { - spid = info->_sifields._rt._pid; + if(sig >= SIGRTMIN && sig <= SIGRTMAX) + { + spid = info->_sifields._rt._pid; + } } /* Parameter 1: spid (type: PT_PID) */ diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000000..4d6a11e691 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,13 @@ +site_name: Falcosecurity Drivers +site_url: https://github.com/falcosecurity/libs +nav: + - Home: index.md + - Matrixes: + - matrix.md + - amd64: matrix_X64.md + - arm64: matrix_ARM64.md + - Syscalls: + - syscalls.md + - Report: report.md + +theme: material diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000..9a8a4ca477 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +mkdocs +mkdocs-material diff --git a/userspace/libscap/CMakeLists.txt b/userspace/libscap/CMakeLists.txt index 5822454a08..39253e544e 100644 --- a/userspace/libscap/CMakeLists.txt +++ b/userspace/libscap/CMakeLists.txt @@ -88,21 +88,19 @@ add_library(scap set_scap_target_properties(scap) add_library(scap_platform_util + STATIC scap_iflist.c scap_proc_util.c) -set_scap_target_properties(scap_platform_util) if(NOT MINIMAL_BUILD) target_link_libraries(scap "${ZLIB_LIB}") endif() -add_library(scap_error strerror.c) +add_library(scap_error STATIC strerror.c) target_link_libraries(scap scap_error) -set_scap_target_properties(scap_error) - if(CMAKE_SYSTEM_NAME MATCHES "Linux") option(BUILD_LIBSCAP_EXAMPLES "Build libscap examples" ON) include(FindMakedev) @@ -131,6 +129,7 @@ elseif(APPLE) endif() add_library(scap_event_schema + STATIC scap_event.c ppm_sc_names.c ../../driver/dynamic_params_table.c @@ -142,23 +141,21 @@ add_library(scap_event_schema target_link_libraries(scap scap_event_schema) -set_scap_target_properties(scap_event_schema) - if(CMAKE_SYSTEM_NAME MATCHES "Linux") add_library(driver_event_schema + STATIC ../../driver/fillers_table.c) target_link_libraries(scap_event_schema driver_event_schema) - set_scap_target_properties(scap_event_schema) endif() if(CMAKE_SYSTEM_NAME MATCHES "Linux") add_library(scap_engine_util + STATIC scap_engine_util.c ringbuffer/devset.c ringbuffer/ringbuffer.c) target_link_libraries(scap scap_engine_util) - set_scap_target_properties(scap_engine_util) endif() add_definitions(-DHAS_ENGINE_NOOP) @@ -174,11 +171,7 @@ endif() add_definitions(-DHAS_ENGINE_SAVEFILE) add_subdirectory(engine/savefile) -# The static and shared build differs here because a shared scap_engine_savefile -# will result in circular dependencies. -if(NOT BUILD_SHARED_LIBS) - target_link_libraries(scap scap_engine_savefile) -endif() +target_link_libraries(scap scap_engine_savefile) add_definitions(-DHAS_ENGINE_SOURCE_PLUGIN) add_subdirectory(engine/source_plugin) @@ -234,28 +227,6 @@ if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND CMAKE_SYSTEM_NAME MATCHES "Linux endif() endif() -# We might need to filter this. -get_target_property(scap_link_libraries scap LINK_LIBRARIES) -if(TARGET scap_engine_bpf) - get_target_property(scap_engine_bpf_link_libraries scap_engine_bpf LINK_LIBRARIES) - if (scap_engine_bpf_link_libraries) - list(APPEND scap_link_libraries ${scap_engine_bpf_link_libraries}) - endif() -endif() -#get_target_property(scap_engine_kmod_link_libraries scap_engine_kmod LINK_LIBRARIES) -get_target_property(scap_event_schema_link_libraries scap_event_schema LINK_LIBRARIES) -if (scap_event_schema_link_libraries) - list(APPEND scap_link_libraries ${scap_event_schema_link_libraries}) -endif() - -set(scap_link_flags "") -foreach(link_library scap_engine_noop ${scap_link_libraries}) - list(APPEND scap_link_flags "-l${link_library}") -endforeach() -list(REMOVE_DUPLICATES scap_link_flags) -string(REPLACE ";" " " SCAP_LINK_LIBRARIES_FLAGS "${scap_link_flags}") -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libscap.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libscap.pc @ONLY) - if (BUILD_LIBSCAP_EXAMPLES) add_subdirectory(examples/01-open) add_subdirectory(examples/02-validatebuffer) diff --git a/userspace/libscap/engine/modern_bpf/scap_modern_bpf.c b/userspace/libscap/engine/modern_bpf/scap_modern_bpf.c index 0dd83f124f..797d867ec0 100644 --- a/userspace/libscap/engine/modern_bpf/scap_modern_bpf.c +++ b/userspace/libscap/engine/modern_bpf/scap_modern_bpf.c @@ -167,12 +167,12 @@ int32_t scap_modern_bpf__init(scap_t* handle, scap_open_args* oargs) */ if(check_buffer_bytes_dim(handle->m_lasterr, params->buffer_bytes_dim) != SCAP_SUCCESS) { - return SCAP_FAILURE; + return ENOTSUP; } if(!pman_check_support()) { - return SCAP_FAILURE; + return ENOTSUP; } /* Initialize the libpman internal state. diff --git a/userspace/libscap/engine/noop/CMakeLists.txt b/userspace/libscap/engine/noop/CMakeLists.txt index e69767909f..15be51a248 100644 --- a/userspace/libscap/engine/noop/CMakeLists.txt +++ b/userspace/libscap/engine/noop/CMakeLists.txt @@ -1,3 +1,2 @@ include_directories(${LIBSCAP_INCLUDE_DIRS}) -add_library(scap_engine_noop noop.c) -set_scap_target_properties(scap_engine_noop) +add_library(scap_engine_noop STATIC noop.c) diff --git a/userspace/libscap/engine/savefile/CMakeLists.txt b/userspace/libscap/engine/savefile/CMakeLists.txt index 4abc0fa230..1dc3536392 100644 --- a/userspace/libscap/engine/savefile/CMakeLists.txt +++ b/userspace/libscap/engine/savefile/CMakeLists.txt @@ -1,19 +1,16 @@ include_directories(${LIBSCAP_INCLUDE_DIRS} ../noop) -set(scap_engine_savefile_sources - ${CMAKE_CURRENT_SOURCE_DIR}/scap_savefile.c - ${CMAKE_CURRENT_SOURCE_DIR}/scap_reader_gzfile.c - ${CMAKE_CURRENT_SOURCE_DIR}/scap_reader_buffered.c) -if (BUILD_SHARED_LIBS) - # Trying to build a shared scap_engine_savefile will result in circular - # dependencies, so just add our sources to scap. - target_sources(scap PRIVATE ${scap_engine_savefile_sources}) -else() - add_library(scap_engine_savefile ${scap_engine_savefile_sources}) +# Since we have circular dependencies between libscap and the savefile engine, +# make this library always static (directly linked into libscap) +add_library(scap_engine_savefile + STATIC + scap_savefile.c + scap_reader_gzfile.c + scap_reader_buffered.c) - if(NOT MINIMAL_BUILD) - add_dependencies(scap_engine_savefile zlib) - endif() - target_link_libraries(scap_engine_savefile scap_engine_noop scap_platform_util ${ZLIB_LIB}) - set_scap_target_properties(scap_engine_savefile) -endif() +target_link_libraries(scap_engine_savefile scap_engine_noop scap_platform_util) + +if(NOT MINIMAL_BUILD) + add_dependencies(scap_engine_savefile zlib) + target_link_libraries(scap_engine_savefile ${ZLIB_LIB}) +endif() \ No newline at end of file diff --git a/userspace/libscap/examples/01-open/scap_open.c b/userspace/libscap/examples/01-open/scap_open.c index 8fa8c66199..d9d65728f6 100644 --- a/userspace/libscap/examples/01-open/scap_open.c +++ b/userspace/libscap/examples/01-open/scap_open.c @@ -856,7 +856,7 @@ int main(int argc, char** argv) if(g_h == NULL || res != SCAP_SUCCESS) { fprintf(stderr, "%s (%d)\n", error, res); - return EXIT_FAILURE; + return res; } print_start_capture(); diff --git a/userspace/libscap/libscap.pc.in b/userspace/libscap/libscap.pc.in index d8b09076e8..f05740e1ef 100644 --- a/userspace/libscap/libscap.pc.in +++ b/userspace/libscap/libscap.pc.in @@ -6,5 +6,5 @@ Name: libscap Description: lib for System CAPture Version: @FALCOSECURITY_LIBS_VERSION@ -Libs: -L${libdir}/@LIBS_PACKAGE_NAME@ -lscap -lscap_error -lscap_platform -lscap_platform_util @SCAP_LINK_LIBRARIES_FLAGS@ +Libs: -L${libdir}/@LIBS_PACKAGE_NAME@ @LIBSCAP_LINK_LIBRARIES_FLAGS@ Cflags: -I${includedir}/@LIBS_PACKAGE_NAME@/userspace/libscap diff --git a/userspace/libscap/linux/CMakeLists.txt b/userspace/libscap/linux/CMakeLists.txt index e8baab260e..117ead7e6f 100644 --- a/userspace/libscap/linux/CMakeLists.txt +++ b/userspace/libscap/linux/CMakeLists.txt @@ -1,3 +1,2 @@ -add_library(scap_platform scap_linux_platform.c scap_procs.c scap_fds.c scap_userlist.c scap_iflist.c scap_cgroup.c) -target_link_libraries(scap_platform scap_error scap_platform_util) -set_scap_target_properties(scap_platform) +add_library(scap_platform STATIC scap_linux_platform.c scap_procs.c scap_fds.c scap_userlist.c scap_iflist.c scap_cgroup.c) +target_link_libraries(scap_platform scap_error scap_platform_util) \ No newline at end of file diff --git a/userspace/libscap/linux/scap_cgroup.c b/userspace/libscap/linux/scap_cgroup.c index 82ccc91a95..b0fc6f0e7c 100644 --- a/userspace/libscap/linux/scap_cgroup.c +++ b/userspace/libscap/linux/scap_cgroup.c @@ -338,8 +338,10 @@ static int32_t get_cgroup_subsystems_v1(struct scap_cgroup_set* subsystems) fclose(cgroups); return SCAP_FAILURE; } + // 3:cpu,cpuacct:/user.slice/user-0.slice/session-13542.scope - // ^p ^q + // ^p ^q + p += 1; char* q = strchr(p, ':'); if(!q) { @@ -348,7 +350,7 @@ static int32_t get_cgroup_subsystems_v1(struct scap_cgroup_set* subsystems) } // 3:cpu,cpuacct - // ^p ^q + // ^p ^q *q = 0; if(strlen(p) == 0) { @@ -358,7 +360,7 @@ static int32_t get_cgroup_subsystems_v1(struct scap_cgroup_set* subsystems) while(1) { // 3:cpu\0cpuacct - // ^p ^q + // ^p ^q char* comma = strchr(p, ','); if(comma) { diff --git a/userspace/libscap/scap.c b/userspace/libscap/scap.c index 51fe07c506..0a3012cbd2 100644 --- a/userspace/libscap/scap.c +++ b/userspace/libscap/scap.c @@ -63,7 +63,7 @@ int32_t scap_init_int(scap_t* handle, scap_open_args* oargs, const struct scap_v return SCAP_FAILURE; } - if((rc = scap_platform_init(handle->m_platform, handle->m_lasterr, handle->m_engine, oargs)) != SCAP_SUCCESS) + if((rc = scap_generic_init_platform(handle->m_platform, handle->m_lasterr, oargs)) != SCAP_SUCCESS) { return rc; } @@ -75,6 +75,11 @@ int32_t scap_init_int(scap_t* handle, scap_open_args* oargs, const struct scap_v return rc; } + if((rc = scap_platform_init(handle->m_platform, handle->m_lasterr, handle->m_engine, oargs)) != SCAP_SUCCESS) + { + return rc; + } + rc = check_api_compatibility(handle->m_vtable, handle->m_engine, handle->m_lasterr); if(rc != SCAP_SUCCESS) { diff --git a/userspace/libscap/scap_platform.c b/userspace/libscap/scap_platform.c index f0cc6b5568..571336270f 100644 --- a/userspace/libscap/scap_platform.c +++ b/userspace/libscap/scap_platform.c @@ -23,7 +23,7 @@ limitations under the License. #include "scap-int.h" #include "scap_os_machine_info.h" -static int32_t scap_generic_init_platform(struct scap_platform* platform, char* lasterr, struct scap_open_args* oargs) +int32_t scap_generic_init_platform(struct scap_platform* platform, char* lasterr, struct scap_open_args* oargs) { memset(&platform->m_machine_info, 0, sizeof(platform->m_machine_info)); if(scap_os_get_machine_info(&platform->m_machine_info, lasterr) != SCAP_SUCCESS) @@ -99,13 +99,6 @@ int32_t scap_platform_init(struct scap_platform *platform, char *lasterr, struct return SCAP_SUCCESS; } - rc = scap_generic_init_platform(platform, lasterr, oargs); - if(rc != SCAP_SUCCESS) - { - scap_platform_close(platform); - return rc; - } - if(platform->m_vtable && platform->m_vtable->init_platform) { rc = platform->m_vtable->init_platform(platform, lasterr, engine, oargs); diff --git a/userspace/libscap/scap_platform.h b/userspace/libscap/scap_platform.h index c6a0d162d5..4947ee1c65 100644 --- a/userspace/libscap/scap_platform.h +++ b/userspace/libscap/scap_platform.h @@ -37,9 +37,14 @@ struct scap_platform; // allocate a generic platform handle with no behavior (no platform data is returned) struct scap_platform* scap_generic_alloc_platform(); +// initialize the common part of the platform handle +// this needs to be called before opening the engine +// as otherwise the proclist callback won't be set up in time +// (for the savefile engine) +int32_t scap_generic_init_platform(struct scap_platform* platform, char* lasterr, struct scap_open_args* oargs); + // initialize a platform handle -// this calls `init_platform` from the vtable and also -// does any common initialization +// this calls `init_platform` from the vtable int32_t scap_platform_init(struct scap_platform *platform, char *lasterr, struct scap_engine_handle engine, struct scap_open_args *oargs); diff --git a/userspace/libscap/scap_platform_impl.h b/userspace/libscap/scap_platform_impl.h index c93629dde0..8ebfd70edc 100644 --- a/userspace/libscap/scap_platform_impl.h +++ b/userspace/libscap/scap_platform_impl.h @@ -47,9 +47,7 @@ struct ppm_proclist_info; struct scap_platform_vtable { // initialize the platform-specific structure - // at this point the engine is allocated but *not initialized yet* - // so it can only be stored away for future use - // (the engine will be initialized before any other platform method is called) + // at this point the engine is fully initialized and operational int32_t (*init_platform)(struct scap_platform* platform, char* lasterr, struct scap_engine_handle engine, struct scap_open_args* oargs); // refresh the interface list and place it inside diff --git a/userspace/libsinsp/fdinfo.cpp b/userspace/libsinsp/fdinfo.cpp index ab5b59b6d3..28503049d0 100644 --- a/userspace/libsinsp/fdinfo.cpp +++ b/userspace/libsinsp/fdinfo.cpp @@ -359,7 +359,7 @@ sinsp_fdinfo_t* sinsp_fdtable::add(int64_t fd, sinsp_fdinfo_t* fdinfo) // m_last_accessed_fd = -1; #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_added_fds++; + m_inspector->m_stats->m_n_added_fds++; #endif std::pair::iterator, bool> insert_res = m_table.emplace(fd, *fdinfo); return &(insert_res.first->second); @@ -429,15 +429,15 @@ void sinsp_fdtable::erase(int64_t fd) // ASSERT(false); #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_failed_fd_lookups++; + m_inspector->m_stats->m_n_failed_fd_lookups++; #endif } else { m_table.erase(fdit); #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_noncached_fd_lookups++; - m_inspector->m_stats.m_n_removed_fds++; + m_inspector->m_stats->m_n_noncached_fd_lookups++; + m_inspector->m_stats->m_n_removed_fds++; #endif } } diff --git a/userspace/libsinsp/fdinfo.h b/userspace/libsinsp/fdinfo.h index a5f11fb87f..4770cf531a 100644 --- a/userspace/libsinsp/fdinfo.h +++ b/userspace/libsinsp/fdinfo.h @@ -525,7 +525,7 @@ class sinsp_fdtable if(m_last_accessed_fd != -1 && fd == m_last_accessed_fd) { #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_cached_fd_lookups++; + m_inspector->m_stats->m_n_cached_fd_lookups++; #endif return m_last_accessed_fdinfo; } @@ -538,14 +538,14 @@ class sinsp_fdtable if(fdit == m_table.end()) { #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_failed_fd_lookups++; + m_inspector->m_stats->m_n_failed_fd_lookups++; #endif return NULL; } else { #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_noncached_fd_lookups++; + m_inspector->m_stats->m_n_noncached_fd_lookups++; #endif m_last_accessed_fd = fd; m_last_accessed_fdinfo = &(fdit->second); diff --git a/userspace/libsinsp/filterchecks.cpp b/userspace/libsinsp/filterchecks.cpp index 9f046db3c1..566da52138 100644 --- a/userspace/libsinsp/filterchecks.cpp +++ b/userspace/libsinsp/filterchecks.cpp @@ -2704,7 +2704,8 @@ uint8_t* sinsp_filter_check_thread::extract_thread_cpu(sinsp_evt *evt, OUT uint3 tcpu = user + system; - uint64_t last_t_tot_cpu = tinfo->get_dynamic_field(*m_thread_dyn_field_accessor.get()); + uint64_t last_t_tot_cpu = 0; + tinfo->get_dynamic_field(*m_thread_dyn_field_accessor.get(), last_t_tot_cpu); if(last_t_tot_cpu != 0) { uint64_t deltaval = tcpu - last_t_tot_cpu; @@ -3137,7 +3138,8 @@ uint8_t* sinsp_filter_check_thread::extract(sinsp_evt *evt, OUT uint32_t* len, b if(tinfo != NULL) { - uint64_t ptot = tinfo->get_dynamic_field(*m_thread_dyn_field_accessor.get()); + uint64_t ptot = 0; + tinfo->get_dynamic_field(*m_thread_dyn_field_accessor.get(), ptot); m_u64val += ptot; tinfo->set_dynamic_field(*m_thread_dyn_field_accessor.get(), m_u64val); RETURN_EXTRACT_VAR(m_u64val); diff --git a/userspace/libsinsp/parsers.cpp b/userspace/libsinsp/parsers.cpp index cf883c6078..a44dd12604 100644 --- a/userspace/libsinsp/parsers.cpp +++ b/userspace/libsinsp/parsers.cpp @@ -934,7 +934,7 @@ void sinsp_parser::store_event(sinsp_evt *evt) // to drop the information it carries. // #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_store_drops++; + m_inspector->m_stats->m_n_store_drops++; #endif return; } @@ -969,7 +969,7 @@ void sinsp_parser::store_event(sinsp_evt *evt) tinfo->m_lastevent_cpuid = evt->get_cpuid(); #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_stored_evts++; + m_inspector->m_stats->m_n_stored_evts++; #endif } @@ -993,7 +993,7 @@ bool sinsp_parser::retrieve_enter_event(sinsp_evt *enter_evt, sinsp_evt *exit_ev // can be truncated // #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_retrieve_drops++; + m_inspector->m_stats->m_n_retrieve_drops++; #endif return false; } @@ -1015,7 +1015,7 @@ bool sinsp_parser::retrieve_enter_event(sinsp_evt *enter_evt, sinsp_evt *exit_ev enter_evt->get_type() == PPME_SYSCALL_EXECVEAT_E) { #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_retrieved_evts++; + m_inspector->m_stats->m_n_retrieved_evts++; #endif return true; } @@ -1029,13 +1029,13 @@ bool sinsp_parser::retrieve_enter_event(sinsp_evt *enter_evt, sinsp_evt *exit_ev //ASSERT(false); exit_evt->m_tinfo->set_lastevent_data_validity(false); #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_retrieve_drops++; + m_inspector->m_stats->m_n_retrieve_drops++; #endif return false; } #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_retrieved_evts++; + m_inspector->m_stats->m_n_retrieved_evts++; #endif return true; } @@ -3681,12 +3681,12 @@ void sinsp_parser::parse_close_exit(sinsp_evt *evt) // increment of m_n_failed_fd_lookups (for the enter event too if there's one). // #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_failed_fd_lookups--; + m_inspector->m_stats->m_n_failed_fd_lookups--; #endif if(evt->m_tinfo && evt->m_tinfo->is_lastevent_data_valid()) { #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_failed_fd_lookups--; + m_inspector->m_stats->m_n_failed_fd_lookups--; #endif } } diff --git a/userspace/libsinsp/plugin.cpp b/userspace/libsinsp/plugin.cpp index 02f9b0d49c..79578f1b6b 100755 --- a/userspace/libsinsp/plugin.cpp +++ b/userspace/libsinsp/plugin.cpp @@ -162,13 +162,15 @@ bool sinsp_plugin::init(const std::string &config, std::string &errstr) ss_plugin_init_input in; ss_plugin_init_tables_input tables_in; + ss_plugin_table_fields_vtable_ext table_fields_ext; in.owner = this; in.get_owner_last_error = sinsp_plugin::get_owner_last_error; in.tables = NULL; in.config = conf.c_str(); if (m_caps & (CAP_PARSING | CAP_EXTRACTION)) { - sinsp_plugin::table_field_api(tables_in.fields); + tables_in.fields_ext = &table_fields_ext; + sinsp_plugin::table_field_api(tables_in.fields, table_fields_ext); tables_in.list_tables = sinsp_plugin::table_api_list_tables; tables_in.get_table = sinsp_plugin::table_api_get_table; tables_in.add_table = sinsp_plugin::table_api_add_table; @@ -190,6 +192,18 @@ bool sinsp_plugin::init(const std::string &config, std::string &errstr) return false; } + // resolve post-init event code filters + if (m_caps & CAP_EXTRACTION) + { + resolve_dylib_compatible_codes(m_handle->api.get_extract_event_types, + m_extract_event_sources, m_extract_event_codes); + } + if (m_caps & CAP_PARSING) + { + resolve_dylib_compatible_codes(m_handle->api.get_parse_event_types, + m_parse_event_sources, m_parse_event_codes); + } + return true; } @@ -289,7 +303,7 @@ void sinsp_plugin::resolve_dylib_field_arg(Json::Value root, filtercheck_field_i // this logic is shared between the field extraction and event parsing caps void sinsp_plugin::resolve_dylib_compatible_codes( - uint16_t *(*get_codes)(uint32_t *numtypes), + uint16_t *(*get_codes)(uint32_t*,ss_plugin_t*), const std::unordered_set& sources, libsinsp::events::set& codes) { @@ -297,7 +311,7 @@ void sinsp_plugin::resolve_dylib_compatible_codes( if (get_codes != NULL) { uint32_t ntypes = 0; - auto types = get_codes(&ntypes); + auto types = get_codes(&ntypes, m_state); if (types) { for (uint32_t i = 0; i < ntypes; i++) @@ -401,6 +415,13 @@ bool sinsp_plugin::resolve_dylib_symbols(std::string &errstr) errstr = "plugin provided an invalid version string: '" + version_str + "'"; return false; } + std::string req_api_version_str = str_from_alloc_charbuf(m_handle->api.get_required_api_version()); + m_required_api_version = sinsp_version(req_api_version_str); + if(!m_required_api_version.m_valid) + { + errstr = "plugin provided an invalid required api version string: '" + req_api_version_str + "'"; + return false; + } // read capabilities and process their info m_caps = plugin_get_capabilities(m_handle, err); @@ -537,16 +558,12 @@ bool sinsp_plugin::resolve_dylib_symbols(std::string &errstr) resolve_dylib_compatible_sources("get_extract_event_sources", m_handle->api.get_extract_event_sources, m_extract_event_sources); - resolve_dylib_compatible_codes(m_handle->api.get_extract_event_types, - m_extract_event_sources, m_extract_event_codes); } if(m_caps & CAP_PARSING) { resolve_dylib_compatible_sources("get_parse_event_sources", m_handle->api.get_parse_event_sources, m_parse_event_sources); - resolve_dylib_compatible_codes(m_handle->api.get_parse_event_types, - m_parse_event_sources, m_parse_event_codes); } if(m_caps & CAP_ASYNC) @@ -570,6 +587,24 @@ std::string sinsp_plugin::get_init_schema(ss_plugin_schema_type& schema_type) co return std::string(""); } +const libsinsp::events::set& sinsp_plugin::extract_event_codes() const +{ + if (!m_inited) + { + throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name); + } + return m_extract_event_codes; +} + +const libsinsp::events::set& sinsp_plugin::parse_event_codes() const +{ + if (!m_inited) + { + throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name); + } + return m_parse_event_codes; +} + void sinsp_plugin::validate_init_config(std::string& config) { ss_plugin_schema_type schema_type; @@ -803,11 +838,13 @@ bool sinsp_plugin::extract_fields(sinsp_evt* evt, uint32_t num_fields, ss_plugin ev.evtsrc = evt->get_source_name(); ss_plugin_field_extract_input in; + ss_plugin_table_reader_vtable_ext table_reader_ext; in.num_fields = num_fields; in.fields = fields; in.owner = (ss_plugin_owner_t *) this; in.get_owner_last_error = sinsp_plugin::get_owner_last_error; - sinsp_plugin::table_read_api(in.table_reader); + in.table_reader_ext = &table_reader_ext; + sinsp_plugin::table_read_api(in.table_reader, table_reader_ext); return m_handle->api.extract_fields(m_state, &ev, &in) == SS_PLUGIN_SUCCESS; } @@ -828,10 +865,14 @@ bool sinsp_plugin::parse_event(sinsp_evt* evt) const ev.evtsrc = evt->get_source_name(); ss_plugin_event_parse_input in; + ss_plugin_table_reader_vtable_ext table_reader_ext; + ss_plugin_table_writer_vtable_ext table_writer_ext; in.owner = (ss_plugin_owner_t *) this; in.get_owner_last_error = sinsp_plugin::get_owner_last_error; - sinsp_plugin::table_read_api(in.table_reader); - sinsp_plugin::table_write_api(in.table_writer); + in.table_reader_ext = &table_reader_ext; + in.table_writer_ext = &table_writer_ext; + sinsp_plugin::table_read_api(in.table_reader, table_reader_ext); + sinsp_plugin::table_write_api(in.table_writer, table_writer_ext); auto res = m_handle->api.parse_event(m_state, &ev, &in); return res == SS_PLUGIN_SUCCESS; diff --git a/userspace/libsinsp/plugin.h b/userspace/libsinsp/plugin.h index 555701580a..70d14bd540 100755 --- a/userspace/libsinsp/plugin.h +++ b/userspace/libsinsp/plugin.h @@ -184,10 +184,7 @@ class sinsp_plugin return m_extract_event_sources; } - inline const libsinsp::events::set& extract_event_codes() const - { - return m_extract_event_codes; - } + const libsinsp::events::set& extract_event_codes() const; inline const std::vector& fields() const { @@ -202,10 +199,7 @@ class sinsp_plugin return m_parse_event_sources; } - inline const libsinsp::events::set& parse_event_codes() const - { - return m_parse_event_codes; - } + const libsinsp::events::set& parse_event_codes() const; bool parse_event(sinsp_evt* evt) const; @@ -254,9 +248,9 @@ class sinsp_plugin libsinsp::events::set m_extract_event_codes; /** Event Parsing **/ - struct table_input_deleter { void operator()(ss_plugin_table_input* r); }; + struct accessed_table_input_deleter { void operator()(ss_plugin_table_input* r); }; using owned_table_t = std::unique_ptr; - using accessed_table_t = std::unique_ptr; + using accessed_table_t = std::unique_ptr; std::unordered_set m_parse_event_sources; libsinsp::events::set m_parse_event_codes; std::shared_ptr m_table_registry; @@ -278,16 +272,16 @@ class sinsp_plugin const char *(*get_sources)(), std::unordered_set& sources); void resolve_dylib_compatible_codes( - uint16_t *(*get_codes)(uint32_t *numtypes), + uint16_t *(*get_codes)(uint32_t* numtypes,ss_plugin_t* s), const std::unordered_set& sources, libsinsp::events::set& codes); void validate_init_config_json_schema(std::string& config, std::string& schema); static const char* get_owner_last_error(ss_plugin_owner_t* o); /** Event parsing helpers **/ - static void table_field_api(ss_plugin_table_fields_vtable& out); - static void table_read_api(ss_plugin_table_reader_vtable& out); - static void table_write_api(ss_plugin_table_writer_vtable& out); + static void table_field_api(ss_plugin_table_fields_vtable& out, ss_plugin_table_fields_vtable_ext& extout); + static void table_read_api(ss_plugin_table_reader_vtable& out, ss_plugin_table_reader_vtable_ext& extout); + static void table_write_api(ss_plugin_table_writer_vtable& out, ss_plugin_table_writer_vtable_ext& extout); static ss_plugin_table_info* table_api_list_tables(ss_plugin_owner_t* o, uint32_t* ntables); static ss_plugin_table_t *table_api_get_table(ss_plugin_owner_t *o, const char *name, ss_plugin_state_type key_type); static ss_plugin_rc table_api_add_table(ss_plugin_owner_t *o, const ss_plugin_table_input* in); diff --git a/userspace/libsinsp/plugin_table_api.cpp b/userspace/libsinsp/plugin_table_api.cpp index 912f4a55bc..a9841bf0b2 100755 --- a/userspace/libsinsp/plugin_table_api.cpp +++ b/userspace/libsinsp/plugin_table_api.cpp @@ -55,8 +55,8 @@ limitations under the License. static inline ss_plugin_state_type typeinfo_to_state_type(const libsinsp::state::typeinfo& i) { - switch(i.index()) - { + switch(i.index()) + { case libsinsp::state::typeinfo::index_t::PT_INT8: return ss_plugin_state_type::SS_PLUGIN_ST_INT8; case libsinsp::state::typeinfo::index_t::PT_INT16: @@ -77,108 +77,610 @@ static inline ss_plugin_state_type typeinfo_to_state_type(const libsinsp::state: return ss_plugin_state_type::SS_PLUGIN_ST_STRING; case libsinsp::state::typeinfo::index_t::PT_BOOL: return ss_plugin_state_type::SS_PLUGIN_ST_BOOL; - default: - throw sinsp_exception("can't convert typeinfo to plugin state type: " + std::string(i.name())); - } + default: + throw sinsp_exception("can't convert typeinfo to plugin state type: " + std::string(i.name())); + } +} + +template static inline void convert_types(const From& from, To& to) +{ + to = from; +} + +// special cases for strings +template<> inline void convert_types(const std::string& from, const char*& to) +{ + to = from.c_str(); +} + +static void noop_release_table_entry(ss_plugin_table_t*, ss_plugin_table_entry_t*) +{ +} + +static ss_plugin_bool noop_iterate_entries(ss_plugin_table_t*, ss_plugin_table_iterator_func_t, ss_plugin_table_iterator_state_t*) +{ + return 0; +} + +struct owned_table_input_deleter +{ + void operator()(ss_plugin_table_input* in) + { + delete in->reader_ext; + delete in->writer_ext; + delete in->fields_ext; + delete in; + } +}; + +using owned_table_input_t = std::shared_ptr; + +// note(jasondellaluce): here we assume that the api version has major number v3 +// todo(jasondellaluce): update the repairing logic and safety checks +// when switching to a v4 minor/major plugin API version +static inline owned_table_input_t copy_and_check_table_input(const sinsp_plugin* p, const ss_plugin_table_input* in) +{ + std::string errprefix = "failure in adding state table defined by plugin '" + p->name() + "': "; + if (!in) + { + throw sinsp_exception(errprefix + "input is null"); + } + if (!in->name) + { + throw sinsp_exception(errprefix + "name is null"); + } + + owned_table_input_t res( + new ss_plugin_table_input(), owned_table_input_deleter()); + res->name = in->name; + res->key_type = in->key_type; + res->table = in->table; + res->reader = in->reader; + res->writer = in->writer; + res->fields = in->fields; + + // note: before minor v1, we didn't have the "extended" vtables for state tables, + // so we need to recreate them from the information we had available before, + // plus adding "no-op" implementations of all the functions not available before v1 + res->reader_ext = new ss_plugin_table_reader_vtable_ext(); + res->writer_ext = new ss_plugin_table_writer_vtable_ext(); + res->fields_ext = new ss_plugin_table_fields_vtable_ext(); + if (p->required_api_version().m_version_minor < 1) + { + res->reader_ext->get_table_name = res->reader.get_table_name; + res->reader_ext->get_table_size = res->reader.get_table_size; + res->reader_ext->get_table_entry = res->reader.get_table_entry; + res->reader_ext->read_entry_field = res->reader.read_entry_field; + res->reader_ext->release_table_entry = noop_release_table_entry; + res->reader_ext->iterate_entries = noop_iterate_entries; + + res->writer_ext->clear_table = res->writer.clear_table; + res->writer_ext->erase_table_entry = res->writer.erase_table_entry; + res->writer_ext->create_table_entry = res->writer.create_table_entry; + res->writer_ext->destroy_table_entry = res->writer.destroy_table_entry; + res->writer_ext->add_table_entry = res->writer.add_table_entry; + res->writer_ext->write_entry_field = res->writer.write_entry_field; + + res->fields_ext->list_table_fields = res->fields.list_table_fields; + res->fields_ext->get_table_field = res->fields.get_table_field; + res->fields_ext->add_table_field = res->fields.add_table_field; + } + else + { + if (!in->reader_ext || !in->writer_ext || !in->fields_ext) + { + throw sinsp_exception(errprefix + "extended vtables must all be defined"); + } + + res->reader_ext->get_table_name = in->reader_ext->get_table_name; + res->reader_ext->get_table_size = in->reader_ext->get_table_size; + res->reader_ext->get_table_entry = in->reader_ext->get_table_entry; + res->reader_ext->read_entry_field = in->reader_ext->read_entry_field; + res->reader_ext->release_table_entry = in->reader_ext->release_table_entry; + res->reader_ext->iterate_entries = in->reader_ext->iterate_entries; + + res->writer_ext->clear_table = in->writer_ext->clear_table; + res->writer_ext->erase_table_entry = in->writer_ext->erase_table_entry; + res->writer_ext->create_table_entry = in->writer_ext->create_table_entry; + res->writer_ext->destroy_table_entry = in->writer_ext->destroy_table_entry; + res->writer_ext->add_table_entry = in->writer_ext->add_table_entry; + res->writer_ext->write_entry_field = in->writer_ext->write_entry_field; + + res->fields_ext->list_table_fields = in->fields_ext->list_table_fields; + res->fields_ext->get_table_field = in->fields_ext->get_table_field; + res->fields_ext->add_table_field = in->fields_ext->add_table_field; + } + + if ((!res->reader_ext->get_table_name || res->reader_ext->get_table_name != res->reader.get_table_name) || + (!res->reader_ext->get_table_size || res->reader_ext->get_table_size != res->reader.get_table_size) || + (!res->reader_ext->get_table_entry || res->reader_ext->get_table_entry != res->reader.get_table_entry) || + (!res->reader_ext->read_entry_field || res->reader_ext->read_entry_field != res->reader.read_entry_field) || + !res->reader_ext->release_table_entry || + !res->reader_ext->iterate_entries) + { + throw sinsp_exception(errprefix + "broken or inconsistent reader vtables"); + } + + if((!res->writer_ext->clear_table || res->writer_ext->clear_table != res->writer.clear_table) || + (!res->writer_ext->erase_table_entry || res->writer_ext->erase_table_entry != res->writer.erase_table_entry) || + (!res->writer_ext->create_table_entry || res->writer_ext->create_table_entry != res->writer.create_table_entry) || + (!res->writer_ext->destroy_table_entry || res->writer_ext->destroy_table_entry != res->writer.destroy_table_entry) || + (!res->writer_ext->add_table_entry || res->writer_ext->add_table_entry != res->writer.add_table_entry) || + (!res->writer_ext->write_entry_field || res->writer_ext->write_entry_field != res->writer.write_entry_field)) + { + throw sinsp_exception(errprefix + "broken or inconsistent writer vtables"); + } + + if((!res->fields_ext->list_table_fields || res->fields_ext->list_table_fields != res->fields.list_table_fields) || + (!res->fields_ext->get_table_field || res->fields_ext->get_table_field != res->fields.get_table_field) || + (!res->fields_ext->add_table_field || res->fields_ext->add_table_field != res->fields.add_table_field)) + { + throw sinsp_exception(errprefix + "broken or inconsistent fields vtables"); + } + + return res; +} + +static inline std::string table_input_error_prefix(const sinsp_plugin* o, ss_plugin_table_input* i) +{ + return "error in state table '" + std::string(i->name) + "' defined by plugin '" + o->name() + "': "; } // wraps instances of ss_plugin_table_input and makes them comply // to the libsinsp::state::table state tables definition. -// note(jasondellaluce): for now, we don't support accessing plugin-owned -// tables from sinsp internal components, and as such this wrapper simply -// throws exceptions in case unsafe access is detected. This choice is for both -// reducing the system's complexity, and also because the C -> C++ type -// conversions can become tricky and unsafe, specially given that plugins -// can be implemented in other languages. template struct plugin_table_wrapper: public libsinsp::state::table { - plugin_table_wrapper(const sinsp_plugin* o, const ss_plugin_table_input* i) - : libsinsp::state::table(i->name, libsinsp::state::static_struct::field_infos()), - m_owner(o), m_input(*i) - { - auto t = libsinsp::state::typeinfo::of(); - if (m_input.key_type != typeinfo_to_state_type(t)) - { - throw sinsp_exception("invalid key type for plugin-owned table: " + std::string(t.name())); - } - } + using ss = libsinsp::state::static_struct; - virtual ~plugin_table_wrapper() = default; - plugin_table_wrapper(plugin_table_wrapper&&) = default; - plugin_table_wrapper& operator = (plugin_table_wrapper&&) = default; - plugin_table_wrapper(const plugin_table_wrapper& s) = delete; - plugin_table_wrapper& operator = (const plugin_table_wrapper& s) = delete; + using ds = libsinsp::state::dynamic_struct; + + struct plugin_field_infos: public ds::field_infos + { + plugin_field_infos( + const sinsp_plugin* o, + const owned_table_input_t& i) + : field_infos(), m_owner(o), m_input(i), m_accessors() {}; + plugin_field_infos(plugin_field_infos&&) = default; + plugin_field_infos& operator = (plugin_field_infos&&) = default; + plugin_field_infos(const plugin_field_infos& s) = delete; + plugin_field_infos& operator = (const plugin_field_infos& s) = delete; + virtual ~plugin_field_infos() = default; + + const sinsp_plugin* m_owner; + owned_table_input_t m_input; + std::vector m_accessors; + + virtual const std::unordered_map& fields() override + { + // list all the fields of the plugin table + uint32_t nfields = 0; + auto res = m_input->fields_ext->list_table_fields(m_input->table, &nfields); + if (res == NULL) + { + throw sinsp_exception(table_input_error_prefix(m_owner, m_input.get()) + "list fields failure: " + m_owner->get_last_error()); + } - const sinsp_plugin* m_owner; - ss_plugin_table_input m_input; + // if there's a different number of fields that in our local copy, + // we re-add all of them. Duplicate definitions will be skipped + // anyways. Note, we set the index of each field info to the order + // index of the first time we received it from the plugin. This is + // relevant because the plugin API does not give guarantees about + // order stability of the returned array of field infos. + if (nfields != ds::field_infos::fields().size()) + { + for (uint32_t i = 0; i < nfields; i++) + { + ds::field_info f; + #define _X(_type, _dtype) \ + { \ + f = ds::field_info::build<_type>(res[i].name, i, this, res[i].read_only); \ + } + __PLUGIN_STATETYPE_SWITCH(res[i].field_type); + #undef _X + ds::field_infos::add_field(f); + } + } - inline std::string invalid_access_msg() const + // at this point, our local copy of the field infos should be consistent + // with what's known by the plugin. So, we make sure we create an + // accessor for each of the field infos. Note, each field is associated + // an accessor that has an array position equal to the field's index. + // This will be used later for instant retrieval of the accessors + // during read-write operations. + const auto& ret = ds::field_infos::fields(); + for (const auto& it : ret) + { + const auto& f = it.second; + while (m_accessors.size() <= f.index()) + { + m_accessors.push_back(nullptr); + } + if (m_accessors[f.index()] == nullptr) + { + auto facc = m_input->fields_ext->get_table_field(m_input->table, f.name().c_str(), typeinfo_to_state_type(f.info())); + if (facc == NULL) + { + throw sinsp_exception(table_input_error_prefix(m_owner, m_input.get()) + "get table field failure: " + m_owner->get_last_error()); + } + m_accessors[f.index()] = facc; + } + } + return ret; + } + + virtual const ds::field_info& add_field(const ds::field_info& field) override + { + auto ret = m_input->fields_ext->add_table_field(m_input->table, field.name().c_str(), typeinfo_to_state_type(field.info())); + if (ret == NULL) + { + throw sinsp_exception(table_input_error_prefix(m_owner, m_input.get()) + "add table field failure: " + m_owner->get_last_error()); + } + + // after adding a new field, we retrieve the whole list again + // to trigger the local copy updates and make sure we're in a + // consistent state. This is necessary because we we add a field, + // we have no guarantee that other components haven't added other + // fields too and we need to get their info as well. + this->fields(); + + // lastly, we leverage the base-class implementation to obtain + // a reference from our local field definitions copy. + return ds::field_infos::add_field(field); + } + }; + + struct plugin_table_entry: public libsinsp::state::table_entry + { + plugin_table_entry( + sinsp_plugin* o, + const owned_table_input_t& i, + const std::shared_ptr& fields, + ss_plugin_table_entry_t* e, + bool detached): + table_entry(fields), + m_owner(o), + m_input(i), + m_entry(e), + m_detached(detached) {}; + plugin_table_entry(const plugin_table_entry& o) = delete; + plugin_table_entry& operator = (const plugin_table_entry& o) = delete; + plugin_table_entry(plugin_table_entry&& o) = default; + plugin_table_entry& operator = (plugin_table_entry&& o) = default; + virtual ~plugin_table_entry() + { + if (m_entry) + { + if (m_detached) + { + m_input->writer_ext->destroy_table_entry(m_input->table, m_entry); + } + else + { + m_input->reader_ext->release_table_entry(m_input->table, m_entry); + } + } + } + + sinsp_plugin* m_owner; + owned_table_input_t m_input; + ss_plugin_table_entry_t* m_entry; + bool m_detached; + + // note(jasondellaluce): dynamic cast is expensive but this is not expected + // to ever be ever invoked, because we set the fields shared pointer + // at construction time. This is just here as a consistency fence in + // case of misuse. + virtual void set_dynamic_fields(const std::shared_ptr& defs) override + { + if (defs && dynamic_cast(defs.get()) == nullptr) + { + throw sinsp_exception(table_input_error_prefix(m_owner, m_input.get()) + "plugin table can only be set with plugin dynamic fields"); + } + table_entry::set_dynamic_fields(defs); + } + + virtual void get_dynamic_field(const ds::field_info& i, void* out) override + { + const auto& infos = get_plugin_field_infos(); + ss_plugin_state_data dout; + auto rc = m_input->reader_ext->read_entry_field(m_input->table, m_entry, infos.m_accessors[i.index()], &dout); + if (rc != SS_PLUGIN_SUCCESS) + { + throw sinsp_exception(table_input_error_prefix(m_owner, m_input.get()) + "read field failure: " + m_owner->get_last_error()); + } + + // note: strings are the only exception to the switch case below, + // because they are represented as std::string in libsinsp' typeinfo + // and as const char*s by the plugin API. + // todo(jasondellaluce): maybe find a common place for all this + // type conversions knowledge (also leaked in dynamic_struct.h) + if (i.info().index() == libsinsp::state::typeinfo::index_t::PT_CHARBUF) + { + *(const char**) out = dout.str; + } + else + { + #define _X(_type, _dtype) \ + { \ + *((_type*) out) = dout._dtype; \ + } + __PLUGIN_STATETYPE_SWITCH(typeinfo_to_state_type(i.info())); + #undef _X + } + } + + virtual void set_dynamic_field(const ds::field_info& i, const void* in) override + { + const auto& infos = get_plugin_field_infos(); + ss_plugin_state_data v; + + // note: strings are the only exception to the switch case below, + // because they are represented as std::string in libsinsp' typeinfo + // and as const char*s by the plugin API. + // todo(jasondellaluce): maybe find a common place for all this + // type conversions knowledge (also leaked in dynamic_struct.h) + if (i.info().index() == libsinsp::state::typeinfo::index_t::PT_CHARBUF) + { + v.str = *(const char**) in; + } + else + { + #define _X(_type, _dtype) \ + { \ + convert_types(*((_type*) in), v._dtype); \ + } + __PLUGIN_STATETYPE_SWITCH(typeinfo_to_state_type(i.info())); + #undef _X + } + + auto rc = m_input->writer_ext->write_entry_field(m_input->table, m_entry, infos.m_accessors[i.index()], &v); + if (rc != SS_PLUGIN_SUCCESS) + { + throw sinsp_exception(table_input_error_prefix(m_owner, m_input.get()) + "write field failure: " + m_owner->get_last_error()); + } + } + private: + const plugin_field_infos& get_plugin_field_infos() const + { + if (dynamic_fields() == nullptr) + { + throw sinsp_exception(table_input_error_prefix(m_owner, m_input.get()) + "local fields definitions not set"); + } + // note: casting should be safe because we force the + // plugin_field_infos subtype both the constructor and the setter + ASSERT(dynamic_cast(dynamic_fields().get()) != nullptr); + return *static_cast(dynamic_fields().get()); + } + }; + + plugin_table_wrapper(sinsp_plugin* o, const ss_plugin_table_input* i) + : libsinsp::state::table(i->name, ss::field_infos()), + m_owner(o), + m_input(copy_and_check_table_input(o, i)), + m_static_fields(), + m_dyn_fields(std::make_shared(o, m_input)) { - return "can't access plugin-owned table '" + this->name() + "' from inspector"; + auto t = libsinsp::state::typeinfo::of(); + if (m_input->key_type != typeinfo_to_state_type(t)) + { + throw sinsp_exception(table_input_error_prefix(m_owner, m_input.get()) + "invalid key type: " + std::string(t.name())); + } } + virtual ~plugin_table_wrapper() = default; + plugin_table_wrapper(plugin_table_wrapper&&) = default; + plugin_table_wrapper& operator = (plugin_table_wrapper&&) = default; + plugin_table_wrapper(const plugin_table_wrapper& s) = delete; + plugin_table_wrapper& operator = (const plugin_table_wrapper& s) = delete; + + sinsp_plugin* m_owner; + owned_table_input_t m_input; + libsinsp::state::static_struct::field_infos m_static_fields; + std::shared_ptr m_dyn_fields; + const libsinsp::state::static_struct::field_infos& static_fields() const override - { - throw sinsp_exception(invalid_access_msg()); - } + { + // note: always empty, plugin-defined table have no "static" fields, + // all of them are dynamically-discovered at runtime + return m_static_fields; + } - const std::shared_ptr& dynamic_fields() const override - { - throw sinsp_exception(invalid_access_msg()); - } + std::shared_ptr dynamic_fields() const override + { + return m_dyn_fields; + } size_t entries_count() const override { - throw sinsp_exception(invalid_access_msg()); + auto res = m_input->reader_ext->get_table_size(m_input->table); + if (res == (uint64_t) -1) + { + throw sinsp_exception(table_input_error_prefix(m_owner, m_input.get()) + "get size failure: " + m_owner->get_last_error()); + } + return (size_t) res; } void clear_entries() override { - throw sinsp_exception(invalid_access_msg()); + auto res = m_input->writer_ext->clear_table(m_input->table); + if (res != SS_PLUGIN_SUCCESS) + { + throw sinsp_exception(table_input_error_prefix(m_owner, m_input.get()) + "clear entries failure: " + m_owner->get_last_error()); + } } - bool foreach_entry(std::function pred) override + // used only for foreach_entry below + struct table_iterator_state { - throw sinsp_exception(invalid_access_msg()); + std::string err; + plugin_table_entry* m_entry; + std::function* m_it; + }; + + // used only for foreach_entry below + static ss_plugin_bool table_iterator_func(ss_plugin_table_iterator_state_t *s, ss_plugin_table_entry_t *_e) + { + auto state = static_cast(s); + state->m_entry->m_entry = _e; + __CATCH_ERR_MSG(state->err, { + return (*state->m_it)(*state->m_entry) ? 1 : 0; + }); + return 0; } - std::unique_ptr new_entry() const override + bool foreach_entry(std::function pred) override { - throw sinsp_exception(invalid_access_msg()); + plugin_table_entry entry(m_owner, m_input, m_dyn_fields, NULL, false); + table_iterator_state state; + state.m_it = &pred; + state.m_entry = &entry; + auto s = static_cast(&state); + if (m_input->reader_ext->iterate_entries(m_input->table, table_iterator_func, s) == 0) + { + // avoids invoking release_table_entry + entry.m_entry = NULL; + if (!state.err.empty()) + { + throw sinsp_exception(table_input_error_prefix(m_owner, m_input.get()) + "iterate entries failure: " + state.err); + } + return false; + } + // avoids invoking release_table_entry + entry.m_entry = NULL; + return true; + } + + std::unique_ptr new_entry() const override + { + auto res = m_input->writer_ext->create_table_entry(m_input->table); + if (res == NULL) + { + throw sinsp_exception(table_input_error_prefix(m_owner, m_input.get()) + "create entry failure: " + m_owner->get_last_error()); + } + return std::unique_ptr(new plugin_table_entry(m_owner, m_input, m_dyn_fields, res, true)); } std::shared_ptr get_entry(const KeyType& key) override { - throw sinsp_exception(invalid_access_msg()); + ss_plugin_state_data keydata; + get_key_as_data(key, keydata); + auto res = m_input->reader_ext->get_table_entry(m_input->table, &keydata); + if (res == NULL) + { + // note: libsinsp::state::table expects nullptr to be returned + // instead of an error exception + return nullptr; + } + return std::shared_ptr(new plugin_table_entry(m_owner, m_input, m_dyn_fields, res, false)); } - std::shared_ptr add_entry(const KeyType& key, std::unique_ptr e) override + std::shared_ptr add_entry(const KeyType& key, std::unique_ptr e) override { - throw sinsp_exception(invalid_access_msg()); + if (!e) + { + throw sinsp_exception(table_input_error_prefix(m_owner, m_input.get()) + "add entry invoked with null entry"); + } + + // we have no formal way for checking for misuses in which the invoker + // adds an entry that has not been created buy this table (with + // the right sub-type). Dynamic cast would be too expensive at this + // level, so we just enable it in debug mode. + ASSERT(dynamic_cast(e.get()) != nullptr); + plugin_table_entry* entry = static_cast(e.get()); + ASSERT(entry->m_entry != nullptr); + + ss_plugin_state_data keydata; + get_key_as_data(key, keydata); + auto res = m_input->writer_ext->add_table_entry(m_input->table, &keydata, entry->m_entry); + if (res == NULL) + { + throw sinsp_exception(table_input_error_prefix(m_owner, m_input.get()) + "add entry failure: " + m_owner->get_last_error()); + } + entry->m_entry = res; + entry->m_detached = false; + return std::shared_ptr(std::move(e)); } - bool erase_entry(const KeyType& key) override + bool erase_entry(const KeyType& key) override { - throw sinsp_exception(invalid_access_msg()); + ss_plugin_state_data keydata; + get_key_as_data(key, keydata); + auto res = m_input->writer_ext->erase_table_entry(m_input->table, &keydata); + // note: in case of failure, libsinsp::state::table expects false + // to be returned instead of an error exception + return res == SS_PLUGIN_SUCCESS; } + + private: + static void get_key_as_data(const KeyType& key, ss_plugin_state_data& out); }; +template<> void plugin_table_wrapper::get_key_as_data(const int8_t& key, ss_plugin_state_data& out) +{ + out.s8 = key; +} + +template<> void plugin_table_wrapper::get_key_as_data(const int16_t& key, ss_plugin_state_data& out) +{ + out.s16 = key; +} + +template<> void plugin_table_wrapper::get_key_as_data(const int32_t& key, ss_plugin_state_data& out) +{ + out.s32 = key; +} + +template<> void plugin_table_wrapper::get_key_as_data(const int64_t& key, ss_plugin_state_data& out) +{ + out.s64 = key; +} + +template<> void plugin_table_wrapper::get_key_as_data(const uint8_t& key, ss_plugin_state_data& out) +{ + out.u8 = key; +} + +template<> void plugin_table_wrapper::get_key_as_data(const uint16_t& key, ss_plugin_state_data& out) +{ + out.u16 = key; +} + +template<> void plugin_table_wrapper::get_key_as_data(const uint32_t& key, ss_plugin_state_data& out) +{ + out.u32 = key; +} + +template<> void plugin_table_wrapper::get_key_as_data(const uint64_t& key, ss_plugin_state_data& out) +{ + out.u64 = key; +} + +template<> void plugin_table_wrapper::get_key_as_data(const std::string& key, ss_plugin_state_data& out) +{ + out.str = key.c_str(); +} + +template<> void plugin_table_wrapper::get_key_as_data(const bool& key, ss_plugin_state_data& out) +{ + out.b = key; +} + // wraps instances of libsinsp::state::table and makes them comply // to the plugin API state tables definitions. struct sinsp_table_wrapper { // wraps a dynamic or a static field accessor and its type - struct field_accessor_wrapper - { + struct field_accessor_wrapper + { void* accessor; bool dynamic; ss_plugin_state_type data_type; - }; + }; template explicit sinsp_table_wrapper(sinsp_plugin* p, libsinsp::state::table* t) - : m_owner_plugin(p), m_key_type(typeinfo_to_state_type(t->key_info())), + : m_owner_plugin(p), m_key_type(typeinfo_to_state_type(t->key_info())), m_table(t), m_field_list(), m_table_plugin_owner(nullptr), m_table_plugin_input(nullptr) { // note: if the we're wrapping a plugin-implemented table under the hood, @@ -190,15 +692,15 @@ struct sinsp_table_wrapper if (pt) { m_table_plugin_owner = pt->m_owner; - m_table_plugin_input = &pt->m_input; + m_table_plugin_input = pt->m_input.get(); } } - sinsp_table_wrapper() = delete; - sinsp_table_wrapper(sinsp_table_wrapper&&) = default; - sinsp_table_wrapper& operator = (sinsp_table_wrapper&&) = default; - sinsp_table_wrapper(const sinsp_table_wrapper& s) = delete; - sinsp_table_wrapper& operator = (const sinsp_table_wrapper& s) = delete; + sinsp_table_wrapper() = delete; + sinsp_table_wrapper(sinsp_table_wrapper&&) = default; + sinsp_table_wrapper& operator = (sinsp_table_wrapper&&) = default; + sinsp_table_wrapper(const sinsp_table_wrapper& s) = delete; + sinsp_table_wrapper& operator = (const sinsp_table_wrapper& s) = delete; virtual ~sinsp_table_wrapper() { for (auto& acc : m_field_accessors) @@ -224,10 +726,10 @@ struct sinsp_table_wrapper } sinsp_plugin* m_owner_plugin; - ss_plugin_state_type m_key_type; - libsinsp::state::base_table* m_table; - std::vector m_field_list; - std::unordered_map m_field_accessors; + ss_plugin_state_type m_key_type; + libsinsp::state::base_table* m_table; + std::vector m_field_list; + std::unordered_map m_field_accessors; const sinsp_plugin* m_table_plugin_owner; ss_plugin_table_input* m_table_plugin_input; @@ -238,7 +740,7 @@ struct sinsp_table_wrapper if (t->m_table_plugin_input) { auto pt = t->m_table_plugin_input->table; - auto ret = t->m_table_plugin_input->fields.list_table_fields(pt, nfields); + auto ret = t->m_table_plugin_input->fields_ext->list_table_fields(pt, nfields); if (ret == NULL) { t->m_owner_plugin->m_last_owner_err = t->m_table_plugin_owner->get_last_error(); @@ -277,7 +779,7 @@ struct sinsp_table_wrapper if (t->m_table_plugin_input) { auto pt = t->m_table_plugin_input->table; - auto ret = t->m_table_plugin_input->fields.get_table_field(pt, name, data_type); + auto ret = t->m_table_plugin_input->fields_ext->get_table_field(pt, name, data_type); if (ret == NULL) { t->m_owner_plugin->m_last_owner_err = t->m_table_plugin_owner->get_last_error(); @@ -302,7 +804,7 @@ struct sinsp_table_wrapper // todo(jasondellaluce): plugins are not aware of the difference // between static and dynamic fields. Do we want to enforce // this limitation in the sinsp tables implementation as well? - throw sinsp_exception("field is defined as both static and dynamic"); + throw sinsp_exception("field is defined as both static and dynamic: " + std::string(name)); } }); @@ -321,7 +823,7 @@ struct sinsp_table_wrapper { if (data_type != typeinfo_to_state_type(fixed_it->second.info())) { - throw sinsp_exception("incompatible data types for field: " + std::string(name)); + throw sinsp_exception("incompatible data types for static field: " + std::string(name)); } __PLUGIN_STATETYPE_SWITCH(data_type); } @@ -343,7 +845,7 @@ struct sinsp_table_wrapper { if (data_type != typeinfo_to_state_type(dyn_it->second.info())) { - throw sinsp_exception("incompatible data types for field: " + std::string(name)); + throw sinsp_exception("incompatible data types for dynamic field: " + std::string(name)); } __PLUGIN_STATETYPE_SWITCH(data_type); } @@ -361,7 +863,7 @@ struct sinsp_table_wrapper if (t->m_table_plugin_input) { auto pt = t->m_table_plugin_input->table; - auto ret = t->m_table_plugin_input->fields.add_table_field(pt, name, data_type); + auto ret = t->m_table_plugin_input->fields_ext->add_table_field(pt, name, data_type); if (ret == NULL) { t->m_owner_plugin->m_last_owner_err = t->m_table_plugin_owner->get_last_error(); @@ -369,6 +871,12 @@ struct sinsp_table_wrapper return ret; } + if (t->m_table->static_fields().find(name) != t->m_table->static_fields().end()) + { + t->m_owner_plugin->m_last_owner_err = "can't add dynamic field already defined as static: " + std::string(name); + return NULL; + } + #define _X(_type, _dtype) \ { \ t->m_table->dynamic_fields()->add_field<_type>(name); \ @@ -404,7 +912,7 @@ struct sinsp_table_wrapper if (t->m_table_plugin_input) { auto pt = t->m_table_plugin_input->table; - auto ret = t->m_table_plugin_input->reader.get_table_size(pt); + auto ret = t->m_table_plugin_input->reader_ext->get_table_size(pt); if (ret == ((uint64_t) -1)) { t->m_owner_plugin->m_last_owner_err = t->m_table_plugin_owner->get_last_error(); @@ -425,7 +933,7 @@ struct sinsp_table_wrapper if (t->m_table_plugin_input) { auto pt = t->m_table_plugin_input->table; - auto ret = t->m_table_plugin_input->reader.get_table_entry(pt, key); + auto ret = t->m_table_plugin_input->reader_ext->get_table_entry(pt, key); if (ret == NULL) { t->m_owner_plugin->m_last_owner_err = t->m_table_plugin_owner->get_last_error(); @@ -433,6 +941,11 @@ struct sinsp_table_wrapper return ret; } + // note: the C++ API returns a shared pointer, but in plugins we only + // use raw pointers without increasing/decreasing/owning the refcount. + // How can we do better than this? + // todo(jasondellaluce): should we actually make plugins own some memory, + // to guarantee that the shared_ptr returned is properly refcounted? #define _X(_type, _dtype) \ { \ auto tt = static_cast*>(t->m_table); \ @@ -450,13 +963,53 @@ struct sinsp_table_wrapper return NULL; } - template static inline void convert_types(const From& from, To& to) + static ss_plugin_rc read_entry_field(ss_plugin_table_t* _t, ss_plugin_table_entry_t* _e, const ss_plugin_table_field_t* f, ss_plugin_state_data* out); + + static void release_table_entry(ss_plugin_table_t* _t, ss_plugin_table_entry_t* _e) { - to = from; + auto t = static_cast(_t); + + if (t->m_table_plugin_input) + { + auto pt = t->m_table_plugin_input->table; + t->m_table_plugin_input->reader_ext->release_table_entry(pt, _e); + return; + } + + // there's nothing to do here, because plugins borrow raw pointers + // from libsinsp's tables' entries shared pointers + // todo(jasondellaluce): should we actually make plugins own some memory, + // to guarantee that the shared_ptr returned is properly refcounted? } - static ss_plugin_rc read_entry_field(ss_plugin_table_t* _t, ss_plugin_table_entry_t* _e, const ss_plugin_table_field_t* f, ss_plugin_state_data* out); + static ss_plugin_bool iterate_entries(ss_plugin_table_t* _t, ss_plugin_table_iterator_func_t it, ss_plugin_table_iterator_state_t* s) + { + auto t = static_cast(_t); + if (t->m_table_plugin_input) + { + auto pt = t->m_table_plugin_input->table; + return t->m_table_plugin_input->reader_ext->iterate_entries(pt, it, s); + } + + std::function iter = [it, s](auto& e) + { + return it(s, static_cast(&e)) != 0; + }; + + #define _X(_type, _dtype) \ + { \ + auto tt = static_cast*>(t->m_table); \ + return tt->foreach_entry(iter); \ + } + __CATCH_ERR_MSG(t->m_owner_plugin->m_last_owner_err, { + __PLUGIN_STATETYPE_SWITCH(t->m_key_type); + }); + #undef _X + + return false; + } + static ss_plugin_rc clear(ss_plugin_table_t* _t) { auto t = static_cast(_t); @@ -464,7 +1017,7 @@ struct sinsp_table_wrapper if (t->m_table_plugin_input) { auto pt = t->m_table_plugin_input->table; - auto ret = t->m_table_plugin_input->writer.clear_table(pt); + auto ret = t->m_table_plugin_input->writer_ext->clear_table(pt); if (ret == SS_PLUGIN_FAILURE) { t->m_owner_plugin->m_last_owner_err = t->m_table_plugin_owner->get_last_error(); @@ -486,7 +1039,7 @@ struct sinsp_table_wrapper if (t->m_table_plugin_input) { auto pt = t->m_table_plugin_input->table; - auto ret = t->m_table_plugin_input->writer.erase_table_entry(pt, key); + auto ret = t->m_table_plugin_input->writer_ext->erase_table_entry(pt, key); if (ret == SS_PLUGIN_FAILURE) { t->m_owner_plugin->m_last_owner_err = t->m_table_plugin_owner->get_last_error(); @@ -520,7 +1073,7 @@ struct sinsp_table_wrapper if (t->m_table_plugin_input) { auto pt = t->m_table_plugin_input->table; - auto ret = t->m_table_plugin_input->writer.create_table_entry(pt); + auto ret = t->m_table_plugin_input->writer_ext->create_table_entry(pt); if (ret == NULL) { t->m_owner_plugin->m_last_owner_err = t->m_table_plugin_owner->get_last_error(); @@ -548,7 +1101,7 @@ struct sinsp_table_wrapper if (t->m_table_plugin_input) { auto pt = t->m_table_plugin_input->table; - t->m_table_plugin_input->writer.destroy_table_entry(pt, _e); + t->m_table_plugin_input->writer_ext->destroy_table_entry(pt, _e); } #define _X(_type, _dtype) \ @@ -570,7 +1123,7 @@ struct sinsp_table_wrapper if (t->m_table_plugin_input) { auto pt = t->m_table_plugin_input->table; - auto ret = t->m_table_plugin_input->writer.add_table_entry(pt, key, _e); + auto ret = t->m_table_plugin_input->writer_ext->add_table_entry(pt, key, _e); if (ret == NULL) { t->m_owner_plugin->m_last_owner_err = t->m_table_plugin_owner->get_last_error(); @@ -596,12 +1149,6 @@ struct sinsp_table_wrapper static ss_plugin_rc write_entry_field(ss_plugin_table_t* _t, ss_plugin_table_entry_t* e, const ss_plugin_table_field_t* f, const ss_plugin_state_data* in); }; -// special case for strings -template<> inline void sinsp_table_wrapper::convert_types(const std::string& from, const char*& to) -{ - to = from.c_str(); -} - ss_plugin_rc sinsp_table_wrapper::read_entry_field(ss_plugin_table_t* _t, ss_plugin_table_entry_t* _e, const ss_plugin_table_field_t* f, ss_plugin_state_data* out) { auto t = static_cast(_t); @@ -609,7 +1156,7 @@ ss_plugin_rc sinsp_table_wrapper::read_entry_field(ss_plugin_table_t* _t, ss_plu if (t->m_table_plugin_input) { auto pt = t->m_table_plugin_input->table; - auto ret = t->m_table_plugin_input->reader.read_entry_field(pt, _e, f, out); + auto ret = t->m_table_plugin_input->reader_ext->read_entry_field(pt, _e, f, out); if (ret == SS_PLUGIN_FAILURE) { t->m_owner_plugin->m_last_owner_err = t->m_table_plugin_owner->get_last_error(); @@ -624,12 +1171,12 @@ ss_plugin_rc sinsp_table_wrapper::read_entry_field(ss_plugin_table_t* _t, ss_plu if (a->dynamic) \ { \ auto aa = static_cast*>(a->accessor); \ - convert_types(e->get_dynamic_field(*aa), out->_dtype); \ + e->get_dynamic_field<_type>(*aa, out->_dtype); \ } \ else \ { \ auto aa = static_cast*>(a->accessor); \ - convert_types(e->get_static_field(*aa), out->_dtype); \ + e->get_static_field<_type>(*aa, out->_dtype); \ } \ return SS_PLUGIN_SUCCESS; \ } @@ -647,7 +1194,7 @@ ss_plugin_rc sinsp_table_wrapper::write_entry_field(ss_plugin_table_t* _t, ss_pl if (t->m_table_plugin_input) { auto pt = t->m_table_plugin_input->table; - auto ret = t->m_table_plugin_input->writer.write_entry_field(pt, _e, f, in); + auto ret = t->m_table_plugin_input->writer_ext->write_entry_field(pt, _e, f, in); if (ret == SS_PLUGIN_FAILURE) { t->m_owner_plugin->m_last_owner_err = t->m_table_plugin_owner->get_last_error(); @@ -662,12 +1209,12 @@ ss_plugin_rc sinsp_table_wrapper::write_entry_field(ss_plugin_table_t* _t, ss_pl if (a->dynamic) \ { \ auto aa = static_cast*>(a->accessor); \ - e->set_dynamic_field(*aa, (_type) in->_dtype); \ + e->set_dynamic_field<_type>(*aa, in->_dtype); \ } \ else \ { \ auto aa = static_cast*>(a->accessor); \ - e->set_static_field(*aa, (_type) in->_dtype); \ + e->set_static_field<_type>(*aa, in->_dtype); \ } \ return SS_PLUGIN_SUCCESS; \ } @@ -686,104 +1233,131 @@ ss_plugin_rc sinsp_table_wrapper::write_entry_field(ss_plugin_table_t* _t, ss_pl static ss_plugin_table_fieldinfo* dispatch_list_fields(ss_plugin_table_t *_t, uint32_t *nfields) { auto t = static_cast(_t); - return t->fields.list_table_fields(t->table, nfields); + return t->fields_ext->list_table_fields(t->table, nfields); } static ss_plugin_table_field_t* dispatch_get_field(ss_plugin_table_t* _t, const char* name, ss_plugin_state_type data_type) { auto t = static_cast(_t); - return t->fields.get_table_field(t->table, name, data_type); + return t->fields_ext->get_table_field(t->table, name, data_type); } static ss_plugin_table_field_t* dispatch_add_field(ss_plugin_table_t* _t, const char* name, ss_plugin_state_type data_type) { auto t = static_cast(_t); - return t->fields.add_table_field(t->table, name, data_type); + return t->fields_ext->add_table_field(t->table, name, data_type); } static const char* dispatch_get_name(ss_plugin_table_t* _t) { auto t = static_cast(_t); - return t->reader.get_table_name(t->table); + return t->reader_ext->get_table_name(t->table); } static uint64_t dispatch_get_size(ss_plugin_table_t* _t) { auto t = static_cast(_t); - return t->reader.get_table_size(t->table); + return t->reader_ext->get_table_size(t->table); } static ss_plugin_table_entry_t* dispatch_get_entry(ss_plugin_table_t* _t, const ss_plugin_state_data* key) { auto t = static_cast(_t); - return t->reader.get_table_entry(t->table, key); + return t->reader_ext->get_table_entry(t->table, key); } static ss_plugin_rc dispatch_read_entry_field(ss_plugin_table_t* _t, ss_plugin_table_entry_t* e, const ss_plugin_table_field_t* f, ss_plugin_state_data* out) { auto t = static_cast(_t); - return t->reader.read_entry_field(t->table, e, f, out); + return t->reader_ext->read_entry_field(t->table, e, f, out); +} + +static void dispatch_release_table_entry(ss_plugin_table_t* _t, ss_plugin_table_entry_t* e) +{ + auto t = static_cast(_t); + t->reader_ext->release_table_entry(t->table, e); +} + +static ss_plugin_bool dispatch_iterate_entries(ss_plugin_table_t* _t, ss_plugin_table_iterator_func_t it, ss_plugin_table_iterator_state_t* s) +{ + auto t = static_cast(_t); + return t->reader_ext->iterate_entries(t->table, it, s); } static ss_plugin_rc dispatch_clear(ss_plugin_table_t* _t) { auto t = static_cast(_t); - return t->writer.clear_table(t->table); + return t->writer_ext->clear_table(t->table); } static ss_plugin_rc dispatch_erase_entry(ss_plugin_table_t* _t, const ss_plugin_state_data* key) { auto t = static_cast(_t); - return t->writer.erase_table_entry(t->table, key); + return t->writer_ext->erase_table_entry(t->table, key); } static ss_plugin_table_entry_t* dispatch_create_table_entry(ss_plugin_table_t* _t) { auto t = static_cast(_t); - return t->writer.create_table_entry(t->table); + return t->writer_ext->create_table_entry(t->table); } static void dispatch_destroy_table_entry(ss_plugin_table_t* _t, ss_plugin_table_entry_t* e) { auto t = static_cast(_t); - return t->writer.destroy_table_entry(t->table, e); + return t->writer_ext->destroy_table_entry(t->table, e); } static ss_plugin_table_entry_t* dispatch_add_entry(ss_plugin_table_t* _t, const ss_plugin_state_data* key, ss_plugin_table_entry_t* entry) { auto t = static_cast(_t); - return t->writer.add_table_entry(t->table, key, entry); + return t->writer_ext->add_table_entry(t->table, key, entry); } static ss_plugin_rc dispatch_write_entry_field(ss_plugin_table_t* _t, ss_plugin_table_entry_t* e, const ss_plugin_table_field_t* f, const ss_plugin_state_data* in) { auto t = static_cast(_t); - return t->writer.write_entry_field(t->table, e, f, in); + return t->writer_ext->write_entry_field(t->table, e, f, in); } -void sinsp_plugin::table_field_api(ss_plugin_table_fields_vtable& out) +void sinsp_plugin::table_field_api(ss_plugin_table_fields_vtable& out, ss_plugin_table_fields_vtable_ext& extout) { - out.list_table_fields = dispatch_list_fields; - out.add_table_field = dispatch_add_field; - out.get_table_field = dispatch_get_field; + extout.list_table_fields = dispatch_list_fields; + extout.add_table_field = dispatch_add_field; + extout.get_table_field = dispatch_get_field; + out.list_table_fields = extout.list_table_fields; + out.add_table_field = extout.add_table_field; + out.get_table_field = extout.get_table_field; } -void sinsp_plugin::table_read_api(ss_plugin_table_reader_vtable& out) +void sinsp_plugin::table_read_api(ss_plugin_table_reader_vtable& out, ss_plugin_table_reader_vtable_ext& extout) { - out.get_table_name = dispatch_get_name; - out.get_table_size = dispatch_get_size; - out.get_table_entry = dispatch_get_entry; - out.read_entry_field = dispatch_read_entry_field; + extout.get_table_name = dispatch_get_name; + extout.get_table_size = dispatch_get_size; + extout.get_table_entry = dispatch_get_entry; + extout.read_entry_field = dispatch_read_entry_field; + extout.release_table_entry = dispatch_release_table_entry; + extout.iterate_entries = dispatch_iterate_entries; + out.get_table_name = extout.get_table_name; + out.get_table_size = extout.get_table_size; + out.get_table_entry = extout.get_table_entry; + out.read_entry_field = extout.read_entry_field; } -void sinsp_plugin::table_write_api(ss_plugin_table_writer_vtable& out) +void sinsp_plugin::table_write_api(ss_plugin_table_writer_vtable& out, ss_plugin_table_writer_vtable_ext& extout) { - out.clear_table = dispatch_clear; - out.erase_table_entry = dispatch_erase_entry; - out.create_table_entry = dispatch_create_table_entry; - out.destroy_table_entry = dispatch_destroy_table_entry; - out.add_table_entry = dispatch_add_entry; - out.write_entry_field = dispatch_write_entry_field; + extout.clear_table = dispatch_clear; + extout.erase_table_entry = dispatch_erase_entry; + extout.create_table_entry = dispatch_create_table_entry; + extout.destroy_table_entry = dispatch_destroy_table_entry; + extout.add_table_entry = dispatch_add_entry; + extout.write_entry_field = dispatch_write_entry_field; + out.clear_table = extout.clear_table; + out.erase_table_entry = extout.erase_table_entry; + out.create_table_entry = extout.create_table_entry; + out.destroy_table_entry = extout.destroy_table_entry; + out.add_table_entry = extout.add_table_entry; + out.write_entry_field = extout.write_entry_field; } ss_plugin_table_info* sinsp_plugin::table_api_list_tables(ss_plugin_owner_t* o, uint32_t* ntables) @@ -805,10 +1379,13 @@ ss_plugin_table_info* sinsp_plugin::table_api_list_tables(ss_plugin_owner_t* o, return NULL; } -void sinsp_plugin::table_input_deleter::operator()(ss_plugin_table_input* in) +void sinsp_plugin::accessed_table_input_deleter::operator()(ss_plugin_table_input* in) { - delete static_cast(in->table); - delete in; + delete static_cast(in->table); + delete in->reader_ext; + delete in->writer_ext; + delete in->fields_ext; + delete in; } ss_plugin_table_t* sinsp_plugin::table_api_get_table(ss_plugin_owner_t *o, const char *name, ss_plugin_state_type key_type) @@ -828,24 +1405,42 @@ ss_plugin_table_t* sinsp_plugin::table_api_get_table(ss_plugin_owner_t *o, const { \ return NULL; \ } \ - accessed_table_t res(new ss_plugin_table_input(), table_input_deleter()); \ + accessed_table_t res(new ss_plugin_table_input(), accessed_table_input_deleter()); \ auto state = new sinsp_table_wrapper(p, t); \ res->table = static_cast(state); \ res->name = state->m_table->name().c_str(); \ res->key_type = state->m_key_type; \ - res->fields.list_table_fields = sinsp_table_wrapper::list_fields; \ - res->fields.add_table_field = sinsp_table_wrapper::add_field; \ - res->fields.get_table_field = sinsp_table_wrapper::get_field; \ - res->reader.get_table_name = sinsp_table_wrapper::get_name; \ - res->reader.get_table_size = sinsp_table_wrapper::get_size; \ - res->reader.get_table_entry = sinsp_table_wrapper::get_entry; \ - res->reader.read_entry_field = sinsp_table_wrapper::read_entry_field; \ - res->writer.clear_table = sinsp_table_wrapper::clear; \ - res->writer.erase_table_entry = sinsp_table_wrapper::erase_entry; \ - res->writer.create_table_entry = sinsp_table_wrapper::create_table_entry; \ - res->writer.destroy_table_entry = sinsp_table_wrapper::destroy_table_entry; \ - res->writer.add_table_entry = sinsp_table_wrapper::add_entry; \ - res->writer.write_entry_field = sinsp_table_wrapper::write_entry_field; \ + res->reader_ext = new ss_plugin_table_reader_vtable_ext(); \ + res->writer_ext = new ss_plugin_table_writer_vtable_ext(); \ + res->fields_ext = new ss_plugin_table_fields_vtable_ext(); \ + res->reader_ext->get_table_name = sinsp_table_wrapper::get_name; \ + res->reader_ext->get_table_size = sinsp_table_wrapper::get_size; \ + res->reader_ext->get_table_entry = sinsp_table_wrapper::get_entry; \ + res->reader_ext->read_entry_field = sinsp_table_wrapper::read_entry_field; \ + res->reader_ext->release_table_entry = sinsp_table_wrapper::release_table_entry; \ + res->reader_ext->iterate_entries = sinsp_table_wrapper::iterate_entries; \ + res->writer_ext->clear_table = sinsp_table_wrapper::clear; \ + res->writer_ext->erase_table_entry = sinsp_table_wrapper::erase_entry; \ + res->writer_ext->create_table_entry = sinsp_table_wrapper::create_table_entry; \ + res->writer_ext->destroy_table_entry = sinsp_table_wrapper::destroy_table_entry; \ + res->writer_ext->add_table_entry = sinsp_table_wrapper::add_entry; \ + res->writer_ext->write_entry_field = sinsp_table_wrapper::write_entry_field; \ + res->fields_ext->list_table_fields = sinsp_table_wrapper::list_fields; \ + res->fields_ext->add_table_field = sinsp_table_wrapper::add_field; \ + res->fields_ext->get_table_field = sinsp_table_wrapper::get_field; \ + res->reader.get_table_name = res->reader_ext->get_table_name; \ + res->reader.get_table_size = res->reader_ext->get_table_size; \ + res->reader.get_table_entry = res->reader_ext->get_table_entry; \ + res->reader.read_entry_field = res->reader_ext->read_entry_field; \ + res->writer.clear_table = res->writer_ext->clear_table; \ + res->writer.erase_table_entry = res->writer_ext->erase_table_entry; \ + res->writer.create_table_entry = res->writer_ext->create_table_entry; \ + res->writer.destroy_table_entry = res->writer_ext->destroy_table_entry; \ + res->writer.add_table_entry = res->writer_ext->add_table_entry; \ + res->writer.write_entry_field = res->writer_ext->write_entry_field; \ + res->fields.list_table_fields = res->fields_ext->list_table_fields; \ + res->fields.add_table_field = res->fields_ext->add_table_field; \ + res->fields.get_table_field = res->fields_ext->get_table_field; \ p->m_accessed_tables[name] = std::move(res); \ return p->m_accessed_tables[name].get(); \ }; diff --git a/userspace/libsinsp/sinsp.cpp b/userspace/libsinsp/sinsp.cpp index a1790e145e..fe1c774a31 100644 --- a/userspace/libsinsp/sinsp.cpp +++ b/userspace/libsinsp/sinsp.cpp @@ -40,16 +40,22 @@ limitations under the License. #include "plugin_filtercheck.h" #include "strl.h" -#ifndef CYGWING_AGENT -#ifndef MINIMAL_BUILD +#if !defined(CYGWING_AGENT) +#if !defined(MINIMAL_BUILD) #include "k8s_api_handler.h" -#endif // MINIMAL_BUILD -#ifdef HAS_CAPTURE -#ifndef MINIMAL_BUILD +#if defined(HAS_CAPTURE) #include -#endif // MINIMAL_BUILD -#endif -#endif +#endif // defined(HAS_CAPTURE) +#else // !defined(MINIMAL_BUILD) +struct k8s_api_handler{}; // note: makes the unique_ptr static asserts happy +#endif // !defined(MINIMAL_BUILD) +#endif // !defined(CYGWING_AGENT) + +#ifdef GATHER_INTERNAL_STATS +#include "stats.h" +#else // GATHER_INTERNAL_STATS +struct sinsp_stats{}; // note: makes the unique_ptr static asserts happy +#endif // GATHER_INTERNAL_STATS #ifdef HAS_ANALYZER #include "analyzer_int.h" @@ -72,6 +78,9 @@ sinsp::sinsp(bool static_container, const std::string &static_id, const std::str m_host_root(scap_get_host_root()), m_container_manager(this, static_container, static_id, static_name, static_image), m_usergroup_manager(this), + m_k8s_api_handler(nullptr), + m_k8s_ext_handler(nullptr), + m_stats(nullptr), m_suppressed_comms(), m_inited(false) { @@ -128,6 +137,10 @@ sinsp::sinsp(bool static_container, const std::string &static_id, const std::str m_self_pid = getpid(); #endif +#ifdef GATHER_INTERNAL_STATS + m_stats = std::make_unique; +#endif + m_proc_scan_timeout_ms = SCAP_PROC_SCAN_TIMEOUT_NONE; m_proc_scan_log_interval_ms = SCAP_PROC_SCAN_LOG_NONE; @@ -348,7 +361,7 @@ void sinsp::init() // Basic inits // #ifdef GATHER_INTERNAL_STATS - m_stats.clear(); + m_stats->clear(); #endif m_nevts = 0; @@ -1978,15 +1991,15 @@ sinsp_stats sinsp::get_stats() { scap_get_stats(m_h, &stats); - m_stats.m_n_seen_evts = stats.n_evts; - m_stats.m_n_drops = stats.n_drops; - m_stats.m_n_preemptions = stats.n_preemptions; + m_stats->m_n_seen_evts = stats.n_evts; + m_stats->m_n_drops = stats.n_drops; + m_stats->m_n_preemptions = stats.n_preemptions; } else { - m_stats.m_n_seen_evts = 0; - m_stats.m_n_drops = 0; - m_stats.m_n_preemptions = 0; + m_stats->m_n_seen_evts = 0; + m_stats->m_n_drops = 0; + m_stats->m_n_preemptions = 0; } // @@ -2002,7 +2015,7 @@ sinsp_stats sinsp::get_stats() // Return the result // - return m_stats; + return *m_stats.get(); } #endif // GATHER_INTERNAL_STATS diff --git a/userspace/libsinsp/sinsp.h b/userspace/libsinsp/sinsp.h index 284b89399e..520f3941ee 100644 --- a/userspace/libsinsp/sinsp.h +++ b/userspace/libsinsp/sinsp.h @@ -70,7 +70,6 @@ limitations under the License. #include "event.h" #include "filter.h" #include "dumper.h" -#include "stats.h" #include "ifinfo.h" #include "container.h" #include "user.h" @@ -107,23 +106,20 @@ class sinsp_analyzer; class sinsp_filter; class cycle_writer; class sinsp_protodecoder; -#if !defined(CYGWING_AGENT) && !defined(MINIMAL_BUILD) class k8s; -#endif // !defined(CYGWING_AGENT) && !defined(MINIMAL_BUILD) class sinsp_partial_tracer; class mesos; class sinsp_plugin; class sinsp_plugin_manager; class sinsp_observer; +class sinsp_stats; -#if defined(HAS_CAPTURE) && !defined(_WIN32) class sinsp_ssl; class sinsp_bearer_token; template class socket_data_handler; template class socket_collector; class k8s_handler; class k8s_api_handler; -#endif // HAS_CAPTURE std::vector sinsp_split(const std::string &s, char delim); @@ -998,6 +994,11 @@ class SINSP_PUBLIC sinsp : public capture_stats_source return m_event_sources; } + inline const std::shared_ptr& get_table_registry() const + { + return m_table_registry; + } + uint64_t get_lastevent_ts() const { return m_lastevent_ts; } const std::string& get_host_root() const { return m_host_root; } @@ -1133,12 +1134,10 @@ VISIBILITY_PRIVATE // // Kubernetes // -#if !defined(CYGWING_AGENT) && !defined(MINIMAL_BUILD) std::string* m_k8s_api_server; std::string* m_k8s_api_cert; std::string* m_k8s_node_name; bool m_k8s_node_name_validated = false; -#ifdef HAS_CAPTURE std::shared_ptr m_k8s_ssl; std::shared_ptr m_k8s_bt; std::unique_ptr m_k8s_api_handler; @@ -1152,10 +1151,8 @@ VISIBILITY_PRIVATE "replicasets" }; bool m_k8s_ext_detect_done = false; -#endif // HAS_CAPTURE k8s* m_k8s_client; uint64_t m_k8s_last_watch_time_ns; -#endif // !defined(CYGWING_AGENT) && !defined(MINIMAL_BUILD) // // Mesos/Marathon @@ -1185,12 +1182,9 @@ VISIBILITY_PRIVATE // // Internal stats // -#ifdef GATHER_INTERNAL_STATS - sinsp_stats m_stats; -#endif -#ifdef HAS_ANALYZER + std::unique_ptr m_stats; + std::vector m_tid_collisions; -#endif // // Saved snaplen @@ -1237,12 +1231,10 @@ VISIBILITY_PRIVATE cycle_writer* m_cycle_writer; bool m_write_cycling; -#ifdef SIMULATE_DROP_MODE // // Some dropping infrastructure // bool m_isdropping; -#endif // // App events @@ -1285,9 +1277,9 @@ VISIBILITY_PRIVATE uint64_t m_next_stats_print_time_ns; static unsigned int m_num_possible_cpus; -#if defined(HAS_CAPTURE) + int64_t m_self_pid; -#endif + // // /proc scan parameters diff --git a/userspace/libsinsp/state/dynamic_struct.h b/userspace/libsinsp/state/dynamic_struct.h index f02f18d981..5ac445f59c 100644 --- a/userspace/libsinsp/state/dynamic_struct.h +++ b/userspace/libsinsp/state/dynamic_struct.h @@ -41,7 +41,20 @@ class dynamic_struct class field_info { public: + template + static field_info build(const std::string& name, size_t index, void* defsptr, bool readonly=false) + { + return field_info(name, index, libsinsp::state::typeinfo::of(), defsptr, readonly); + } + + field_info(const std::string& n, size_t in, const typeinfo& i, void* defsptr, bool r) + : m_readonly(r), + m_index(in), + m_name(n), + m_info(i), + m_defsptr(defsptr) {} field_info(): + m_readonly(true), m_index((size_t) -1), m_name(""), m_info(typeinfo::of()), @@ -65,6 +78,14 @@ class dynamic_struct return !(a == b); }; + /** + * @brief Returns true if the field is read only. + */ + bool readonly() const + { + return m_readonly; + } + /** * @brief Returns true if the field info is valid. */ @@ -81,6 +102,14 @@ class dynamic_struct return m_name; } + /** + * @brief Returns the index of the field. + */ + size_t index() const + { + return m_index; + } + /** * @brief Returns the type info of the field. */ @@ -112,18 +141,7 @@ class dynamic_struct } private: - field_info(const std::string& n, size_t in, const typeinfo& i, void* defsptr) - : m_index(in), - m_name(n), - m_info(i), - m_defsptr(defsptr) { } - - template - static field_info _build(const std::string& name, size_t index, void* defsptr) - { - return field_info(name, index, libsinsp::state::typeinfo::of(), defsptr); - } - + bool m_readonly; size_t m_index; std::string m_name; libsinsp::state::typeinfo m_info; @@ -180,11 +198,6 @@ class dynamic_struct field_infos(const field_infos& s) = delete; field_infos& operator = (const field_infos& s) = delete; - inline const std::unordered_map& fields() const - { - return m_definitions; - } - /** * @brief Adds metadata for a new field to the list. An exception is * thrown if two fields are defined with the same name and with @@ -194,26 +207,37 @@ class dynamic_struct * @param name Display name of the field. */ template - const field_info& add_field(const std::string& name) + inline const field_info& add_field(const std::string& name) + { + auto field = field_info::build(name, m_definitions.size(), this); + return add_field(field); + } + + virtual const std::unordered_map& fields() { - const auto &it = m_definitions.find(name); + return m_definitions; + } + +protected: + virtual const field_info& add_field(const field_info& field) + { + const auto &it = m_definitions.find(field.name()); if (it != m_definitions.end()) { - auto t = libsinsp::state::typeinfo::of(); + const auto& t = field.info(); if (it->second.info() != t) { throw sinsp_exception("multiple definitions of dynamic field with different types in struct: " - + name + ", prevtype=" + it->second.info().name() + ", newtype=" + t.name()); + + field.name() + ", prevtype=" + it->second.info().name() + ", newtype=" + t.name()); } return it->second; } - m_definitions.insert({ name, field_info::_build(name, m_definitions.size(), this) }); - const auto& def = m_definitions.at(name); + m_definitions.insert({ field.name(), field }); + const auto& def = m_definitions.at(field.name()); m_definitions_ordered.push_back(&def); return def; } - private: std::unordered_map m_definitions; std::vector m_definitions_ordered; friend class dynamic_struct; @@ -240,29 +264,25 @@ class dynamic_struct /** * @brief Accesses a field with the given accessor and reads its value. */ - template - inline const T& get_dynamic_field(const field_accessor& a) + template + inline void get_dynamic_field(const field_accessor& a, Val& out) { - if (!a.info().valid()) - { - throw sinsp_exception("can't get invalid field in dynamic struct"); - } - _check_defsptr(a.info().m_defsptr); - return *(reinterpret_cast(_access_dynamic_field(a.info().m_index))); + _check_defsptr(a.info(), false); + get_dynamic_field(a.info(), reinterpret_cast(&out)); } /** * @brief Accesses a field with the given accessor and writes its value. */ - template - inline void set_dynamic_field(const field_accessor& a, const T& v) + template + inline void set_dynamic_field(const field_accessor& a, const Val& in) { - if (!a.info().valid()) + _check_defsptr(a.info(), true); + if (a.info().readonly()) { - throw sinsp_exception("can't set invalid field in dynamic struct"); + throw sinsp_exception("can't set a read-only dynamic struct field: " + a.info().name()); } - _check_defsptr(a.info().m_defsptr); - *(reinterpret_cast(_access_dynamic_field(a.info().m_index))) = v; + set_dynamic_field(a.info(), reinterpret_cast(&in)); } /** @@ -278,7 +298,7 @@ class dynamic_struct * The definitions can be set to a non-null value only once, either at * construction time by invoking this method. */ - inline void set_dynamic_fields(const std::shared_ptr& defs) + virtual void set_dynamic_fields(const std::shared_ptr& defs) { if (m_dynamic_fields) { @@ -291,13 +311,60 @@ class dynamic_struct m_dynamic_fields = defs; } +protected: + /** + * @brief Gets the value of a dynamic field and writes it into "out". + * "out" points to a variable having the type of the field_info argument, + * according to the type definitions supported in libsinsp::state::typeinfo. + * For strings, "out" is considered of type const char**. + */ + virtual void get_dynamic_field(const field_info& i, void* out) + { + const auto* buf = _access_dynamic_field(i.m_index); + if (i.info().index() == PT_CHARBUF) + { + *((const char**) out) = ((const std::string*) buf)->c_str(); + } + else + { + memcpy(out, buf, i.info().size()); + } + } + + /** + * @brief Sets the value of a dynamic field by reading it from "in". + * "in" points to a variable having the type of the field_info argument, + * according to the type definitions supported in libsinsp::state::typeinfo. + * For strings, "in" is considered of type const char**. + */ + virtual void set_dynamic_field(const field_info& i, const void* in) + { + auto* buf = _access_dynamic_field(i.m_index); + if (i.info().index() == PT_CHARBUF) + { + *((std::string*) buf) = *((const char**) in); + } + else + { + memcpy(buf, in, i.info().size()); + } + } + private: - inline void _check_defsptr(void* ptr) const + inline void _check_defsptr(const field_info& i, bool write) const { - if (m_dynamic_fields.get() != ptr) + if (!i.valid()) + { + throw sinsp_exception("can't set invalid field in dynamic struct"); + } + if (m_dynamic_fields.get() != i.m_defsptr) { throw sinsp_exception("using dynamic field accessor on struct it was not created from"); } + if (write && i.readonly()) + { + throw sinsp_exception("can't set a read-only dynamic struct field: " + i.name()); + } } inline void* _access_dynamic_field(size_t index) @@ -329,3 +396,40 @@ class dynamic_struct }; // state }; // libsinsp + +// specializations for string types + +template<> inline void libsinsp::state::dynamic_struct::get_dynamic_field( + const field_accessor& a, const char*& out) +{ + _check_defsptr(a.info(), false); + get_dynamic_field(a.info(), reinterpret_cast(&out)); +} + +template<> inline void libsinsp::state::dynamic_struct::get_dynamic_field( + const field_accessor& a, std::string& out) +{ + const char* s = NULL; + get_dynamic_field(a, s); + if (!s) + { + out.clear(); + } + else + { + out = s; + } +} + +template <> inline void libsinsp::state::dynamic_struct::set_dynamic_field( + const field_accessor& a, const char* const& in) +{ + _check_defsptr(a.info(), true); + set_dynamic_field(a.info(), reinterpret_cast(&in)); +} + +template <> inline void libsinsp::state::dynamic_struct::set_dynamic_field( + const field_accessor& a, const std::string& in) +{ + set_dynamic_field(a, in.c_str()); +} diff --git a/userspace/libsinsp/state/static_struct.h b/userspace/libsinsp/state/static_struct.h index 91dcc38428..e336e2a223 100644 --- a/userspace/libsinsp/state/static_struct.h +++ b/userspace/libsinsp/state/static_struct.h @@ -199,12 +199,21 @@ class static_struct return *(reinterpret_cast((void*) (((uintptr_t) this) + a.info().m_offset))); } + /** + * @brief Accesses a field with the given accessor and reads its value. + */ + template + inline void get_static_field(const field_accessor& a, Val& out) const + { + out = get_static_field(a); + } + /** * @brief Accesses a field with the given accessor and writes its value. * An exception is thrown if the field is read-only. */ - template - inline void set_static_field(const field_accessor& a, const T& v) + template + inline void set_static_field(const field_accessor& a, const Val& in) { if (!a.info().valid()) { @@ -214,7 +223,7 @@ class static_struct { throw sinsp_exception("can't set a read-only static struct field: " + a.info().name()); } - *(reinterpret_cast((void*) (((uintptr_t) this) + a.info().m_offset))) = v; + *(reinterpret_cast((void*) (((uintptr_t) this) + a.info().m_offset))) = in; } /** @@ -260,3 +269,10 @@ class static_struct }; // state }; // libsinsp + +// specializations for strings +template <> inline void libsinsp::state::static_struct::get_static_field( + const field_accessor& a, const char*& out) const +{ + out = get_static_field(a).c_str(); +} diff --git a/userspace/libsinsp/state/table.h b/userspace/libsinsp/state/table.h index d8b3beb828..62204f623a 100644 --- a/userspace/libsinsp/state/table.h +++ b/userspace/libsinsp/state/table.h @@ -78,7 +78,7 @@ class base_table * for the value data type of this table. This fields will be accessible * for all the entries of this table. */ - inline virtual const static_struct::field_infos& static_fields() const + virtual const static_struct::field_infos& static_fields() const { return m_static_fields; } @@ -91,7 +91,7 @@ class base_table * be allocated and accessible for all the present and future entries * present in the table. */ - inline virtual const std::shared_ptr& dynamic_fields() const + virtual std::shared_ptr dynamic_fields() const { return m_dynamic_fields; } diff --git a/userspace/libsinsp/test/plugins.ut.cpp b/userspace/libsinsp/test/plugins.ut.cpp index 28fadd583e..06658e2da8 100644 --- a/userspace/libsinsp/test/plugins.ut.cpp +++ b/userspace/libsinsp/test/plugins.ut.cpp @@ -245,6 +245,7 @@ TEST_F(sinsp_with_test_input, plugin_custom_source) TEST(sinsp_plugin, plugin_extract_compatibility) { + std::string tmp; sinsp i; plugin_api api; get_plugin_api_sample_plugin_extract(api); @@ -252,6 +253,7 @@ TEST(sinsp_plugin, plugin_extract_compatibility) // compatible event sources specified, event types not specified api.get_name = [](){ return "p1"; }; auto p = i.register_plugin(&api); + p->init("", tmp); ASSERT_EQ(p->extract_event_sources().size(), 1); ASSERT_TRUE(sinsp_plugin::is_source_compatible(p->extract_event_sources(), "sample")); ASSERT_FALSE(sinsp_plugin::is_source_compatible(p->extract_event_sources(), sinsp_syscall_event_source_name)); @@ -259,14 +261,28 @@ TEST(sinsp_plugin, plugin_extract_compatibility) ASSERT_TRUE(p->extract_event_codes().contains(PPME_PLUGINEVENT_E)); ASSERT_FALSE(p->extract_event_codes().contains(PPME_SYSCALL_OPEN_E)); + // compatible event sources specified, event types specified (config-altered) + api.get_name = [](){ return "p1-2"; }; + p = i.register_plugin(&api); + ASSERT_ANY_THROW(p->extract_event_codes()); // can't be called before init + p->init("322,402", tmp); + ASSERT_EQ(p->extract_event_sources().size(), 1); + ASSERT_TRUE(sinsp_plugin::is_source_compatible(p->extract_event_sources(), "sample")); + ASSERT_FALSE(sinsp_plugin::is_source_compatible(p->extract_event_sources(), sinsp_syscall_event_source_name)); + ASSERT_EQ(p->extract_event_codes().size(), 2); + ASSERT_TRUE(p->extract_event_codes().contains(PPME_PLUGINEVENT_E)); + ASSERT_TRUE(p->extract_event_codes().contains(PPME_ASYNCEVENT_E)); + ASSERT_FALSE(p->extract_event_codes().contains(PPME_SYSCALL_OPEN_E)); + // compatible event sources specified, event types specified api.get_name = [](){ return "p2"; }; - api.get_extract_event_types = [](uint32_t* n) { + api.get_extract_event_types = [](uint32_t* n, ss_plugin_t* s) { static uint16_t ret[] = { PPME_SYSCALL_OPEN_E }; *n = sizeof(ret) / sizeof(uint16_t); return &ret[0]; }; p = i.register_plugin(&api); + p->init("", tmp); ASSERT_EQ(p->extract_event_sources().size(), 1); ASSERT_TRUE(sinsp_plugin::is_source_compatible(p->extract_event_sources(), "sample")); ASSERT_FALSE(sinsp_plugin::is_source_compatible(p->extract_event_sources(), sinsp_syscall_event_source_name)); @@ -279,6 +295,7 @@ TEST(sinsp_plugin, plugin_extract_compatibility) api.get_extract_event_sources = NULL; api.get_extract_event_types = NULL; p = i.register_plugin(&api); + p->init("", tmp); ASSERT_EQ(p->extract_event_sources().size(), 0); ASSERT_TRUE(sinsp_plugin::is_source_compatible(p->extract_event_sources(), "sample")); ASSERT_TRUE(sinsp_plugin::is_source_compatible(p->extract_event_sources(), sinsp_syscall_event_source_name)); @@ -296,6 +313,7 @@ TEST(sinsp_plugin, plugin_extract_compatibility) api.close = src_api.close; api.next_batch = src_api.next_batch; p = i.register_plugin(&api); + p->init("", tmp); ASSERT_EQ(p->extract_event_sources().size(), 1); ASSERT_TRUE(sinsp_plugin::is_source_compatible(p->extract_event_sources(), "sample")); ASSERT_FALSE(sinsp_plugin::is_source_compatible(p->extract_event_sources(), sinsp_syscall_event_source_name)); @@ -422,3 +440,146 @@ TEST_F(sinsp_with_test_input, plugin_syscall_async) m_inspector.close(); ASSERT_EQ(count, max_count); } + +// Scenario we load a plugin that parses any event and plays with the +// thread table, by stressing all the operations supported. After that, we +// also play with the plugin's table from the inspector C++ interface. +// Basically, we are verifying that the sinsp <-> plugin tables access +// is bidirectional and consistent. +TEST_F(sinsp_with_test_input, plugin_tables) +{ + auto& reg = m_inspector.get_table_registry(); + + add_default_init_thread(); + + // the threads table is always present + ASSERT_EQ(reg->tables().size(), 1); + ASSERT_NE(reg->tables().find("threads"), reg->tables().end()); + + // make sure we see a new table when we register the plugin + ASSERT_EQ(reg->tables().find("plugin_sample"), reg->tables().end()); + ASSERT_EQ(reg->get_table("plugin_sample"), nullptr); + register_plugin(&m_inspector, get_plugin_api_sample_tables); + ASSERT_EQ(reg->tables().size(), 2); + ASSERT_NE(reg->tables().find("plugin_sample"), reg->tables().end()); + ASSERT_ANY_THROW(reg->get_table("plugin_sample")); // wrong key type + ASSERT_NE(reg->get_table("plugin_sample"), nullptr); + + // get the plugin table and check its fields and info + auto table = reg->get_table("plugin_sample"); + ASSERT_EQ(table->name(), "plugin_sample"); + ASSERT_EQ(table->entries_count(), 0); + ASSERT_EQ(table->key_info(), libsinsp::state::typeinfo::of()); + ASSERT_EQ(table->static_fields().size(), 0); + ASSERT_EQ(table->dynamic_fields()->fields().size(), 1); + + // get an already existing field form the plugin table + auto sfield = table->dynamic_fields()->fields().find("u64_val"); + ASSERT_NE(sfield, table->dynamic_fields()->fields().end()); + ASSERT_EQ(sfield->second.readonly(), false); + ASSERT_EQ(sfield->second.valid(), true); + ASSERT_EQ(sfield->second.index(), 0); + ASSERT_EQ(sfield->second.name(), "u64_val"); + ASSERT_EQ(sfield->second.info(), libsinsp::state::typeinfo::of()); + + // add a new field in the plugin table + const auto& dfield = table->dynamic_fields()->add_field("str_val"); + ASSERT_NE(table->dynamic_fields()->fields().find("str_val"), table->dynamic_fields()->fields().end()); + ASSERT_EQ(dfield, table->dynamic_fields()->fields().find("str_val")->second); + ASSERT_EQ(dfield.readonly(), false); + ASSERT_EQ(dfield.valid(), true); + ASSERT_EQ(dfield.index(), 1); + ASSERT_EQ(dfield.name(), "str_val"); + ASSERT_EQ(dfield.info(), libsinsp::state::typeinfo::of()); + + // we open a capture and iterate, so that we make sure that all + // the state operations keep working at every round of the loop + open_inspector(); + auto asyncname = "sampleasync"; + auto sample_plugin_evtdata = "hello world"; + uint64_t max_iterations = 10000; + for (uint64_t i = 0; i < max_iterations; i++) + { + auto evt = add_event_advance_ts(increasing_ts(), 1, PPME_ASYNCEVENT_E, 3, (uint32_t) 0, asyncname, scap_const_sized_buffer{&sample_plugin_evtdata, strlen(sample_plugin_evtdata) + 1}); + ASSERT_EQ(evt->get_type(), PPME_ASYNCEVENT_E); + ASSERT_EQ(evt->get_source_idx(), 0); + } + + // we play around with the plugin's table, like if it was a C++ one from sinsp + auto sfieldacc = sfield->second.new_accessor(); + auto dfieldacc = dfield.new_accessor(); + + for (uint64_t i = 0; i < max_iterations; i++) + { + ASSERT_EQ(table->entries_count(), i); + + // get non-existing entry + ASSERT_EQ(table->get_entry(i), nullptr); + + // creating a destroying a thread without adding it to the table + table->new_entry(); + + // creating and adding a thread to the table + auto t = table->add_entry(i, std::move(table->new_entry())); + ASSERT_NE(t, nullptr); + ASSERT_NE(table->get_entry(i), nullptr); + ASSERT_EQ(table->entries_count(), i + 1); + + // read and write from newly-created thread (existing field) + uint64_t tmpu64 = (uint64_t) -1; + t->get_dynamic_field(sfieldacc, tmpu64); + ASSERT_EQ(tmpu64, 0); + tmpu64 = 5; + t->set_dynamic_field(sfieldacc, tmpu64); + tmpu64 = 0; + t->get_dynamic_field(sfieldacc, tmpu64); + ASSERT_EQ(tmpu64, 5); + + // read and write from newly-created thread (added field) + std::string tmpstr = "test"; + t->get_dynamic_field(dfieldacc, tmpstr); + ASSERT_EQ(tmpstr, ""); + tmpstr = "hello"; + t->set_dynamic_field(dfieldacc, tmpstr); + tmpstr = ""; + t->get_dynamic_field(dfieldacc, tmpstr); + ASSERT_EQ(tmpstr, "hello"); + } + + // full iteration + auto it = [&](libsinsp::state::table_entry& e) -> bool + { + uint64_t tmpu64; + std::string tmpstr; + e.get_dynamic_field(sfieldacc, tmpu64); + EXPECT_EQ(tmpu64, 5); + e.get_dynamic_field(dfieldacc, tmpstr); + EXPECT_EQ(tmpstr, "hello"); + return true; + }; + ASSERT_TRUE(table->foreach_entry(it)); + + // iteration with break-out + ASSERT_FALSE(table->foreach_entry([&](libsinsp::state::table_entry& e) -> bool + { + return false; + })); + + // iteration with error + ASSERT_ANY_THROW(table->foreach_entry([&](libsinsp::state::table_entry& e) -> bool + { + throw sinsp_exception("some error"); + })); + + // erasing an unknown thread + ASSERT_EQ(table->erase_entry(max_iterations), false); + ASSERT_EQ(table->entries_count(), max_iterations); + + // erase one of the newly-created thread + ASSERT_EQ(table->erase_entry(0), true); + ASSERT_EQ(table->entries_count(), max_iterations - 1); + + // clear all + ASSERT_NO_THROW(table->clear_entries()); + ASSERT_EQ(table->entries_count(), 0); +} diff --git a/userspace/libsinsp/test/plugins/plugin_extract.cpp b/userspace/libsinsp/test/plugins/plugin_extract.cpp index a21d6b8005..246350ed36 100644 --- a/userspace/libsinsp/test/plugins/plugin_extract.cpp +++ b/userspace/libsinsp/test/plugins/plugin_extract.cpp @@ -18,6 +18,7 @@ limitations under the License. #include #include #include +#include #include @@ -33,6 +34,7 @@ typedef struct plugin_state std::string lasterr; std::string strstorage; const char* strptr; + std::vector event_types; } plugin_state; static const char* plugin_get_required_api_version() @@ -73,9 +75,46 @@ static const char* plugin_get_extract_event_sources() return "[\"sample\"]"; } +static uint16_t* plugin_get_extract_event_types(uint32_t* num_types, ss_plugin_t* s) +{ + plugin_state *ps = (plugin_state *) s; + if (!ps->event_types.empty()) + { + *num_types = (uint32_t) ps->event_types.size(); + return ps->event_types.data(); + } + + static uint16_t types[] = {}; + *num_types = 0; + return types; +} + static ss_plugin_t* plugin_init(const ss_plugin_init_input* in, ss_plugin_rc* rc) { plugin_state *ret = new plugin_state(); + + // init config may indicate the comma-separated, event-types to filter + std::string cfg = in->config; + if (!cfg.empty()) + { + if (cfg.back() != ',') + { + cfg += ","; + } + std::string val; + std::stringstream test(cfg); + while(std::getline(test, val, ',')) + { + auto v = std::atoi(val.c_str()); + if (v == 0) + { + ret->lasterr = "invalid init config string: " + cfg; + return ret; + } + ret->event_types.push_back((uint16_t) v); + } + } + *rc = SS_PLUGIN_SUCCESS; return ret; } @@ -124,5 +163,6 @@ void get_plugin_api_sample_plugin_extract(plugin_api& out) out.destroy = plugin_destroy; out.get_fields = plugin_get_fields; out.get_extract_event_sources = plugin_get_extract_event_sources; + out.get_extract_event_types = plugin_get_extract_event_types; out.extract_fields = plugin_extract_fields; } diff --git a/userspace/libsinsp/test/plugins/sample_table.h b/userspace/libsinsp/test/plugins/sample_table.h index ea909a59c5..37c85312fe 100644 --- a/userspace/libsinsp/test/plugins/sample_table.h +++ b/userspace/libsinsp/test/plugins/sample_table.h @@ -36,6 +36,12 @@ class sample_table public: virtual ~entry() { + // note: makes sure that release_table_entry is invoked consistently + if (refcount > 0) + { + fprintf(stderr, "sample_table: table entry deleted with non-zero refcount %ld\n", refcount); + exit(1); + } for (auto &p : data) { delete p; @@ -43,6 +49,8 @@ class sample_table } private: std::vector data; + std::vector strings; + uint64_t refcount; friend class sample_table; }; @@ -92,12 +100,33 @@ class sample_table static ss_plugin_table_field_t* add_field(ss_plugin_table_t* _t, const char* name, ss_plugin_state_type data_type) { auto t = static_cast(_t); - t->strings.push_back(name); + for (size_t i = 0; i < t->fields.size(); i++) + { + const auto& f = t->fields[i]; + if (strcmp(f.name, name) == 0) + { + if (f.field_type != data_type) + { + t->lasterr = "field defined with incompatible types: " + std::string(name); + return NULL; + } + // note: shifted by 1 so that we never return 0 (interpreted as NULL) + return (ss_plugin_table_field_t*) (i + 1); + } + } + ss_plugin_table_fieldinfo f; - f.name = t->strings[t->strings.size() - 1].c_str(); + t->strings.push_back(name); f.field_type = data_type; f.read_only = false; t->fields.push_back(f); + for (size_t i = 0; i < t->fields.size(); i++) + { + // note: previous string pointers may have been changed so we + // we need to set all of them again + t->fields[i].name = t->strings[i].c_str(); + } + // note: shifted by 1 so that we never return 0 (interpreted as NULL) return (ss_plugin_table_field_t*) (t->fields.size()); } @@ -108,6 +137,7 @@ class sample_table auto it = t->entries.find(key->u64); if (it != t->entries.end()) { + it->second.refcount++; return static_cast(&it->second); } t->lasterr = "unknown entry at key: " + std::to_string(key->u64); @@ -116,16 +146,44 @@ class sample_table static ss_plugin_rc read_entry_field(ss_plugin_table_t *_t, ss_plugin_table_entry_t *_e, const ss_plugin_table_field_t *_f, ss_plugin_state_data *out) { + auto t = static_cast(_t); auto e = static_cast(_e); auto f = size_t (_f) - 1; while (e->data.size() <= f) { e->data.push_back(new ss_plugin_state_data()); + e->strings.emplace_back(); + } + if (t->fields[f].field_type == SS_PLUGIN_ST_STRING) + { + out->str = e->strings[f].c_str(); + } + else + { + memcpy(out, e->data[f], sizeof(ss_plugin_state_data)); } - memcpy(out, e->data[f], sizeof(ss_plugin_state_data)); return SS_PLUGIN_SUCCESS; } + static void release_table_entry(ss_plugin_table_t* _t, ss_plugin_table_entry_t* _e) + { + auto e = static_cast(_e); + e->refcount--; + } + + static ss_plugin_bool iterate_entries(ss_plugin_table_t* _t, ss_plugin_table_iterator_func_t it, ss_plugin_table_iterator_state_t* s) + { + auto t = static_cast(_t); + for (auto& [k, e]: t->entries) + { + if (it(s, static_cast(&e)) != 1) + { + return 0; + } + } + return 1; + } + static ss_plugin_rc clear(ss_plugin_table_t *_t) { auto t = static_cast(_t); @@ -148,27 +206,47 @@ class sample_table static ss_plugin_table_entry_t *create_entry(ss_plugin_table_t *t) { - return static_cast(new sample_table::entry()); + auto e = new sample_table::entry(); + e->refcount = 1; + return static_cast(e); + } + + static void destroy_entry(ss_plugin_table_t* _t, ss_plugin_table_entry_t* _e) + { + auto e = static_cast(_e); + e->refcount = 0; + delete e; } static ss_plugin_table_entry_t *add_entry(ss_plugin_table_t *_t, const ss_plugin_state_data *key, ss_plugin_table_entry_t *_e) { auto t = static_cast(_t); auto e = static_cast(_e); + e->refcount = 0; t->entries.insert({ key->u64, *e }); delete e; - return &t->entries[key->u64]; + t->entries[key->u64].refcount = 1; + return static_cast(&t->entries[key->u64]); } static ss_plugin_rc write_entry_field(ss_plugin_table_t* _t, ss_plugin_table_entry_t* _e, const ss_plugin_table_field_t* _f, const ss_plugin_state_data* in) { + auto t = static_cast(_t); auto e = static_cast(_e); auto f = size_t (_f) - 1; while (e->data.size() <= f) { e->data.push_back(new ss_plugin_state_data()); + e->strings.emplace_back(); + } + if (t->fields[f].field_type == SS_PLUGIN_ST_STRING) + { + e->strings[f] = in->str; + } + else + { + memcpy(e->data[f], in, sizeof(ss_plugin_state_data)); } - memcpy(e->data[f], in, sizeof(ss_plugin_state_data)); return SS_PLUGIN_SUCCESS; } @@ -190,18 +268,37 @@ class sample_table ret->name = t->name.c_str(); ret->table = t; ret->key_type = ss_plugin_state_type::SS_PLUGIN_ST_UINT64; - ret->fields.list_table_fields = list_fields; - ret->fields.get_table_field = get_field; - ret->fields.add_table_field = add_field; - ret->reader.get_table_name = get_name; - ret->reader.get_table_size = get_size; - ret->reader.get_table_entry = get_entry; - ret->reader.read_entry_field = read_entry_field; - ret->writer.clear_table = clear; - ret->writer.erase_table_entry = erase_entry; - ret->writer.create_table_entry = create_entry; - ret->writer.add_table_entry = add_entry; - ret->writer.write_entry_field = write_entry_field; + ret->reader_ext = &t->reader_vtable; + ret->writer_ext = &t->writer_vtable; + ret->fields_ext = &t->fields_vtable; + ret->fields_ext->list_table_fields = list_fields; + ret->fields_ext->get_table_field = get_field; + ret->fields_ext->add_table_field = add_field; + ret->fields.list_table_fields = ret->fields_ext->list_table_fields; + ret->fields.get_table_field = ret->fields_ext->get_table_field; + ret->fields.add_table_field = ret->fields_ext->add_table_field; + ret->reader_ext->get_table_name = get_name; + ret->reader_ext->get_table_size = get_size; + ret->reader_ext->get_table_entry = get_entry; + ret->reader_ext->read_entry_field = read_entry_field; + ret->reader_ext->release_table_entry = release_table_entry; + ret->reader_ext->iterate_entries = iterate_entries; + ret->reader.get_table_name = ret->reader_ext->get_table_name; + ret->reader.get_table_size = ret->reader_ext->get_table_size; + ret->reader.get_table_entry = ret->reader_ext->get_table_entry; + ret->reader.read_entry_field = ret->reader_ext->read_entry_field; + ret->writer_ext->clear_table = clear; + ret->writer_ext->erase_table_entry = erase_entry; + ret->writer_ext->create_table_entry = create_entry; + ret->writer_ext->destroy_table_entry = destroy_entry; + ret->writer_ext->add_table_entry = add_entry; + ret->writer_ext->write_entry_field = write_entry_field; + ret->writer.clear_table = ret->writer_ext->clear_table; + ret->writer.erase_table_entry = ret->writer_ext->erase_table_entry; + ret->writer.create_table_entry = ret->writer_ext->create_table_entry; + ret->writer.destroy_table_entry = ret->writer_ext->destroy_table_entry; + ret->writer.add_table_entry = ret->writer_ext->add_table_entry; + ret->writer.write_entry_field = ret->writer_ext->write_entry_field; return ret; } @@ -211,4 +308,7 @@ class sample_table std::vector strings; std::unordered_map entries; std::vector fields; + ss_plugin_table_reader_vtable_ext reader_vtable; + ss_plugin_table_writer_vtable_ext writer_vtable; + ss_plugin_table_fields_vtable_ext fields_vtable; }; diff --git a/userspace/libsinsp/test/plugins/syscall_extract.cpp b/userspace/libsinsp/test/plugins/syscall_extract.cpp index b1c32282ce..4a3e3a3e4a 100644 --- a/userspace/libsinsp/test/plugins/syscall_extract.cpp +++ b/userspace/libsinsp/test/plugins/syscall_extract.cpp @@ -106,7 +106,7 @@ static const char* plugin_get_extract_event_sources() return "[\"syscall\"]"; } -static uint16_t* plugin_get_extract_event_types(uint32_t* num_types) +static uint16_t* plugin_get_extract_event_types(uint32_t* num_types, ss_plugin_t* s) { static uint16_t types[] = { PPME_SYSCALL_OPEN_E, @@ -236,11 +236,13 @@ static ss_plugin_rc plugin_extract_fields(ss_plugin_t *s, const ss_plugin_event_ { auto err = in->get_owner_last_error(in->owner); ps->lasterr = err ? err : ("can't read ope counter from thread with tid=" + std::to_string(ev->evt->tid)); + in->table_reader_ext->release_table_entry(ps->thread_table, thread); return SS_PLUGIN_FAILURE; } ps->u64storage = tmp.u64; in->fields[i].res.u64 = &ps->u64storage; in->fields[i].res_len = 1; + in->table_reader_ext->release_table_entry(ps->thread_table, thread); break; case 2: // evt_count if (!ps->evtcount_table || !ps->evtcount_count_field) @@ -282,11 +284,13 @@ static ss_plugin_rc plugin_extract_fields(ss_plugin_t *s, const ss_plugin_event_ { auto err = in->get_owner_last_error(in->owner); ps->lasterr = err ? err : ("can't read event counter for type=" + std::to_string(ev->evt->type)); + in->table_reader_ext->release_table_entry(ps->evtcount_table, evtcount); return SS_PLUGIN_FAILURE; } ps->u64storage = tmp.u64; in->fields[i].res.u64 = &ps->u64storage; in->fields[i].res_len = 1; + in->table_reader_ext->release_table_entry(ps->evtcount_table, evtcount); break; case 3: // test.proc_name tmp.s64 = ev->evt->tid; @@ -302,12 +306,14 @@ static ss_plugin_rc plugin_extract_fields(ss_plugin_t *s, const ss_plugin_event_ { auto err = in->get_owner_last_error(in->owner); ps->lasterr = err ? err : ("can't read proc name from thread with tid=" + std::to_string(ev->evt->tid)); + in->table_reader_ext->release_table_entry(ps->thread_table, thread); return SS_PLUGIN_FAILURE; } ps->strstorage = tmp.str; ps->strptrstorage = ps->strstorage.c_str(); in->fields[i].res.str = &ps->strptrstorage; in->fields[i].res_len = 1; + in->table_reader_ext->release_table_entry(ps->thread_table, thread); break; case 4: // sample.tick if (ev->evt->type == PPME_ASYNCEVENT_E diff --git a/userspace/libsinsp/test/plugins/syscall_parse.cpp b/userspace/libsinsp/test/plugins/syscall_parse.cpp index 27f3cdd3df..fc8956e4cc 100644 --- a/userspace/libsinsp/test/plugins/syscall_parse.cpp +++ b/userspace/libsinsp/test/plugins/syscall_parse.cpp @@ -86,7 +86,7 @@ static const char* plugin_get_parse_event_sources() return "[\"syscall\"]"; } -static uint16_t* plugin_get_parse_event_types(uint32_t* num_types) +static uint16_t* plugin_get_parse_event_types(uint32_t* num_types, ss_plugin_t* s) { static uint16_t types[] = { PPME_SYSCALL_OPEN_E, @@ -195,6 +195,7 @@ static ss_plugin_rc plugin_parse_event(ss_plugin_t *s, const ss_plugin_event_inp ps->event_count_table->table, evtcounter, ps->event_count_table_count_field, &tmp)) { ps->lasterr = "can't read event counter in table"; + ps->event_count_table->reader_ext->release_table_entry(ps->event_count_table->table, evtcounter); return SS_PLUGIN_FAILURE; } tmp.u64++; @@ -202,8 +203,10 @@ static ss_plugin_rc plugin_parse_event(ss_plugin_t *s, const ss_plugin_event_inp ps->event_count_table->table, evtcounter, ps->event_count_table_count_field, &tmp)) { ps->lasterr = "can't write event counter in table"; + ps->event_count_table->reader_ext->release_table_entry(ps->event_count_table->table, evtcounter); return SS_PLUGIN_FAILURE; } + ps->event_count_table->reader_ext->release_table_entry(ps->event_count_table->table, evtcounter); // update counter for current thread if (evt_type_is_open(ev->evt->type)) @@ -221,6 +224,7 @@ static ss_plugin_rc plugin_parse_event(ss_plugin_t *s, const ss_plugin_event_inp { auto err = in->get_owner_last_error(in->owner); ps->lasterr = err ? err : ("can't read open counter from thread with tid=" + std::to_string(ev->evt->tid)); + in->table_reader_ext->release_table_entry(ps->thread_table, thread); return SS_PLUGIN_FAILURE; } @@ -230,8 +234,10 @@ static ss_plugin_rc plugin_parse_event(ss_plugin_t *s, const ss_plugin_event_inp { auto err = in->get_owner_last_error(in->owner); ps->lasterr = err ? err : ("can't write open counter to thread with tid=" + std::to_string(ev->evt->tid)); + in->table_reader_ext->release_table_entry(ps->thread_table, thread); return SS_PLUGIN_FAILURE; } + in->table_reader_ext->release_table_entry(ps->thread_table, thread); } return SS_PLUGIN_SUCCESS; diff --git a/userspace/libsinsp/test/plugins/tables.cpp b/userspace/libsinsp/test/plugins/tables.cpp new file mode 100644 index 0000000000..f5cdf437b0 --- /dev/null +++ b/userspace/libsinsp/test/plugins/tables.cpp @@ -0,0 +1,544 @@ +/* +Copyright (C) 2023 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include +#include +#include +#include + +#include +#include "sample_table.h" +#include "test_plugins.h" + +/** + * Example of plugin that accesses the thread table and that exposes its own + * sta table. The goal is to test all the methods of the table API. + */ +typedef struct plugin_state +{ + std::string lasterr; + ss_plugin_table_t* thread_table; + ss_plugin_table_field_t* thread_static_field; + ss_plugin_table_field_t* thread_dynamic_field; + ss_plugin_table_field_t* thread_dynamic_field_str; + sample_table::ptr_t internal_table; + ss_plugin_table_field_t* internal_dynamic_field; +} plugin_state; + +static const char* plugin_get_required_api_version() +{ + return PLUGIN_API_VERSION_STR; +} + +static const char* plugin_get_version() +{ + return "0.1.0"; +} + +static const char* plugin_get_name() +{ + return "sample_tables"; +} + +static const char* plugin_get_description() +{ + return "some desc"; +} + +static const char* plugin_get_contact() +{ + return "some contact"; +} + +static const char* plugin_get_parse_event_sources() +{ + return "[\"syscall\"]"; +} + +static uint16_t* plugin_get_parse_event_types(uint32_t* num_types, ss_plugin_t* s) +{ + static uint16_t types[] = {}; + *num_types = 0; + return types; +} + +static ss_plugin_t* plugin_init(const ss_plugin_init_input* in, ss_plugin_rc* rc) +{ + *rc = SS_PLUGIN_SUCCESS; + plugin_state *ret = new plugin_state(); + + if (!in || !in->tables) + { + *rc = SS_PLUGIN_FAILURE; + ret->lasterr = "invalid config input"; + return ret; + } + + // get accessor for thread table + ret->thread_table = in->tables->get_table( + in->owner, "threads", ss_plugin_state_type::SS_PLUGIN_ST_INT64); + if (!ret->thread_table) + { + *rc = SS_PLUGIN_FAILURE; + auto err = in->get_owner_last_error(in->owner); + ret->lasterr = err ? err : "can't access thread table"; + return ret; + } + + // get an existing field from thread table entries + // todo(jasondellaluce): add tests for fields of other types as well + ret->thread_static_field = in->tables->fields.get_table_field( + ret->thread_table, "comm", ss_plugin_state_type::SS_PLUGIN_ST_STRING); + if (!ret->thread_static_field) + { + *rc = SS_PLUGIN_FAILURE; + auto err = in->get_owner_last_error(in->owner); + ret->lasterr = err ? err : "can't get static field in thread table"; + return ret; + } + + // define a new field in thread table entries + // todo(jasondellaluce): add tests for fields of other types as well + ret->thread_dynamic_field = in->tables->fields.add_table_field( + ret->thread_table, "some_new_dynamic_field", ss_plugin_state_type::SS_PLUGIN_ST_UINT64); + if (!ret->thread_dynamic_field) + { + *rc = SS_PLUGIN_FAILURE; + auto err = in->get_owner_last_error(in->owner); + ret->lasterr = err ? err : "can't add dynamic field in thread table"; + return ret; + } + ret->thread_dynamic_field_str = in->tables->fields.add_table_field( + ret->thread_table, "some_new_dynamic_field_str", ss_plugin_state_type::SS_PLUGIN_ST_STRING); + if (!ret->thread_dynamic_field_str) + { + *rc = SS_PLUGIN_FAILURE; + auto err = in->get_owner_last_error(in->owner); + ret->lasterr = err ? err : "can't add dynamic field in thread table"; + return ret; + } + + // define a new table that keeps a counter for all events. + // todo(jasondellaluce): add tests for fields of other types as well + ret->internal_table = sample_table::create("plugin_sample", ret->lasterr); + ret->internal_dynamic_field = ret->internal_table->fields.add_table_field( + ret->internal_table->table, "u64_val", + ss_plugin_state_type::SS_PLUGIN_ST_UINT64); + if (!ret->internal_dynamic_field) + { + *rc = SS_PLUGIN_FAILURE; + ret->lasterr = "can't define internal table field"; + return ret; + } + + if (SS_PLUGIN_SUCCESS != in->tables->add_table(in->owner, ret->internal_table.get())) + { + *rc = SS_PLUGIN_FAILURE; + auto err = in->get_owner_last_error(in->owner); + ret->lasterr = err ? err : "can't add internal table"; + return ret; + } + return ret; +} + +static void plugin_destroy(ss_plugin_t* s) +{ + delete ((plugin_state *) s); +} + +static const char* plugin_get_last_error(ss_plugin_t* s) +{ + return ((plugin_state *) s)->lasterr.c_str(); +} + +// parses events and keeps a count for each thread about the syscalls of the open family +static ss_plugin_rc plugin_parse_event(ss_plugin_t *s, const ss_plugin_event_input *ev, const ss_plugin_event_parse_input* in) +{ + static int64_t s_new_thread_tid = 999999; + int step = 0; + ss_plugin_state_data tmp; + ss_plugin_table_entry_t* thread; + plugin_state *ps = (plugin_state *) s; + + // get table name + step++; + { + if (strcmp("threads", in->table_reader_ext->get_table_name(ps->thread_table))) + { + fprintf(stderr, "table_reader.get_table_name (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + } + + // check that the table contains only the init thread + step++; + { + auto size = in->table_reader_ext->get_table_size(ps->thread_table); + if (size != 1) + { + fprintf(stderr, "table_reader.get_table_size (%d) failure: (%lu) %s\n", step, size, in->get_owner_last_error(in->owner)); + exit(1); + } + } + + // get the init thread and read its comm + step++; + { + tmp.s64 = 1; + thread = in->table_reader_ext->get_table_entry(ps->thread_table, &tmp); + if (!thread) + { + fprintf(stderr, "table_reader.get_table_entry (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + if (SS_PLUGIN_SUCCESS != in->table_reader_ext->read_entry_field(ps->thread_table, thread, ps->thread_static_field, &tmp)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + if (strcmp("init", tmp.str)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) inconsistency\n", step); + exit(1); + } + } + + // read-write dynamic field from existing thread + step++; + { + if (SS_PLUGIN_SUCCESS != in->table_reader_ext->read_entry_field(ps->thread_table, thread, ps->thread_dynamic_field, &tmp)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + if (tmp.u64 != 0) + { + fprintf(stderr, "table_reader.read_entry_field (%d) inconsistency\n", step); + exit(1); + } + tmp.u64 = 5; + if (SS_PLUGIN_SUCCESS != in->table_writer_ext->write_entry_field(ps->thread_table, thread, ps->thread_dynamic_field, &tmp)) + { + fprintf(stderr, "table_reader.write_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + if (SS_PLUGIN_SUCCESS != in->table_reader_ext->read_entry_field(ps->thread_table, thread, ps->thread_dynamic_field, &tmp)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + if (tmp.u64 != 5) + { + fprintf(stderr, "table_reader.read_entry_field (%d) inconsistency\n", step); + exit(1); + } + } + + // read-write dynamic field (str) from existing thread + step++; + { + if (SS_PLUGIN_SUCCESS != in->table_reader_ext->read_entry_field(ps->thread_table, thread, ps->thread_dynamic_field_str, &tmp)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + if (strcmp("", tmp.str)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) inconsistency\n", step); + exit(1); + } + tmp.str = "hello"; + if (SS_PLUGIN_SUCCESS != in->table_writer_ext->write_entry_field(ps->thread_table, thread, ps->thread_dynamic_field_str, &tmp)) + { + fprintf(stderr, "table_reader.write_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + if (SS_PLUGIN_SUCCESS != in->table_reader_ext->read_entry_field(ps->thread_table, thread, ps->thread_dynamic_field_str, &tmp)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + if (strcmp("hello", tmp.str)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) inconsistency\n", step); + exit(1); + } + } + + // get non-existing thread + step++; + { + tmp.s64 = s_new_thread_tid; + thread = in->table_reader_ext->get_table_entry(ps->thread_table, &tmp); + if (thread) + { + fprintf(stderr, "table_reader.get_table_entry (%d) inconsistency\n", step); + exit(1); + } + } + + // creating a destroying a thread without adding it to the table + step++; + { + thread = in->table_writer_ext->create_table_entry(ps->thread_table); + if (!thread) + { + fprintf(stderr, "table_reader.create_table_entry (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + in->table_writer_ext->destroy_table_entry(ps->thread_table, thread); + } + + // creating and adding a thread to the table + step++; + { + thread = in->table_writer_ext->create_table_entry(ps->thread_table); + if (!thread) + { + fprintf(stderr, "table_reader.create_table_entry (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + tmp.s64 = s_new_thread_tid; + thread = in->table_writer_ext->add_table_entry(ps->thread_table, &tmp, thread); + if (!thread) + { + fprintf(stderr, "table_reader.add_table_entry (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + auto size = in->table_reader_ext->get_table_size(ps->thread_table); + if (size != 2) + { + fprintf(stderr, "table_reader.get_table_size (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + tmp.s64 = s_new_thread_tid; + in->table_reader_ext->release_table_entry(ps->thread_table, &tmp); + } + + // get newly-created thread + step++; + { + tmp.s64 = s_new_thread_tid; + thread = in->table_reader_ext->get_table_entry(ps->thread_table, &tmp); + if (!thread) + { + fprintf(stderr, "table_reader.get_table_entry (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + } + + // read and write from newly-created thread (static field) + step++; + { + if (SS_PLUGIN_SUCCESS != in->table_reader_ext->read_entry_field(ps->thread_table, thread, ps->thread_static_field, &tmp)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + if (strcmp("", tmp.str)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) inconsistency\n", step); + exit(1); + } + tmp.str = "hello"; + if (SS_PLUGIN_SUCCESS != in->table_writer_ext->write_entry_field(ps->thread_table, thread, ps->thread_static_field, &tmp)) + { + fprintf(stderr, "table_reader.write_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + if (SS_PLUGIN_SUCCESS != in->table_reader_ext->read_entry_field(ps->thread_table, thread, ps->thread_static_field, &tmp)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + if (strcmp("hello", tmp.str)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) inconsistency\n", step); + exit(1); + } + } + + // read and write from newly-created thread (dynamic field) + step++; + { + if (SS_PLUGIN_SUCCESS != in->table_reader_ext->read_entry_field(ps->thread_table, thread, ps->thread_dynamic_field, &tmp)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + if (tmp.u64 != 0) + { + fprintf(stderr, "table_reader.read_entry_field (%d) inconsistency\n", step); + exit(1); + } + tmp.u64 = 5; + if (SS_PLUGIN_SUCCESS != in->table_writer_ext->write_entry_field(ps->thread_table, thread, ps->thread_dynamic_field, &tmp)) + { + fprintf(stderr, "table_reader.write_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + if (SS_PLUGIN_SUCCESS != in->table_reader_ext->read_entry_field(ps->thread_table, thread, ps->thread_dynamic_field, &tmp)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + if (tmp.u64 != 5) + { + fprintf(stderr, "table_reader.read_entry_field (%d) inconsistency\n", step); + exit(1); + } + + tmp.s64 = s_new_thread_tid; + in->table_reader_ext->release_table_entry(ps->thread_table, &tmp); + } + + // erasing an unknown thread + step++; + { + tmp.s64 = 10; + if (SS_PLUGIN_SUCCESS == in->table_writer_ext->erase_table_entry(ps->thread_table, &tmp)) + { + fprintf(stderr, "table_reader.erase_table_entry (%d) inconsistency\n", step); + exit(1); + } + } + + // loop over all threads, we expect to only find two (init, and our new one) + step++; + { + struct iterate_entries_state + { + int* step = nullptr; + uint64_t count = 0; + const ss_plugin_event_parse_input *in = nullptr; + plugin_state* ps = nullptr; + }; + + // successful iteration + iterate_entries_state its1; + its1.in = in; + its1.ps = ps; + its1.step = &step; + auto it1 = [](ss_plugin_table_iterator_state_t* s, ss_plugin_table_entry_t* e) -> ss_plugin_bool + { + auto st = (iterate_entries_state*) s; + st->count++; + + ss_plugin_state_data val; + if (SS_PLUGIN_SUCCESS != st->in->table_reader_ext->read_entry_field(st->ps->thread_table, e, st->ps->thread_static_field, &val)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) failure: %s\n", (*st->step), st->in->get_owner_last_error(st->in->owner)); + exit(1); + } + if (strcmp(val.str, "init") == 0 || strcmp(val.str, "hello") == 0) + { + if (SS_PLUGIN_SUCCESS != st->in->table_reader_ext->read_entry_field(st->ps->thread_table, e, st->ps->thread_dynamic_field, &val)) + { + fprintf(stderr, "table_reader.read_entry_field (%d) failure: %s\n", (*st->step), st->in->get_owner_last_error(st->in->owner)); + exit(1); + } + if (val.u64 != 5) + { + fprintf(stderr, "table_reader.read_entry_field (%d) inconsistency\n", (*st->step)); + exit(1); + } + } + else + { + fprintf(stderr, "table_reader.read_entry_field (%d) unexpected value: %s\n", (*st->step), val.str); + exit(1); + } + + return 1; + }; + if (in->table_reader_ext->iterate_entries(ps->thread_table, it1, (ss_plugin_table_iterator_state_t*) &its1) != 1) + { + fprintf(stderr, "table_reader.iterate_entries (%d) unexpected break-out\n", step); + exit(1); + } + if (its1.count != 2) + { + fprintf(stderr, "table_reader.iterate_entries (%d) unexpected count result\n", step); + exit(1); + } + + // iteration with break-out + auto it2 = [](ss_plugin_table_iterator_state_t* s, ss_plugin_table_entry_t* e) -> ss_plugin_bool + { + return false; + }; + if (in->table_reader_ext->iterate_entries(ps->thread_table, it2, (ss_plugin_table_iterator_state_t*) &its1) != 0) + { + fprintf(stderr, "table_reader.iterate_entries (%d) break-out was expected\n", step); + exit(1); + } + } + + // erase newly-created thread + step++; + { + tmp.s64 = s_new_thread_tid; + if (SS_PLUGIN_SUCCESS != in->table_writer_ext->erase_table_entry(ps->thread_table, &tmp)) + { + fprintf(stderr, "table_reader.erase_table_entry (%d) inconsistency\n", step); + exit(1); + } + } + + // revert edits made to existing thread + step++; + { + tmp.s64 = 1; + thread = in->table_reader_ext->get_table_entry(ps->thread_table, &tmp); + if (!thread) + { + fprintf(stderr, "table_reader.get_table_entry (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + tmp.u64 = 0; + if (SS_PLUGIN_SUCCESS != in->table_writer_ext->write_entry_field(ps->thread_table, thread, ps->thread_dynamic_field, &tmp)) + { + fprintf(stderr, "table_reader.write_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + tmp.str = ""; + if (SS_PLUGIN_SUCCESS != in->table_writer_ext->write_entry_field(ps->thread_table, thread, ps->thread_dynamic_field_str, &tmp)) + { + fprintf(stderr, "table_reader.write_entry_field (%d) failure: %s\n", step, in->get_owner_last_error(in->owner)); + exit(1); + } + tmp.s64 = 1; + in->table_reader_ext->release_table_entry(ps->thread_table, &tmp); + } + + return SS_PLUGIN_SUCCESS; +} + +void get_plugin_api_sample_tables(plugin_api& out) +{ + memset(&out, 0, sizeof(plugin_api)); + out.get_required_api_version = plugin_get_required_api_version; + out.get_version = plugin_get_version; + out.get_description = plugin_get_description; + out.get_contact = plugin_get_contact; + out.get_name = plugin_get_name; + out.get_last_error = plugin_get_last_error; + out.init = plugin_init; + out.destroy = plugin_destroy; + out.get_parse_event_sources = plugin_get_parse_event_sources; + out.get_parse_event_types = plugin_get_parse_event_types; + out.parse_event = plugin_parse_event; +} diff --git a/userspace/libsinsp/test/plugins/test_plugins.h b/userspace/libsinsp/test/plugins/test_plugins.h index e3d3cddf8e..2086ba4024 100644 --- a/userspace/libsinsp/test/plugins/test_plugins.h +++ b/userspace/libsinsp/test/plugins/test_plugins.h @@ -24,3 +24,4 @@ void get_plugin_api_sample_syscall_parse(plugin_api& out); void get_plugin_api_sample_syscall_async(plugin_api& out); void get_plugin_api_sample_plugin_source(plugin_api& out); void get_plugin_api_sample_plugin_extract(plugin_api& out); +void get_plugin_api_sample_tables(plugin_api& out); diff --git a/userspace/libsinsp/test/state.ut.cpp b/userspace/libsinsp/test/state.ut.cpp index 695938d036..477c7e465a 100644 --- a/userspace/libsinsp/test/state.ut.cpp +++ b/userspace/libsinsp/test/state.ut.cpp @@ -104,7 +104,9 @@ TEST(static_struct, defs_and_access) ASSERT_EQ(s.get_static_field(acc_num), 0); s.set_num(5); ASSERT_EQ(s.get_num(), 5); - ASSERT_EQ(s.get_static_field(acc_num), 5); + uint32_t u32tmp = 0; + s.get_static_field(acc_num, u32tmp); + ASSERT_EQ(u32tmp, 5); s.set_static_field(acc_num, (uint32_t) 6); ASSERT_EQ(s.get_num(), 6); ASSERT_EQ(s.get_static_field(acc_num), 6); @@ -115,8 +117,20 @@ TEST(static_struct, defs_and_access) str = "hello"; s.set_str("hello"); ASSERT_EQ(s.get_str(), str); - ASSERT_EQ(s.get_static_field(acc_str), str); - ASSERT_ANY_THROW(s.set_static_field(acc_str, str)); // readonly + s.get_static_field(acc_str, str); + ASSERT_EQ(str, "hello"); + ASSERT_ANY_THROW(s.set_static_field(acc_str, "hello")); // readonly + + const char* cstr = "sample"; + s.set_str(""); + s.get_static_field(acc_str, cstr); + ASSERT_EQ(strcmp(cstr, ""), 0); + s.set_str("hello"); + s.get_static_field(acc_str, cstr); + ASSERT_EQ(strcmp(cstr, "hello"), 0); + ASSERT_EQ(cstr, s.get_str().c_str()); + ASSERT_ANY_THROW(s.set_static_field(acc_str, cstr)); // readonly + // illegal access from an accessor created from different definition list // note: this should supposedly be checked for and throw an exception, @@ -174,19 +188,35 @@ TEST(dynamic_struct, defs_and_access) ASSERT_ANY_THROW(field_num.new_accessor()); ASSERT_ANY_THROW(field_str.new_accessor()); - ASSERT_EQ(s.get_dynamic_field(acc_num), 0); + uint64_t tmp; + s.get_dynamic_field(acc_num, tmp); + ASSERT_EQ(tmp, 0); s.set_dynamic_field(acc_num, (uint64_t) 6); - ASSERT_EQ(s.get_dynamic_field(acc_num), 6); + s.get_dynamic_field(acc_num, tmp); + ASSERT_EQ(tmp, 6); - ASSERT_EQ(s.get_dynamic_field(acc_str), std::string("")); + std::string tmpstr; + s.get_dynamic_field(acc_str, tmpstr); + ASSERT_EQ(tmpstr, std::string("")); s.set_dynamic_field(acc_str, std::string("hello")); - ASSERT_EQ(s.get_dynamic_field(acc_str), std::string("hello")); + s.get_dynamic_field(acc_str, tmpstr); + ASSERT_EQ(tmpstr, std::string("hello")); + + s.set_dynamic_field(acc_str, std::string("")); + const char* ctmpstr = "sample"; + s.get_dynamic_field(acc_str, ctmpstr); + ASSERT_EQ(strcmp(ctmpstr, ""), 0); + ctmpstr = "hello"; + s.set_dynamic_field(acc_str, ctmpstr); + ctmpstr = ""; + s.get_dynamic_field(acc_str, ctmpstr); + ASSERT_EQ(strcmp(ctmpstr, "hello"), 0); // illegal access from an accessor created from different definition list auto fields2 = std::make_shared(); auto field_num2 = fields2->add_field("num"); auto acc_num2 = field_num2.new_accessor(); - ASSERT_ANY_THROW(s.get_dynamic_field(acc_num2)); + ASSERT_ANY_THROW(s.get_dynamic_field(acc_num2, tmp)); } TEST(table_registry, defs_and_access) @@ -303,12 +333,15 @@ TEST(thread_manager, table_access) ASSERT_EQ(addedt->get_static_field(comm_acc), "test"); // add a dynamic field to table + std::string tmpstr; auto dynf_acc = table->dynamic_fields()->add_field("some_new_field").new_accessor(); ASSERT_EQ(table->dynamic_fields()->fields().size(), 1); ASSERT_EQ(addedt->dynamic_fields()->fields().size(), 1); - ASSERT_EQ(addedt->get_dynamic_field(dynf_acc), ""); + addedt->get_dynamic_field(dynf_acc, tmpstr); + ASSERT_EQ(tmpstr, ""); addedt->set_dynamic_field(dynf_acc, std::string("hello")); - ASSERT_EQ(addedt->get_dynamic_field(dynf_acc), "hello"); + addedt->get_dynamic_field(dynf_acc, tmpstr); + ASSERT_EQ(tmpstr, "hello"); // add another thread newt = table->new_entry(); @@ -316,9 +349,11 @@ TEST(thread_manager, table_access) ASSERT_NO_THROW(table->add_entry(1000, std::move(newt))); addedt = table->get_entry(1000); ASSERT_EQ(addedt->get_static_field(tid_acc), (int64_t) 1000); - ASSERT_EQ(addedt->get_dynamic_field(dynf_acc), ""); + addedt->get_dynamic_field(dynf_acc, tmpstr); + ASSERT_EQ(tmpstr, ""); addedt->set_dynamic_field(dynf_acc, std::string("world")); - ASSERT_EQ(addedt->get_dynamic_field(dynf_acc), "world"); + addedt->get_dynamic_field(dynf_acc, tmpstr); + ASSERT_EQ(tmpstr, "world"); // loop over entries int count = 0; diff --git a/userspace/libsinsp/threadinfo.cpp b/userspace/libsinsp/threadinfo.cpp index 56dfda2177..ad965f98f7 100644 --- a/userspace/libsinsp/threadinfo.cpp +++ b/userspace/libsinsp/threadinfo.cpp @@ -1414,11 +1414,11 @@ void sinsp_thread_manager::clear() m_n_drops = 0; #ifdef GATHER_INTERNAL_STATS - m_failed_lookups = &m_inspector->m_stats.get_metrics_registry().register_counter(internal_metrics::metric_name("thread_failed_lookups","Failed thread lookups")); - m_cached_lookups = &m_inspector->m_stats.get_metrics_registry().register_counter(internal_metrics::metric_name("thread_cached_lookups","Cached thread lookups")); - m_non_cached_lookups = &m_inspector->m_stats.get_metrics_registry().register_counter(internal_metrics::metric_name("thread_non_cached_lookups","Non cached thread lookups")); - m_added_threads = &m_inspector->m_stats.get_metrics_registry().register_counter(internal_metrics::metric_name("thread_added","Number of added threads")); - m_removed_threads = &m_inspector->m_stats.get_metrics_registry().register_counter(internal_metrics::metric_name("thread_removed","Removed threads")); + m_failed_lookups = &m_inspector->m_stats->get_metrics_registry().register_counter(internal_metrics::metric_name("thread_failed_lookups","Failed thread lookups")); + m_cached_lookups = &m_inspector->m_stats->get_metrics_registry().register_counter(internal_metrics::metric_name("thread_cached_lookups","Cached thread lookups")); + m_non_cached_lookups = &m_inspector->m_stats->get_metrics_registry().register_counter(internal_metrics::metric_name("thread_non_cached_lookups","Non cached thread lookups")); + m_added_threads = &m_inspector->m_stats->get_metrics_registry().register_counter(internal_metrics::metric_name("thread_added","Number of added threads")); + m_removed_threads = &m_inspector->m_stats->get_metrics_registry().register_counter(internal_metrics::metric_name("thread_removed","Removed threads")); #endif } @@ -1666,9 +1666,9 @@ void sinsp_thread_manager::recreate_child_dependencies() void sinsp_thread_manager::update_statistics() { #ifdef GATHER_INTERNAL_STATS - m_inspector->m_stats.m_n_threads = get_thread_count(); + m_inspector->m_stats->m_n_threads = get_thread_count(); - m_inspector->m_stats.m_n_fds = 0; + m_inspector->m_stats->m_n_fds = 0; for(threadinfo_map_iterator_t it = m_threadtable.begin(); it != m_threadtable.end(); it++) { sinsp_fdtable* fd_table_ptr = it->second.get_fd_table(); @@ -1677,7 +1677,7 @@ void sinsp_thread_manager::update_statistics() ASSERT(false); return; } - m_inspector->m_stats.m_n_fds += fd_table_ptr->size(); + m_inspector->m_stats->m_n_fds += fd_table_ptr->size(); } #endif } diff --git a/userspace/libsinsp/threadinfo.h b/userspace/libsinsp/threadinfo.h index 7fe79bd85c..35893a45c9 100644 --- a/userspace/libsinsp/threadinfo.h +++ b/userspace/libsinsp/threadinfo.h @@ -718,11 +718,8 @@ class SINSP_PUBLIC sinsp_thread_manager: public libsinsp::state::table { throw sinsp_exception("unknown entry type added to thread table"); } - if (tinfo->m_tid != key) - { - throw sinsp_exception("key does not match pid of entry added to thread table"); - } entry.release(); + tinfo->m_tid = key; add_thread(tinfo, false); return get_entry(key); } diff --git a/userspace/plugin/plugin_api.h b/userspace/plugin/plugin_api.h index 14bfcff8e2..48857b6314 100644 --- a/userspace/plugin/plugin_api.h +++ b/userspace/plugin/plugin_api.h @@ -27,8 +27,9 @@ extern "C" { // // API versions of this plugin framework // +// todo(jasondellaluce): when/if major changes to v4, check and solve all todos #define PLUGIN_API_VERSION_MAJOR 3 -#define PLUGIN_API_VERSION_MINOR 0 +#define PLUGIN_API_VERSION_MINOR 1 #define PLUGIN_API_VERSION_PATCH 0 // @@ -44,6 +45,16 @@ extern "C" { // #define PLUGIN_MAX_ERRLEN 1024 +// Supported by the API but deprecated. Use the extended version ss_plugin_table_reader_vtable_ext instead. +// todo(jasondellaluce): when/if major changes to v4, remove this and +// give this name to the associated *_ext struct. +typedef struct +{ + ss_plugin_table_fieldinfo* (*list_table_fields)(ss_plugin_table_t* t, uint32_t* nfields); + ss_plugin_table_field_t* (*get_table_field)(ss_plugin_table_t* t, const char* name, ss_plugin_state_type data_type); + ss_plugin_table_field_t* (*add_table_field)(ss_plugin_table_t* t, const char* name, ss_plugin_state_type data_type); +} ss_plugin_table_fields_vtable; + // Vtable for controlling and the fields for the entries of a state table. // This allows discovering the fields available in the table, defining new ones, // and obtaining accessors usable at runtime for reading and writing the fields' @@ -72,10 +83,29 @@ typedef struct // Returns NULL in case of issues (including when a field is defined multiple // times with different data types). ss_plugin_table_field_t* (*add_table_field)(ss_plugin_table_t* t, const char* name, ss_plugin_state_type data_type); -} ss_plugin_table_fields_vtable; +} ss_plugin_table_fields_vtable_ext; + +// Supported by the API but deprecated. Use the extended version ss_plugin_table_reader_vtable_ext instead. +// todo(jasondellaluce): when/if major changes to v4, remove this and +// give this name to the associated *_ext struct. +typedef struct +{ + const char* (*get_table_name)(ss_plugin_table_t* t); + uint64_t (*get_table_size)(ss_plugin_table_t* t); + ss_plugin_table_entry_t* (*get_table_entry)(ss_plugin_table_t* t, const ss_plugin_state_data* key); + ss_plugin_rc (*read_entry_field)(ss_plugin_table_t* t, ss_plugin_table_entry_t* e, const ss_plugin_table_field_t* f, ss_plugin_state_data* out); +} ss_plugin_table_reader_vtable; + +// Opaque pointer to the state data relative to a state table iteration. +// This is passed initially by the invoker when starting the iteration, and +// is then dispatched to the iterator for each of the entries of the table. +typedef void ss_plugin_table_iterator_state_t; + +// Iterator function callback used by a plugin for looping through all the +// entries of a given state table. Returns true if the iteration should +// proceed to the next element, or false in case of break out. +typedef ss_plugin_bool (*ss_plugin_table_iterator_func_t)(ss_plugin_table_iterator_state_t* s, ss_plugin_table_entry_t* e); -// Vtable for controlling a state table for read operations. -// todo(jasondellaluce): support looping over a table typedef struct { // Returns the table's name, or NULL in case of error. @@ -89,6 +119,8 @@ typedef struct // Returns an opaque pointer to an entry present in the table at the given // key, or NULL in case of issues (including if no entry is found at the // given key). The returned pointer is owned by the table's owner. + // Every non-NULL returned entry must be released by invoking release_table_entry() + // once it becomes no more used by the invoker. ss_plugin_table_entry_t* (*get_table_entry)(ss_plugin_table_t* t, const ss_plugin_state_data* key); // // Reads the value of an entry field from a table's entry. @@ -96,7 +128,30 @@ typedef struct // The read value is stored in the "out" parameter. // Returns SS_PLUGIN_SUCCESS if successful, and SS_PLUGIN_FAILURE otherwise. ss_plugin_rc (*read_entry_field)(ss_plugin_table_t* t, ss_plugin_table_entry_t* e, const ss_plugin_table_field_t* f, ss_plugin_state_data* out); -} ss_plugin_table_reader_vtable; + // + // Releases a table entry obtained by from previous invocation of get_table_entry(). + // After being released, the same table entry cannot be reused by the invoker. + // However, the same entry can be re-obtained through an invocation of get_table_entry(). + void (*release_table_entry)(ss_plugin_table_t* t, ss_plugin_table_entry_t* e); + // + // Iterates through all the entries of a table, invoking the interation + // callback function for each of them. Returns false in case of failure or + // iteration break-out, and true otherwise. + ss_plugin_bool (*iterate_entries)(ss_plugin_table_t* t, ss_plugin_table_iterator_func_t it, ss_plugin_table_iterator_state_t* s); +} ss_plugin_table_reader_vtable_ext; + +// Supported by the API but deprecated. Use the extended version ss_plugin_table_writer_vtable_ext instead. +// todo(jasondellaluce): when/if major changes to v4, remove this and +// give this name to the associated *_ext struct. +typedef struct +{ + ss_plugin_rc (*clear_table)(ss_plugin_table_t* t); + ss_plugin_rc (*erase_table_entry)(ss_plugin_table_t* t, const ss_plugin_state_data* key); + ss_plugin_table_entry_t* (*create_table_entry)(ss_plugin_table_t* t); + void (*destroy_table_entry)(ss_plugin_table_t* t, ss_plugin_table_entry_t* e); + ss_plugin_table_entry_t* (*add_table_entry)(ss_plugin_table_t* t, const ss_plugin_state_data* key, ss_plugin_table_entry_t* entry); + ss_plugin_rc (*write_entry_field)(ss_plugin_table_t* t, ss_plugin_table_entry_t* e, const ss_plugin_table_field_t* f, const ss_plugin_state_data* in); +} ss_plugin_table_writer_vtable; // Vtable for controlling a state table for write operations. typedef struct @@ -125,7 +180,8 @@ typedef struct // with the given key. If another entry is already present with the same key, // it gets replaced. After insertion, table will be come the owner of the // entry's pointer. Returns an opaque pointer to the newly-added table's entry, - // or NULL in case of error. + // or NULL in case of error. Every non-NULL returned entry must be released + // by invoking release_table_entry() once it becomes no more used by the invoker. ss_plugin_table_entry_t* (*add_table_entry)(ss_plugin_table_t* t, const ss_plugin_state_data* key, ss_plugin_table_entry_t* entry); // // Updates a table's entry by writing a value for one of its fields. @@ -133,7 +189,7 @@ typedef struct // The written value is read from the "in" parameter. // Returns SS_PLUGIN_SUCCESS if successful, and SS_PLUGIN_FAILURE otherwise. ss_plugin_rc (*write_entry_field)(ss_plugin_table_t* t, ss_plugin_table_entry_t* e, const ss_plugin_table_field_t* f, const ss_plugin_state_data* in); -} ss_plugin_table_writer_vtable; +} ss_plugin_table_writer_vtable_ext; // Plugin-provided input passed to the add_table() callback of // ss_plugin_init_tables_input, that can be used by the plugin to inform its @@ -154,14 +210,29 @@ typedef struct // This will be passed as parameters to all the callbacks defined below. ss_plugin_table_t* table; // - // Vtable for controlling read operations on the state table. + // Supported but deprecated. Use the extended version reader_ext. + // todo(jasondellaluce): when/if major changes to v4, remove this and + // give this name to the associated *_ext pointer. ss_plugin_table_reader_vtable reader; // - // Vtable for controlling write operations on the state table. + // Supported but deprecated. Use the extended version writer_ext. + // todo(jasondellaluce): when/if major changes to v4, remove this and + // give this name to the associated *_ext pointer. ss_plugin_table_writer_vtable writer; // - // Vtable for controlling operations related to fields on the state table. + // Supported but deprecated. Use the extended version fields_ext. + // todo(jasondellaluce): when/if major changes to v4, remove this and + // give this name to the associated *_ext pointer. ss_plugin_table_fields_vtable fields; + // + // Vtable for controlling read operations on the state table. + ss_plugin_table_reader_vtable_ext* reader_ext; + // + // Vtable for controlling write operations on the state table. + ss_plugin_table_writer_vtable_ext* writer_ext; + // + // Vtable for controlling operations related to fields on the state table. + ss_plugin_table_fields_vtable_ext* fields_ext; } ss_plugin_table_input; // Initialization-time input related to the event parsing capability. @@ -185,9 +256,14 @@ typedef struct // by other actors of the plugin's owner to interact with the state table. ss_plugin_rc (*add_table)(ss_plugin_owner_t* o, const ss_plugin_table_input* in); // + // Supported but deprecated. Use the extended version fields_ext. + // todo(jasondellaluce): when/if major changes to v4, remove this and + // give this name to the associated *_ext pointer. + ss_plugin_table_fields_vtable fields; + // // Vtable for controlling operations related to fields on the state tables // registeted in the plugin's owner. - ss_plugin_table_fields_vtable fields; + ss_plugin_table_fields_vtable_ext* fields_ext; } ss_plugin_init_tables_input; // Input passed at the plugin through plugin_init(). This contain information @@ -240,8 +316,13 @@ typedef struct ss_plugin_field_extract_input // extract_fields() call. ss_plugin_extract_field *fields; // - // Vtable for controlling a state table for read operations. + // Supported but deprecated. Use the extended version table_reader_ext. + // todo(jasondellaluce): when/if major changes to v4, remove this and + // give this name to the associated *_ext pointer. ss_plugin_table_reader_vtable table_reader; + // + // Vtable for controlling a state table for read operations. + ss_plugin_table_reader_vtable_ext* table_reader_ext; } ss_plugin_field_extract_input; // Input passed to the plugin when parsing an event for the event parsing @@ -258,11 +339,21 @@ typedef struct ss_plugin_event_parse_input // The string pointer is owned by the plugin's owenr. const char *(*get_owner_last_error)(ss_plugin_owner_t *o); // - // Vtable for controlling a state table for read operations. + // Supported but deprecated. Use the extended version table_reader_ext. + // todo(jasondellaluce): when/if major changes to v4, remove this and + // give this name to the associated *_ext pointer. ss_plugin_table_reader_vtable table_reader; // - // Vtable for controlling a state table for write operations. + // Supported but deprecated. Use the extended version table_writer_ext. + // todo(jasondellaluce): when/if major changes to v4, remove this and + // give this name to the associated *_ext pointer. ss_plugin_table_writer_vtable table_writer; + // + // Vtable for controlling a state table for read operations. + ss_plugin_table_reader_vtable_ext* table_reader_ext; + // + // Vtable for controlling a state table for write operations. + ss_plugin_table_writer_vtable_ext* table_writer_ext; } ss_plugin_event_parse_input; // @@ -578,8 +669,18 @@ typedef struct struct { // - // Return the list of event types that this plugin can consume + // Return the list of event types that this plugin will receive // for field extraction. The event types follow the libscap specific. + // This will be invoked only once by the framework after the plugin's + // initialization. Events that are not included in the returned list + // will not be received by the plugin. + // + // This is a non-functional filter that should not influence the plugin's + // functional behavior. Instead, this is a performance optimization + // with the goal of avoiding unnecessary communication between the + // framework and the plugin for events that are known to be not used for + // field extraction. + // // Required: no // // This function is optional--if NULL or an empty array, then: @@ -587,7 +688,9 @@ typedef struct // get_extract_event_sources (either default or custom) is compatible // with the "syscall" event source, otherwise // - the plugin will only receive events of plugin type (code 322). - uint16_t* (*get_extract_event_types)(uint32_t* numtypes); + // todo(jasondellaluce): when/if major changes to v4, reorder the arguments + // and put ss_plugin_t* as first + uint16_t* (*get_extract_event_types)(uint32_t* numtypes, ss_plugin_t* s); // // Return a string describing the event sources that this plugin @@ -665,8 +768,17 @@ typedef struct struct { // - // Return the list of event types that this plugin is capable of parsing. - // The event types follow the libscap specific. + // Return the list of event types that this plugin will receive + // for event parsing. The event types follow the libscap specific. + // This will be invoked only once by the framework after the plugin's + // initialization. Events that are not included in the returned list + // will not be received by the plugin. + // + // This is a non-functional filter that should not influence the plugin's + // functional behavior. Instead, this is a performance optimization + // with the goal of avoiding unnecessary communication between the + // framework and the plugin for events that are known to be not used for + // event parsing. // // Required: no // @@ -675,7 +787,9 @@ typedef struct // get_parse_event_sources (either default or custom) is compatible // with the "syscall" event source, otherwise // - the plugin will only receive events of plugin type (code 322). - uint16_t* (*get_parse_event_types)(uint32_t* numtypes); + // todo(jasondellaluce): when/if major changes to v4, reorder the arguments + // and put ss_plugin_t* as first + uint16_t* (*get_parse_event_types)(uint32_t* numtypes, ss_plugin_t* s); // // Return a string describing the event sources that this plugin // is capable of parsing.