commit 5b73822c55baf8b3c2bd5b6c6fc6e87887c06c7c Author: tezc Date: Wed Nov 11 01:19:49 2020 +0300 init diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 0000000..2726ce7 --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,12 @@ +freebsd_task: + only_if: $CIRRUS_BRANCH == 'master' + use_compute_credits: $CIRRUS_USER_COLLABORATOR == 'true' + freebsd_instance: + image_family: freebsd-12-1 + cpu: 1 + memory: 2G + test_script: + - pkg install -y git cmake + - mkdir build && cd build + - cmake -DSANITIZER=address .. && make -j && make check && rm -rf * + - cmake -DSANITIZER=undefined .. && make -j && make check && rm -rf * \ No newline at end of file diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..78ed555 --- /dev/null +++ b/.clang-format @@ -0,0 +1,118 @@ +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: true +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: No +BinPackArguments: true +BinPackParameters: true + +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 8 +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - sc_array_foreach + - sc_list_foreach + - sc_list_foreach_safe + - sc_map_foreach + - sc_map_foreach_key + - sc_map_foreach_value + - sc_queue_foreach +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^("[.a-zA-Z0-9_-]+\.h")' + Priority: 1 + + # Local headers: "foo/bar.h" + - Regex: '^("[.a-zA-Z0-9_/-]+\.h")' + Priority: 2 + + # C Header: , , etc + - Regex: '^(<[.a-zA-Z0-9_/-]+\.h>)' + Priority: 3 + +IncludeIsMainRegex: '[^_test]' +IndentPPDirectives: BeforeHash +IndentGotoLabels: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: None +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 100000000 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Latest +TabWidth: 4 +UseTab: Never +... + diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..cd368d1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.h linguist-language=C \ No newline at end of file diff --git a/.github/workflows/.actions.yml b/.github/workflows/.actions.yml new file mode 100644 index 0000000..2f14930 --- /dev/null +++ b/.github/workflows/.actions.yml @@ -0,0 +1,147 @@ +on: [ push, pull_request ] + +jobs: + archs: + # The host should always be linux + runs-on: ubuntu-18.04 + name: Build on ${{ matrix.distro }} ${{ matrix.arch }} + + # Run steps on a matrix of 3 arch/distro combinations + strategy: + fail-fast: false + matrix: + include: + - arch: armv6 + distro: buster + - arch: armv7 + distro: buster + - arch: aarch64 + distro: fedora_latest + - arch: ppc64le + distro: fedora_latest + - arch: s390x + distro: fedora_latest + + steps: + - uses: actions/checkout@v2.1.0 + - uses: uraimo/run-on-arch-action@v2.0.7 + name: Build artifact + id: build + with: + arch: ${{ matrix.arch }} + distro: ${{ matrix.distro }} + + # Not required, but speeds up builds + githubToken: ${{ github.token }} + + # Pass some environment variables to the container + env: | # YAML, but pipe character is necessary + artifact_name: git-${{ matrix.distro }}_${{ matrix.arch }} + + # The shell to run commands with in the container + shell: /bin/sh + + # Install some dependencies in the container. This speeds up builds if + # you are also using githubToken. Any dependencies installed here will + # be part of the container image that gets cached, so subsequent + # builds don't have to re-install them. The image layer is cached + # publicly in your project's package repository, so it is vital that + # no secrets are present in the container state or logs. + install: | + case "${{ matrix.distro }}" in + ubuntu*|jessie|stretch|buster) + apt-get update -q -y + apt-get install -q -y build-essential git gcc valgrind cmake + ;; + fedora*) + dnf -y update + dnf -y install git gcc valgrind cmake + ;; + alpine*) + apk update + apk add libexecinfo-dev linux-headers util-linux alpine-sdk perf libc-dev + apk add git gcc clang valgrind cmake + ;; + esac + + # Produce a binary artifact and place it in the mounted volume + run: | + uname -a;id;uname -m;lscpu | grep Endian + mkdir build && cd build + cmake .. && make -j && make check + ubuntu: + runs-on: ubuntu-latest + name: Build on Ubuntu + + strategy: + fail-fast: false + matrix: + compiler: [ gcc, clang ] + steps: + - uses: actions/checkout@v2.1.0 + - name: build + env: + CC: ${{ matrix.compiler }} + run: | + sudo apt-get install valgrind cmake + mkdir build-debug && cd build-debug + cmake .. -DSANITIZER=address + make -j + make check + rm -rf * + cmake .. -DSANITIZER=undefined + make -j + make check + rm -rf * + cmake .. + make -j + make valgrind + macos: + runs-on: macos-latest + name: Build on Mac OS + + strategy: + fail-fast: false + matrix: + compiler: [ gcc, clang ] + + steps: + - uses: actions/checkout@v2.1.0 + - name: build + env: + CC: ${{ matrix.compiler }} + run: | + mkdir build-debug && cd build-debug + cmake .. -DSANITIZER=address + make -j + make check + rm -rf * + cmake .. -DSANITIZER=undefined + make -j + make check + + windows: + runs-on: windows-latest + name: Build on Windows + + steps: + - uses: actions/checkout@v2.1.0 + - name: build + run: | + mkdir build-debug && cd build-debug + cmake -G "Visual Studio 16 2019" -A x64 .. + cmake --build . + ctest -C Debug + coverage: + runs-on: ubuntu-latest + name: Coverage + steps: + - uses: actions/checkout@v2.1.0 + - name: build + run: | + sudo apt-get install cmake lcov + mkdir build-debug && cd build-debug + cmake .. -DCMAKE_BUILD_TYPE=Coverage + make -j + make coverage + bash <(curl -s https://codecov.io/bash) -f coverage.info -t ${{ secrets.CODECOV }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..966ea03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf +**/build* +**/cmake* +*.idea* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e77b1bc --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,75 @@ +cmake_minimum_required(VERSION 3.5.1) +project(tz_lib C) +include(CTest) +include(CheckCCompilerFlag) + +enable_testing() + +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Debug") +endif () + +message(STATUS "Build type ${CMAKE_BUILD_TYPE}") + +add_subdirectory(array) +add_subdirectory(crc32) +add_subdirectory(heap) +add_subdirectory(ini) +add_subdirectory(linked-list) +add_subdirectory(logger) +add_subdirectory(map) +add_subdirectory(mutex) +add_subdirectory(queue) +add_subdirectory(perf) +add_subdirectory(string) +add_subdirectory(time) +add_subdirectory(timer) + + + +# --------------------------------------------------------------------------- # +# --------------------- Test Configuration Start ---------------------------- # +# --------------------------------------------------------------------------- # + +# ----------------------- - Code Coverage Start ----------------------------- # + +if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + add_compile_options(--coverage) + link_libraries(gcov) + else() + message(FATAL_ERROR "Only GCC is supported for coverage") + endif() +endif () + +add_custom_target(coverage) +add_custom_command( + TARGET coverage + COMMAND lcov --capture --directory .. + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*' + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1 +) + +# -------------------------- Code Coverage End ------------------------------ # + +SET(MEMORYCHECK_COMMAND_OPTIONS + "-q --log-fd=2 --trace-children=yes --track-origins=yes \ + --leak-check=full --show-leak-kinds=all --show-reachable=yes \ + --error-exitcode=255") + +add_custom_target(valgrind ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ + --overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS} + --verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_custom_target(check ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ --verbose + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_dependencies(coverage check) + +# ----------------------- Test Configuration End ---------------------------- # \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..18b2601 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Ozan Tezcan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ed545f --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ + +## Standalone C libraries +- Each sub-folder is independent, consist one *.c *.h pair, just copy files to your project. + +##### OS +- Linux, Windows, macOS : ![.github/workflows/.actions.yml](https://github.com/tezc/simple-c/workflows/.github/workflows/.actions.yml/badge.svg) +- FreeBSD : [![Build Status](https://api.cirrus-ci.com/github/tezc/simple-c.svg)](https://cirrus-ci.com/github/tezc/simple-c) +[![codecov](https://codecov.io/gh/tezc/simple-c/branch/master/graph/badge.svg)](https://codecov.io/gh/tezc/simple-c) + + +## Vector + +| | | | +|-----------------|-----------------------------------------------------------------------------| +| **array** | Generic growable array | +| **crc32** | Crc32 implementation contains hardware & software versions | +| **heap** | Heap implementation which can be used as minheap/max heap/priority queue | +| **ini** | Ini file parser | +| **linked-list** | Intrusive linked-list which can be used as queue/dequeue/stack etc. | +| **logger** | Logger which is configurable to deliver logs to file/stdout/callback. | +| **map** | Generic hashmap | +| **mutex** | Mutex wrapper for POSIX and Windows | +| **perf** | Simple benchmarking tool for Linux | +| **queue** | Generic queue implementation which can be used as queue/dequeue/stack etc. | +| **string** | Length prefixed string with a few utility functions | +| **time** | Time functions for POSIX and Windows | +| **timer** | Hashed timer wheel implementation | + + +#### OS +| | | +|---------------------|----------| +| Linux | +| Windows +| MacOS +| FreeBSD \ No newline at end of file diff --git a/array/CMakeLists.txt b/array/CMakeLists.txt new file mode 100644 index 0000000..e892a21 --- /dev/null +++ b/array/CMakeLists.txt @@ -0,0 +1,97 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_array C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(sc_array array_example.c sc_array.h sc_array.c) + +if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror") +endif () + + +# --------------------------------------------------------------------------- # +# --------------------- Test Configuration Start ---------------------------- # +# --------------------------------------------------------------------------- # + +include(CTest) +include(CheckCCompilerFlag) + +enable_testing() + +add_executable(${PROJECT_NAME}_test array_test.c sc_array.c) + +target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=140000ul) + +message(STATUS "Compiler is ${CMAKE_C_COMPILER_ID}") + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + message(STATUS "Defined SC_HAVE_WRAP") + target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin) + target_link_options(${PROJECT_NAME}_test PRIVATE -Wl,--wrap=realloc) + endif () +endif () + +if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer) + + if (SANITIZER) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + endif () +endif () + +add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test) + +SET(MEMORYCHECK_COMMAND_OPTIONS + "-q --log-fd=2 --trace-children=yes --track-origins=yes \ + --leak-check=full --show-leak-kinds=all --show-reachable=yes \ + --error-exitcode=255") + +add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ + --overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS} + --verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ --verbose + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +# ----------------------- - Code Coverage Start ----------------------------- # + +if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage) + target_link_libraries(${PROJECT_NAME}_test gcov) + else() + message(FATAL_ERROR "Only GCC is supported for coverage") + endif() +endif () + +add_custom_target(coverage_${PROJECT_NAME}) +add_custom_command( + TARGET coverage_${PROJECT_NAME} + COMMAND lcov --capture --directory .. + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*' + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1 +) + +add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME}) + +# -------------------------- Code Coverage End ------------------------------ # + + +# ----------------------- Test Configuration End ---------------------------- # + diff --git a/array/README.md b/array/README.md new file mode 100644 index 0000000..545da7f --- /dev/null +++ b/array/README.md @@ -0,0 +1,68 @@ +# Generic array + +#### Overview + +- Type generic array which grows when you add elements. +- Index access is possible (e.g float* arr; 'printf("%f", arr[i]')). +- Just copy sc_array.h and sc_array.c to your project. + + +##### Usage + + +```c + int *p; + + sc_array_create(p, 0); + + sc_array_add(p, 0); + sc_array_add(p, 1); + sc_array_add(p, 3); + + printf("\nRemoving first element \n\n"); + sc_array_remove(p, 0); + + printf("Capacity %zu \n", sc_array_cap(p)); + printf("Element count %zu \n", sc_array_size(p)); + + + // Simple loop + for (int i = 0; i < sc_array_size(p); i++) { + printf("Elem = %d \n", p[i]); + } + + sc_array_destroy(p); +``` +#### Internals + +##### Memory +- Single allocation for all the data. +- Lazy allocation. No memory allocation until first 'add'. + +##### Performance +- As all the items are in a single contiguous memory, it gives the best +performance you can expect. + +##### Note + +Array pointer is not stable. If you pass the array to another function which +can add items, do it by passing reference of the array pointer : + +```c +void some_function_to_add_elems(long **p) +{ + sc_array_add(*p, 500); +} + +int main(int argc, char *argv[]) +{ + long *p; + + sc_array_create(p, 0); + sc_array_add(p, 300); + + some_function_to_add_elems(&p); + sc_array_destroy(p); +} + +``` diff --git a/array/array_example.c b/array/array_example.c new file mode 100644 index 0000000..9fe38d4 --- /dev/null +++ b/array/array_example.c @@ -0,0 +1,29 @@ +#include "sc_array.h" + +#include + +int main(int argc, char *argv[]) +{ + int *p; + + sc_array_create(p, 0); + + sc_array_add(p, 0); + sc_array_add(p, 1); + sc_array_add(p, 3); + + printf("\nRemoving first element \n\n"); + sc_array_remove(p, 0); + + printf("Capacity %zu \n", sc_array_cap(p)); + printf("Element count %zu \n", sc_array_size(p)); + + // Simple loop + for (int i = 0; i < sc_array_size(p); i++) { + printf("Elem = %d \n", p[i]); + } + + sc_array_destroy(p); + + return 0; +} diff --git a/array/array_test.c b/array/array_test.c new file mode 100644 index 0000000..30e475e --- /dev/null +++ b/array/array_test.c @@ -0,0 +1,174 @@ +#include "sc_array.h" + +#include +#include +#include + +int example() +{ + int *p; + + sc_array_create(p, 0); + + sc_array_add(p, 0); + sc_array_add(p, 1); + sc_array_add(p, 3); + + printf("\nRemoving first element \n\n"); + sc_array_remove(p, 0); + + printf("Capacity %zu \n", sc_array_cap(p)); + printf("Element count %zu \n", sc_array_size(p)); + + for (int i = 0; i < sc_array_size(p); i++) { + printf("Elem = %d \n", p[i]); + } + + sc_array_destroy(p); + + return 0; +} + +static int compare(const void *a, const void *b) +{ + const int *x = a; + const int *y = b; + + return *x - *y; +} + +static void test1(void) +{ + int *arr, total = 0; + + sc_array_create(arr, 5); + sc_array_add(arr, 3); + sc_array_add(arr, 4); + sc_array_add(arr, 5); + + assert(sc_array_size(arr) == 3); + + sc_array_remove(arr, 0); + assert(arr[0] == 4); + sc_array_remove_last(arr); + assert(arr[0] == 4); + + sc_array_add(arr, 1); + sc_array_add(arr, 3); + sc_array_add(arr, 2); + sc_array_add(arr, 0); + + sc_array_sort(arr, compare); + + for (int i = 0; i < sc_array_size(arr); i++) { + total += arr[i]; + } + + assert(total == 10); + + for (int i = 0; i < sc_array_size(arr); i++) { + assert(arr[i] == i); + } + + sc_array_destroy(arr); +} + +#ifdef SC_HAVE_WRAP + +bool fail_realloc = false; +void *__real_realloc(void *p, size_t size); +void *__wrap_realloc(void *p, size_t n) +{ + if (fail_realloc) { + return NULL; + } + + return __real_realloc(p, n); +} + +void fail_test() +{ + int *arr, total = 0; + + assert(sc_array_create(arr, SIZE_MAX) == false); + assert(arr == NULL); + assert(sc_array_create(arr, 0) == true); + assert(arr != NULL); + sc_array_destroy(arr); + assert(arr == NULL); + assert(sc_array_create(arr, 0) == true); + + size_t count = SC_SIZE_MAX / sizeof(*arr); + bool success = false; + + for (int i = 0; i < count + 5; i++) { + success = sc_array_add(arr, i); + } + + assert(!success); + + sc_array_destroy(arr); + sc_array_create(arr, 0); + assert(sc_array_size(arr) == 0); + + fail_realloc = true; + success = sc_array_add(arr, 0); + assert(!success); + + fail_realloc = false; + success = sc_array_add(arr, 222); + assert(success); + sc_array_destroy(arr); + + fail_realloc = true; + assert(sc_array_create(arr, 222) == false); + fail_realloc = false; + + assert(sc_array_create(arr, 0) == true); + fail_realloc = true; + success = sc_array_add(arr, 222); + assert(!success); + fail_realloc = false; + + sc_array_add(arr, 3); + sc_array_add(arr, 4); + sc_array_add(arr, 5); + + assert(sc_array_size(arr) == 3); + + sc_array_remove(arr, 0); + assert(arr[0] == 4); + sc_array_remove_last(arr); + assert(arr[0] == 4); + + sc_array_add(arr, 1); + sc_array_add(arr, 3); + sc_array_add(arr, 2); + sc_array_add(arr, 0); + + sc_array_sort(arr, compare); + + for (int i = 0; i < sc_array_size(arr); i++) { + total += arr[i]; + } + + assert(total == 10); + + for (int i = 0; i < sc_array_size(arr); i++) { + assert(arr[i] == i); + } + + sc_array_destroy(arr); +} +#else +void fail_test(void) +{ +} +#endif + +int main(int argc, char *argv[]) +{ + example(); + test1(); + fail_test(); +} diff --git a/array/sc_array.c b/array/sc_array.c new file mode 100644 index 0000000..a0d4538 --- /dev/null +++ b/array/sc_array.c @@ -0,0 +1,108 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "sc_array.h" + +#include +#include + +#ifndef SC_SIZE_MAX + #define SC_SIZE_MAX SIZE_MAX +#endif + + +/** + * Empty array instance. + * Zero element arrays point at it to avoid initial allocation, so unused + * arrays will not allocate memory. + */ +static const struct sc_array sc_empty = {.size = 0, .cap = 0}; + +bool sc_array_init(void **arr, size_t elem_size, size_t cap) +{ + struct sc_array *meta; + + if (cap == 0) { + *arr = (void *) sc_empty.elems; + return true; + } + + // Check overflow + if (cap > SC_SIZE_MAX / elem_size) { + *arr = NULL; + return false; + } + + meta = sc_array_realloc(NULL, sizeof(*meta) + (elem_size * cap)); + if (meta == NULL) { + *arr = NULL; + return false; + } + + meta->size = 0; + meta->cap = cap; + *arr = meta->elems; + + return true; +} + +void sc_array_term(void **arr) +{ + struct sc_array *meta = sc_array_meta(*arr); + + if (meta != &sc_empty) { + sc_array_free(meta); + } + + *arr = NULL; +} + +bool sc_array_expand(void **arr, size_t elem_size) +{ + size_t size, cap; + struct sc_array *prev, *meta = sc_array_meta(*arr); + + if (meta->size == meta->cap) { + + // Check overflow + if (meta->cap > SC_SIZE_MAX / elem_size / 2) { + return false; + } + + size = meta->size; + cap = (meta != &sc_empty) ? meta->cap * 2 : 2; + prev = (meta != &sc_empty) ? meta : NULL; + + meta = sc_array_realloc(prev, sizeof(*meta) + (elem_size * cap)); + if (meta == NULL) { + return false; + } + + meta->size = size; + meta->cap = cap; + *arr = meta->elems; + } + + return true; +} diff --git a/array/sc_array.h b/array/sc_array.h new file mode 100644 index 0000000..80b4bef --- /dev/null +++ b/array/sc_array.h @@ -0,0 +1,169 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SC_ARRAY_H +#define SC_ARRAY_H + +#include +#include +#include +#include +#include +#include + +/** + * Internals, do not use + */ +struct sc_array +{ + size_t size; + size_t cap; + uint8_t elems[]; +}; + +#define sc_array_meta(arr) \ + ((struct sc_array *) ((char *) (arr) -offsetof(struct sc_array, elems))) + +bool sc_array_init(void **arr, size_t elem_size, size_t cap); +void sc_array_term(void **arr); +bool sc_array_expand(void **arr, size_t elem_size); + +/** + * Internal End. + */ + + +/** + * Configure memory allocators here. You can plug your allocator if you want, + * replace 'realloc' and 'free' with your allocator, make sure you include + * new allocator header. + */ +#define sc_array_realloc realloc +#define sc_array_free free + +/** + * @param arr Array pointer + * @param cap Initial capacity, '0' is accepted, then no memory allocation + * will be made until first 'add' operation. + * @return 'true' on success, 'false' on out of memory + */ +#define sc_array_create(arr, cap) \ + sc_array_init((void **) &(arr), sizeof(*(arr)), cap) + +/** + * @param arr Array pointer + */ +#define sc_array_destroy(arr) sc_array_term((void **) &(arr)); + +/** + * @param arr Array pointer + * @return Current allocated capacity + */ +#define sc_array_cap(arr) (sc_array_meta((arr))->cap) + +/** + * @param arr Array pointer + * @return Current element count + */ +#define sc_array_size(arr) (sc_array_meta((arr))->size) + +/** + * Removes items from the list without deallocating underlying memory + * + * @param arr Array pointer + */ +#define sc_array_clear(arr) (sc_array_meta((arr))->size = 0) + +/** + * @param v Array pointer + * @param elem Element to be appended + * @return 'true' on success, 'false' if memory allocation fails if we try + * to expand underlying memory. + */ +#define sc_array_add(arr, elem) \ + sc_array_expand((void **) &((arr)), sizeof(*(arr))) == true ? \ + (arr)[sc_array_meta(arr)->size++] = (elem), \ + true : false + + +/** + * Removes the element at index i, moves elements to fill the space + * unless removed element is the last element + * + * vec[a,b,c,d,e,f] -> sc_array_remove(vec, 2) - > vec[a,b,d,f,e] + * + * @param arr Array pointer + * @param i Element index to be removed + * + * If 'i' is out of the range, result is undefined. + */ +#define sc_array_remove(arr, i) \ + do { \ + assert((i) < sc_array_meta(arr)->size); \ + const size_t to_move = sc_array_size(arr) - (i) -1; \ + if (to_move > 0) { \ + memmove(&(arr)[i], &(arr)[(i) + 1], to_move * sizeof(*(arr))); \ + } \ + sc_array_meta((arr))->size--; \ + } while (0) + +/** + * Removes an element at index i, replaces last element with removed element + * unless removed element is the last element. This is faster than moving + * elements but elements will no longer be in the push order + * + * arr[a,b,c,d,e,f] -> sc_array_remove_unordered(vec, 2) - > arr[a,b,f,d,e] + * + * @param arr Array pointer + * @param i Element index to be removed + * + * If 'i' is out of the range, result is undefined. + */ +#define sc_array_remove_unordered(arr, i) \ + do { \ + assert((i) < sc_array_meta(arr)->size); \ + (arr)[i] = (arr)[(--sc_array_meta((arr))->size)]; \ + } while (0) + + +/** + * Remove the last element. If there is no element, result is undefined. + * @param arr Array pointer + */ +#define sc_array_remove_last(arr) \ + do { \ + assert(sc_array_meta(arr)->size != 0); \ + sc_array_meta(arr)->size--; \ + } while (0) + +/** + * Sorts the array using qsort() + * @param arr Array pointer + * @param cmp Comparator, check qsort docs online for details + */ +#define sc_array_sort(arr, cmp) \ + (qsort((arr), sc_array_size((arr)), sizeof(*(arr)), cmp)) + + +#endif diff --git a/crc32/CMakeLists.txt b/crc32/CMakeLists.txt new file mode 100644 index 0000000..e2be2f9 --- /dev/null +++ b/crc32/CMakeLists.txt @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_crc32 C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(sc_crc32 crc32_example.c sc_crc32.h sc_crc32.c) + +if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror") +endif () + + +# --------------------------------------------------------------------------- # +# --------------------- Test Configuration Start ---------------------------- # +# --------------------------------------------------------------------------- # + +include(CTest) +include(CheckCCompilerFlag) + +enable_testing() + +add_executable(${PROJECT_NAME}_test crc32_test.c sc_crc32.c) + +if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer) + + if (SANITIZER) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + endif () +endif () + +add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test) + +SET(MEMORYCHECK_COMMAND_OPTIONS + "-q --log-fd=2 --trace-children=yes --track-origins=yes \ + --leak-check=full --show-leak-kinds=all --show-reachable=yes \ + --error-exitcode=255") + +add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ + --overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS} + --verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ --verbose + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +# ----------------------- - Code Coverage Start ----------------------------- # + +if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage) + target_link_libraries(${PROJECT_NAME}_test gcov) + else() + message(FATAL_ERROR "Only GCC is supported for coverage") + endif() +endif () + +add_custom_target(coverage_${PROJECT_NAME}) +add_custom_command( + TARGET coverage_${PROJECT_NAME} + COMMAND lcov --capture --directory .. + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*' + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1 +) + +add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME}) + +# -------------------------- Code Coverage End ------------------------------ # + + +# ----------------------- Test Configuration End ---------------------------- # + diff --git a/crc32/README.md b/crc32/README.md new file mode 100644 index 0000000..524cd26 --- /dev/null +++ b/crc32/README.md @@ -0,0 +1,54 @@ +# CRC32c function + +- Same code from : https://stackoverflow.com/a/17646775 +- Fixed some alignment issues, replaced asm code with compiler intrinsics +- Just copy sc_crc32.h and sc_crc32.c to your project. + +- Compile time switch to hardware version if supported (crc32c instruction on x64), + fallback to software version if not available +- See CmakeLists.txt, it just checks "-msse4.2" flag. Stackoverflow answer has + runtime dispatch between hardware and software versions if you'd like that. + + + - This is Crc32c algorithm, not Crc32 + - A faster version might be possible with 'PCLMULQDQ' instruction as explained here + + [[link]](https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/crc-iscsi-polynomial-crc32-instruction-paper.pdf) + + + ```cmake +## Cmake + +## Only use hardware version in 64 bit architectures. +if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + check_c_compiler_flag(-msse4.2 HAVE_CRC32_HARDWARE) + if (${HAVE_CRC32_HARDWARE}) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse4.2 -DSC_HAVE_CRC32_HARDWARE") + endif () +endif() +``` + +```c +#include "sc_crc32.h" + +#include + +int main(int argc, char *argv[]) +{ + uint32_t crc; + const uint8_t buf[100] = {0}; + + sc_crc32_global_init(); + + // Partial calculation example + crc = sc_crc32(0, buf, 10); + crc = sc_crc32(crc, buf + 10, sizeof(buf) - 10); + printf("crc : %u \n", crc); + + // Calculate at once + crc = sc_crc32(0, buf, sizeof(buf)); + printf("crc : %u \n", crc); + + return 0; +} +``` \ No newline at end of file diff --git a/crc32/crc32_example.c b/crc32/crc32_example.c new file mode 100644 index 0000000..35c75a2 --- /dev/null +++ b/crc32/crc32_example.c @@ -0,0 +1,22 @@ +#include "sc_crc32.h" + +#include + +int main(int argc, char *argv[]) +{ + uint32_t crc; + const uint8_t buf[100] = {0}; + + sc_crc32_global_init(); + + // Partial calculation example + crc = sc_crc32(0, buf, 10); + crc = sc_crc32(crc, buf + 10, sizeof(buf) - 10); + printf("crc : %u \n", crc); + + // Calculate at once + crc = sc_crc32(0, buf, sizeof(buf)); + printf("crc : %u \n", crc); + + return 0; +} diff --git a/crc32/crc32_test.c b/crc32/crc32_test.c new file mode 100644 index 0000000..31845b7 --- /dev/null +++ b/crc32/crc32_test.c @@ -0,0 +1,24 @@ +#include "sc_crc32.h" + +#include + +int main(int argc, char *argv[]) +{ + uint32_t crc1, crc2, crc3; + uint8_t buf[128] = {1, 1, 2, 3}; + uint8_t buf2[4096 * 8] = {2 , 5, 6 ,5}; + + sc_crc32_global_init(); + + crc1 = sc_crc32(0, buf, 100); + crc2 = sc_crc32(crc1, buf + 100, 28); + crc3 = sc_crc32(0, buf, 128); + + assert(crc2 == crc3); + + crc1 = sc_crc32(0, buf2, 4096 * 4); + crc2 = sc_crc32(crc1, buf2 + (4096 * 4), 4096 * 4); + crc3 = sc_crc32(0, buf2, 4096 * 8); + + assert(crc2 == crc3); +} diff --git a/crc32/sc_crc32.c b/crc32/sc_crc32.c new file mode 100644 index 0000000..273cdb9 --- /dev/null +++ b/crc32/sc_crc32.c @@ -0,0 +1,339 @@ +/* crc32c.c -- compute CRC-32C using the Intel crc32 instruction + * Copyright (C) 2013 Mark Adler + * Version 1.1 1 Aug 2013 Mark Adler + */ + +/* + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler + madler@alumni.caltech.edu + */ + +/* Use hardware CRC instruction on Intel SSE 4.2 processors. This computes a + CRC-32C, *not* the CRC-32 used by Ethernet and zip, gzip, etc. A software + version is provided as a fall-back, as well as for speed comparisons. */ + +/* Version history: + 1.0 10 Feb 2013 First version + 1.1 1 Aug 2013 Correct comments on why three crc instructions in parallel + 1.2 2020 Added gcc intrinsics, fixed alignment issues. + */ + +#include +#include +#include + +/* CRC-32C (iSCSI) polynomial in reversed bit order. */ +#define CRC32_POLY 0x82f63b78 + +#ifdef SC_HAVE_CRC32_HARDWARE +#include + +/* Multiply a matrix times a vector over the Galois field of two elements, + GF(2). Each element is a bit in an unsigned integer. mat must have at + least as many entries as the power of two for most significant one bit in + vec. */ +static inline uint32_t gf2_matrix_times(uint32_t *mat, uint32_t vec) +{ + uint32_t sum = 0; + + while (vec) { + if (vec & 1) { + sum ^= *mat; + } + + vec >>= 1; + mat++; + } + + return sum; +} + +/* Multiply a matrix by itself over GF(2). Both mat and square must have 32 + rows. */ +static inline void gf2_matrix_square(uint32_t *square, uint32_t *mat) +{ + for (int n = 0; n < 32; n++) { + square[n] = gf2_matrix_times(mat, mat[n]); + } +} + +/* Construct an operator to apply len zeros to a crc. len must be a power of + two. If len is not a power of two, then the result is the same as for the + largest power of two less than len. The result for len == 0 is the same as + for len == 1. A version of this routine could be easily written for any + len, but that is not needed for this application. */ +static void crc32_zeros_op(uint32_t *even, size_t len) +{ + uint32_t row = 1; + uint32_t odd[32]; /* odd-power-of-two zeros operator */ + + /* put operator for one zero bit in odd */ + odd[0] = CRC32_POLY; /* CRC-32C polynomial */ + + for (int n = 1; n < 32; n++) { + odd[n] = row; + row <<= 1; + } + + /* put operator for two zero bits in even */ + gf2_matrix_square(even, odd); + + /* put operator for four zero bits in odd */ + gf2_matrix_square(odd, even); + + /* first square will put the operator for one zero byte (eight zero bits), + in even -- next square puts operator for two zero bytes in odd, and so + on, until len has been rotated down to zero */ + do { + gf2_matrix_square(even, odd); + len >>= 1; + if (len == 0) { + return; + } + gf2_matrix_square(odd, even); + len >>= 1; + } while (len); + + /* answer ended up in odd -- copy to even */ + for (int n = 0; n < 32; n++) { + even[n] = odd[n]; + } +} + +/* Take a length and build four lookup tables for applying the zeros operator + for that length, byte-by-byte on the operand. */ +static void crc32_zeros(uint32_t zeros[][256], size_t len) +{ + uint32_t op[32]; + + crc32_zeros_op(op, len); + + for (uint32_t n = 0; n < 256; n++) { + zeros[0][n] = gf2_matrix_times(op, n); + zeros[1][n] = gf2_matrix_times(op, n << 8); + zeros[2][n] = gf2_matrix_times(op, n << 16); + zeros[3][n] = gf2_matrix_times(op, n << 24); + } +} + +/* Apply the zeros operator table to crc. */ +static inline uint32_t crc32_shift(uint32_t zeros[][256], uint32_t crc) +{ + return zeros[0][(crc >> 0) & 0xff] ^ zeros[1][(crc >> 8) & 0xff] ^ + zeros[2][(crc >> 16) & 0xff] ^ zeros[3][(crc >> 24) & 0xff]; +} + +/* Block sizes for three-way parallel crc computation. LONG and SHORT must + both be powers of two. The associated string constants must be set + accordingly, for use in constructing the assembler instructions. */ +#define CRC32_LONG 2048 +#define CRC32_SHORT 256 + +static uint32_t crc32c_long[4][256]; +static uint32_t crc32c_short[4][256]; + +static void crc32_init_hw(void) +{ + crc32_zeros(crc32c_long, CRC32_LONG); + crc32_zeros(crc32c_short, CRC32_SHORT); +} + +uint32_t crc32_hw(uint32_t crc, const uint8_t *buf, uint32_t len) +{ + const unsigned char *next = buf; + const unsigned char *end; + uint64_t crc0, crc1, crc2; /* need to be 64 bits for crc32q */ + + /* pre-process the crc */ + crc0 = crc ^ 0xffffffff; + + /* compute the crc for up to seven leading bytes to bring the data pointer + to an eight-byte boundary */ + while (len && ((uintptr_t) next & 7) != 0) { + crc0 = _mm_crc32_u8(crc0, *next); + next++; + len--; + } + + /* compute the crc on sets of LONG*3 bytes, executing three independent crc + instructions, each on LONG bytes -- this is optimized for the Nehalem, + Westmere, Sandy Bridge, and Ivy Bridge architectures, which have a + throughput of one crc per cycle, but a latency of three cycles */ + while (len >= CRC32_LONG * 3) { + crc1 = 0; + crc2 = 0; + + end = next + CRC32_LONG; + do { + uint64_t a, b, c; + + memcpy(&a, next, 8); + memcpy(&b, next + CRC32_LONG, 8); + memcpy(&c, next + (CRC32_LONG * 2), 8); + + crc0 = _mm_crc32_u64(crc0, a); + crc1 = _mm_crc32_u64(crc1, b); + crc2 = _mm_crc32_u64(crc2, c); + + next += 8; + } while (next < end); + + crc0 = crc32_shift(crc32c_long, crc0) ^ crc1; + crc0 = crc32_shift(crc32c_long, crc0) ^ crc2; + + next += (CRC32_LONG * 2); + len -= (CRC32_LONG * 3); + } + + /* do the same thing, but now on SHORT * 3 blocks for the remaining data + less than a LONG * 3 block */ + while (len >= CRC32_SHORT * 3) { + crc1 = 0; + crc2 = 0; + + end = next + CRC32_SHORT; + do { + uint64_t a, b, c; + + memcpy(&a, next, 8); + memcpy(&b, next + CRC32_SHORT, 8); + memcpy(&c, next + (CRC32_SHORT * 2), 8); + + crc0 = _mm_crc32_u64(crc0, a); + crc1 = _mm_crc32_u64(crc1, b); + crc2 = _mm_crc32_u64(crc2, c); + + next += 8; + } while (next < end); + + crc0 = crc32_shift(crc32c_short, crc0) ^ crc1; + crc0 = crc32_shift(crc32c_short, crc0) ^ crc2; + + next += (CRC32_SHORT * 2); + len -= (CRC32_SHORT * 3); + } + + /* compute the crc on the remaining eight-byte units less than a SHORT * 3 + block */ + end = next + (len - (len & 7)); + while (next < end) { + uint64_t a; + + memcpy(&a, next, 8); + crc0 = _mm_crc32_u64(crc0, a); + next += 8; + } + len &= 7; + + /* compute the crc for up to seven trailing bytes */ + while (len) { + crc0 = _mm_crc32_u8(crc0, *next); + next++; + len--; + } + + /* return a post-processed crc */ + return (uint32_t) crc0 ^ 0xffffffff; +} + +#else + +/* Table for a quadword-at-a-time software crc. */ +static uint32_t crc32c_table[8][256]; + +/* Construct table for software CRC-32C calculation. */ +static void crc32_init_sw(void) +{ + for (uint32_t n = 0; n < 256; n++) { + uint32_t crc = n; + + crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1; + crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1; + crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1; + crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1; + crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1; + crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1; + crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1; + crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1; + + crc32c_table[0][n] = crc; + } + + for (uint32_t n = 0; n < 256; n++) { + uint32_t crc = crc32c_table[0][n]; + + for (uint32_t k = 1; k < 8; k++) { + crc = crc32c_table[0][crc & 0xff] ^ (crc >> 8); + crc32c_table[k][n] = crc; + } + } +} + +/* Table-driven software version as a fall-back. This is about 15 times slower + than using the hardware instructions. This assumes little-endian integers, + as is the case on Intel processors that the assembler code here is for. */ +static uint32_t crc32_sw(uint32_t crci, const void *buf, size_t len) +{ + const unsigned char *next = buf; + uint64_t crc = crci ^ 0xffffffff; + + while (len && ((uintptr_t) next & 7) != 0) { + crc = crc32c_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8); + len--; + } + + while (len >= 8) { + crc ^= *(uint64_t *) next; + crc = crc32c_table[7][crc & 0xff] ^ crc32c_table[6][(crc >> 8) & 0xff] ^ + crc32c_table[5][(crc >> 16) & 0xff] ^ + crc32c_table[4][(crc >> 24) & 0xff] ^ + crc32c_table[3][(crc >> 32) & 0xff] ^ + crc32c_table[2][(crc >> 40) & 0xff] ^ + crc32c_table[1][(crc >> 48) & 0xff] ^ crc32c_table[0][crc >> 56]; + next += 8; + len -= 8; + } + + while (len) { + crc = crc32c_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8); + len--; + } + + return (uint32_t) crc ^ 0xffffffff; +} + +#endif + +uint32_t sc_crc32(uint32_t crc, const uint8_t *buf, uint32_t len) +{ +#ifdef SC_HAVE_CRC32_HARDWARE + return crc32_hw(crc, buf, len); +#else + return crc32_sw(crc, buf, len); +#endif +} + +void sc_crc32_global_init() +{ +#ifdef SC_HAVE_CRC32_HARDWARE + crc32_init_hw(); +#else + crc32_init_sw(); +#endif +} diff --git a/crc32/sc_crc32.h b/crc32/sc_crc32.h new file mode 100644 index 0000000..6b5d5f9 --- /dev/null +++ b/crc32/sc_crc32.h @@ -0,0 +1,34 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SC_CRC32_H +#define SC_CRC32_H + +#include + +void sc_crc32_global_init(void); + +uint32_t sc_crc32(uint32_t crc, const uint8_t *buf, uint32_t len); + +#endif diff --git a/heap/CMakeLists.txt b/heap/CMakeLists.txt new file mode 100644 index 0000000..5a89498 --- /dev/null +++ b/heap/CMakeLists.txt @@ -0,0 +1,94 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_heap C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(sc_heap heap_example.c sc_heap.h sc_heap.c) + +if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -pedantic -Werror -D_GNU_SOURCE") +endif () + + +# --------------------------------------------------------------------------- # +# --------------------- Test Configuration Start ---------------------------- # +# --------------------------------------------------------------------------- # + +include(CTest) +include(CheckCCompilerFlag) + +enable_testing() + +add_executable(${PROJECT_NAME}_test heap_test.c sc_heap.c) + +target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=140000ul) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin) + target_link_options(${PROJECT_NAME}_test PRIVATE + -Wl,--wrap=malloc,--wrap=realloc) + endif () +endif () + +if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer) + + if (SANITIZER) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + endif () +endif () + +add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test) + +SET(MEMORYCHECK_COMMAND_OPTIONS + "-q --log-fd=2 --trace-children=yes --track-origins=yes \ + --leak-check=full --show-leak-kinds=all --show-reachable=yes \ + --error-exitcode=255") + +add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ + --overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS} + --verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ --verbose + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +# ----------------------- - Code Coverage Start ----------------------------- # + +if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage) + target_link_libraries(${PROJECT_NAME}_test gcov) + else() + message(FATAL_ERROR "Only GCC is supported for coverage") + endif() +endif () + +add_custom_target(coverage_${PROJECT_NAME}) +add_custom_command( + TARGET coverage_${PROJECT_NAME} + COMMAND lcov --capture --directory .. + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*' + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1 +) + +add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME}) + +# -------------------------- Code Coverage End ------------------------------ # + + +# ----------------------- Test Configuration End ---------------------------- # + diff --git a/heap/README.md b/heap/README.md new file mode 100644 index 0000000..8db40a1 --- /dev/null +++ b/heap/README.md @@ -0,0 +1,78 @@ +# Heap + +### Overview + +- Min-heap implementation, it can be used as Max-heap/priority queue as well. +- Just copy sc_heap.h and sc_heap.c to your project. + +#### Usage + + +```c + +#include "sc_heap.h" + +#include + + +int main(int argc, char *argv[]) +{ + struct data + { + int priority; + char *data; + }; + + struct data n[] = {{1, "first"}, + {4, "fourth"}, + {5, "fifth"}, + {3, "third"}, + {2, "second"}}; + + int64_t key; + void *data; + struct sc_heap heap; + + sc_heap_init(&heap, 0); + + // Min-heap usage + for (int i = 0; i < 5; i++) { + sc_heap_add(&heap, n[i].priority, n[i].data); + } + + while (sc_heap_pop(&heap, &key, &data)) { + printf("key = %ld, data = %s \n", key, (char *) data); + } + printf("---------------- \n"); + + /** + * Max-heap usage, negate when adding into heap + * and negate back after pop : + */ + + for (int i = 0; i < 5; i++) { + sc_heap_add(&heap, -(n[i].priority), n[i].data); + } + + while (sc_heap_pop(&heap, &key, &data)) { + printf("key = %ld, data = %s \n", -key, (char *) data); + } + + return 0; +} +``` + +####Internals +##### Memory + +- All items are on a single contiguous memory. +- Lazy allocation is possible. (No memory allocation until the first item) + +##### Performance + +- Heaps are not the fastest data structures as they require unpredictable + branches on add/extract operations. Intentionally, only 'void*' values are + allowed, so this not a 'generic' data structure. Placing larger values only + will worsen this implementation's performance. An item is 16 bytes in the + heap and operating on a contiguous memory gives us acceptable performance + for some use cases. diff --git a/heap/heap_example.c b/heap/heap_example.c new file mode 100644 index 0000000..6b0b2f3 --- /dev/null +++ b/heap/heap_example.c @@ -0,0 +1,50 @@ +#include "sc_heap.h" + +#include + + +int main(int argc, char *argv[]) +{ + struct data + { + int priority; + char *data; + }; + + struct data n[] = {{1, "first"}, + {4, "fourth"}, + {5, "fifth"}, + {3, "third"}, + {2, "second"}}; + + int64_t key; + void *data; + struct sc_heap heap; + + sc_heap_init(&heap, 0); + + // Min-heap usage + for (int i = 0; i < 5; i++) { + sc_heap_add(&heap, n[i].priority, n[i].data); + } + + while (sc_heap_pop(&heap, &key, &data)) { + printf("key = %ld, data = %s \n", (long int) key, (char *) data); + } + printf("---------------- \n"); + + /** + * Max-heap usage, negate when adding into heap + * and negate back after pop : + */ + + for (int i = 0; i < 5; i++) { + sc_heap_add(&heap, -(n[i].priority), n[i].data); + } + + while (sc_heap_pop(&heap, &key, &data)) { + printf("key = %ld, data = %s \n", (long int) -key, (char *) data); + } + + return 0; +} diff --git a/heap/heap_test.c b/heap/heap_test.c new file mode 100644 index 0000000..4b4a1f0 --- /dev/null +++ b/heap/heap_test.c @@ -0,0 +1,234 @@ +#include "sc_heap.h" + +#include +#include +#include +#include + +int example(void) +{ + struct data + { + int priority; + char *data; + }; + + struct data n[] = {{1, "first"}, + {4, "fourth"}, + {5, "fifth"}, + {3, "third"}, + {2, "second"}}; + int64_t key; + void *data; + struct sc_heap heap; + + sc_heap_init(&heap, 0); + + // Min-heap usage + for (int i = 0; i < 5; i++) { + sc_heap_add(&heap, n[i].priority, n[i].data); + } + + while (sc_heap_pop(&heap, &key, &data)) { + printf("key = %d, data = %s \n", (int) key, (char *) data); + } + printf("---------------- \n"); + + // Max-heap usage, negate when adding into heap and negate back after pop : + for (int i = 0; i < 5; i++) { + sc_heap_add(&heap, -(n[i].priority), n[i].data); + } + + while (sc_heap_pop(&heap, &key, &data)) { + printf("key = %d, data = %s \n", (int) -key, (char *) data); + } + + sc_heap_term(&heap); + + return 0; +} + +void test1(void) +{ + int64_t key; + void *data; + struct sc_heap heap; + + assert(sc_heap_init(&heap, SIZE_MAX / 2) == false); + assert(sc_heap_init(&heap, 3) == true); + + for (int i = 0; i < 1000; i++) { + assert(sc_heap_add(&heap, i, (void *) (uintptr_t) i) == true); + assert(sc_heap_pop(&heap, &key, &data) == true); + assert(key == (uintptr_t) data); + } + + int64_t arr[] = {1, 0, 4, 5, 7, 9, 8, 6, 3, 2}; + + for (int i = 0; i < 10; i++) { + assert(sc_heap_add(&heap, arr[i], (void *) (uintptr_t)(arr[i] * 2)) == + true); + } + + for (int i = 0; i < 10; i++) { + assert(sc_heap_pop(&heap, &key, &data) == true); + assert(key == i); + assert((uintptr_t) data == i * 2); + } + + sc_heap_term(&heap); +} + +void test2(void) +{ + static const int64_t arr[] = {1, 0, 4, 5, 7, 9, 8, 6, 3, 2}; + int64_t key; + void *data; + struct sc_heap heap; + + assert(sc_heap_init(&heap, 0) == true); + + for (int i = 0; i < 1000; i++) { + assert(sc_heap_add(&heap, i, (void *) (uintptr_t) i) == true); + assert(sc_heap_pop(&heap, &key, &data) == true); + assert(key == (uintptr_t) data); + assert(key == (uintptr_t) i); + } + + for (int i = 0; i < 10; i++) { + assert(sc_heap_add(&heap, -arr[i], (void *) (uintptr_t)(arr[i] * 2)) == + true); + } + + for (int i = 0; i < 10; i++) { + assert(sc_heap_pop(&heap, &key, &data) == true); + assert(-key == 9 - i); + assert((uintptr_t) data == (9 - i) * 2); + } + + sc_heap_term(&heap); +} + +void test3(void) +{ + int64_t key; + int arr[100]; + void *data; + struct sc_heap heap; + + assert(sc_heap_init(&heap, 2) == true); + assert(sc_heap_add(&heap, 9, (void *) (uintptr_t) 9) == true); + assert(sc_heap_peek(&heap, &key, &data) == true); + assert(key == 9); + assert(data == (void *) (uintptr_t) 9); + assert(sc_heap_pop(&heap, &key, &data) == true); + assert(key == 9); + assert(data == (void *) (uintptr_t) 9); + + for (int i = 0; i < 100; i++) { + arr[i] = i; + } + + for (int i = 0; i < 100; i++) { + int k = arr[i]; + arr[i] = arr[(i * 15) % 100]; + arr[(i * 15) % 100] = k; + } + + for (int i = 0; i < 100; i++) { + assert(sc_heap_add(&heap, i, (void *) (uintptr_t) i) == true); + } + + for (int i = 0; i < 100; i++) { + assert(sc_heap_pop(&heap, &key, &data) == true); + assert(key == i); + assert(data == (void *) (uintptr_t) i); + } + + assert(sc_heap_peek(&heap, &key, &data) == false); + assert(sc_heap_pop(&heap, &key, &data) == false); + + assert(sc_heap_size(&heap) == 0); + assert(sc_heap_add(&heap, 1, NULL) == true); + assert(sc_heap_size(&heap) == 1); + sc_heap_clear(&heap); + assert(sc_heap_size(&heap) == 0); + assert(sc_heap_add(&heap, 1, NULL) == true); + assert(sc_heap_size(&heap) == 1); + + sc_heap_term(&heap); +} + +#ifdef SC_HAVE_WRAP + +bool fail_malloc = false; +void *__real_malloc(size_t n); +void *__wrap_malloc(size_t n) +{ + if (fail_malloc) { + return NULL; + } + + return __real_malloc(n); +} + +bool fail_realloc = false; +void *__real_realloc(void *p, size_t size); +void *__wrap_realloc(void *p, size_t n) +{ + if (fail_realloc) { + return NULL; + } + + return __real_realloc(p, n); +} + +void fail_test(void) +{ + struct sc_heap heap; + + assert(sc_heap_init(&heap, 2) == true); + + size_t count = SC_SIZE_MAX / sizeof(struct sc_heap_data); + bool success = false; + + for (int i = 0; i < count + 5; i++) { + success = sc_heap_add(&heap, i, (void *) (uintptr_t) i); + } + + assert(!success); + + sc_heap_term(&heap); + + assert(sc_heap_init(&heap, 0) == true); + + fail_realloc = true; + assert(sc_heap_add(&heap, 1, NULL) == false); + fail_realloc = false; + + sc_heap_term(&heap); + + fail_malloc = true; + assert(sc_heap_init(&heap, 1) == false); + + fail_malloc = false; + assert(sc_heap_init(&heap, 1) == true); + + sc_heap_term(&heap); +} +#else +void fail_test() +{ +} +#endif + +int main(int argc, char *argv[]) +{ + example(); + fail_test(); + test1(); + test2(); + test3(); + + return 0; +} diff --git a/heap/sc_heap.c b/heap/sc_heap.c new file mode 100644 index 0000000..64b319e --- /dev/null +++ b/heap/sc_heap.c @@ -0,0 +1,148 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "sc_heap.h" + +#include + +#ifndef SC_SIZE_MAX + #define SC_SIZE_MAX SIZE_MAX +#endif + +#define SC_CAP_MAX SC_SIZE_MAX / sizeof(struct sc_heap_data) + +bool sc_heap_init(struct sc_heap *heap, size_t cap) +{ + void *elems; + const size_t alloc = cap * sizeof(struct sc_heap_data); + + *heap = (struct sc_heap){0}; + + if (cap == 0) { + return true; + } + + // Check overflow + if (cap > SC_CAP_MAX || (elems = sc_heap_malloc(alloc)) == NULL) { + return false; + } + + heap->elems = elems; + heap->cap = cap; + + return true; +} + +void sc_heap_term(struct sc_heap *heap) +{ + sc_heap_free(heap->elems); +} + +size_t sc_heap_size(struct sc_heap *heap) +{ + return heap->size; +} + +void sc_heap_clear(struct sc_heap *heap) +{ + heap->size = 0; +} + +bool sc_heap_add(struct sc_heap *heap, int64_t key, void *data) +{ + size_t i; + void *exp; + + if (++heap->size >= heap->cap) { + const size_t cap = heap->cap != 0 ? heap->cap * 2 : 4; + const size_t alloc = cap * 2 * sizeof(struct sc_heap_data); + // Check overflow + if (heap->cap >= SC_CAP_MAX / 2 || + (exp = sc_heap_realloc(heap->elems, alloc)) == NULL) { + return false; + } + + heap->elems = exp; + heap->cap = cap; + } + + i = heap->size; + while (i != 1 && key < heap->elems[i / 2].key) { + heap->elems[i] = heap->elems[i / 2]; + i /= 2; + } + + heap->elems[i].key = key; + heap->elems[i].data = data; + + return true; +} + +bool sc_heap_peek(struct sc_heap *heap, int64_t *key, void **data) +{ + if (heap->size == 0) { + return false; + } + + // Top element is always at heap->elems[1]. + *key = heap->elems[1].key; + *data = heap->elems[1].data; + + return true; +} + +bool sc_heap_pop(struct sc_heap *heap, int64_t *key, void **data) +{ + size_t i = 1, child = 2; + struct sc_heap_data last; + + if (heap->size == 0) { + return false; + } + + // Top element is always at heap->elems[1]. + *key = heap->elems[1].key; + *data = heap->elems[1].data; + + last = heap->elems[heap->size--]; + while (child <= heap->size) { + if (child < heap->size && + heap->elems[child].key > heap->elems[child + 1].key) { + child++; + }; + + if (last.key <= heap->elems[child].key) { + break; + } + + heap->elems[i] = heap->elems[child]; + + i = child; + child *= 2; + } + + heap->elems[i] = last; + + return true; +} diff --git a/heap/sc_heap.h b/heap/sc_heap.h new file mode 100644 index 0000000..9a9bc36 --- /dev/null +++ b/heap/sc_heap.h @@ -0,0 +1,108 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SC_HEAP_H +#define SC_HEAP_H + + +#include +#include +#include + + +struct sc_heap_data +{ + int64_t key; + void *data; +}; + +struct sc_heap +{ + size_t cap; + size_t size; + struct sc_heap_data *elems; +}; + +/** + * Plug your memory allocator. + */ +#define sc_heap_malloc malloc +#define sc_heap_realloc realloc +#define sc_heap_free free + +/** + * @param heap Heap + * @param cap Initial capacity, pass '0' for no initial memory allocation + * @return 'true' on success, 'false' on failure (memory allocation failure) + */ +bool sc_heap_init(struct sc_heap *heap, size_t cap); + +/** + * Destroys heap, frees memory + * @param heap Heap + */ +void sc_heap_term(struct sc_heap *heap); + +/** + * @param heap Heap + * @return Current element count + */ +size_t sc_heap_size(struct sc_heap *heap); + +/** + * Clears elements from the queue, does not free the allocated memory. + * @param heap heap pointer + */ +void sc_heap_clear(struct sc_heap *heap); + +/** + * @param heap Heap + * @param key Key + * @param data Data + * @return 'false' on out of memory. + */ +bool sc_heap_add(struct sc_heap *heap, int64_t key, void *data); + +/** + * Read top element without removing from the heap. + * + * @param heap Heap + * @param key [out] key + * @param data [out] data + * @return 'false' if there is no element in the heap. + */ +bool sc_heap_peek(struct sc_heap *heap, int64_t *key, void **data); + +/** + * Read top element and remove it from the heap. + * + * @param heap Heap + * @param key [out] key + * @param data [out] data + * @return 'false' if there is no element in the heap. + */ +bool sc_heap_pop(struct sc_heap *heap, int64_t *key, void **data); + + +#endif diff --git a/ini/CMakeLists.txt b/ini/CMakeLists.txt new file mode 100644 index 0000000..7cef60f --- /dev/null +++ b/ini/CMakeLists.txt @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_ini C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(sc_ini ini_example.c sc_ini.h sc_ini.c) + +if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror") +endif () + + +# --------------------------------------------------------------------------- # +# --------------------- Test Configuration Start ---------------------------- # +# --------------------------------------------------------------------------- # + +include(CTest) +include(CheckCCompilerFlag) + +enable_testing() + +add_executable(${PROJECT_NAME}_test ini_test.c sc_ini.c) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer) + + if (SANITIZER) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + endif () + endif () +endif () + +add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test) + +SET(MEMORYCHECK_COMMAND_OPTIONS + "-q --log-fd=2 --trace-children=yes --track-origins=yes \ + --leak-check=full --show-leak-kinds=all --show-reachable=yes \ + --error-exitcode=255") + +add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ + --overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS} + --verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ --verbose + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +# ----------------------- - Code Coverage Start ----------------------------- # + +if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage) + target_link_libraries(${PROJECT_NAME}_test gcov) + else() + message(FATAL_ERROR "Only GCC is supported for coverage") + endif() +endif () + +add_custom_target(coverage_${PROJECT_NAME}) +add_custom_command( + TARGET coverage_${PROJECT_NAME} + COMMAND lcov --capture --directory .. + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*' + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --ini coverage.info --rc lcov_branch_coverage=1 +) + +add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME}) + +# -------------------------- Code Coverage End ------------------------------ # + + +# ----------------------- Test Configuration End ---------------------------- # + diff --git a/ini/README.md b/ini/README.md new file mode 100644 index 0000000..4976dfe --- /dev/null +++ b/ini/README.md @@ -0,0 +1,116 @@ +# INI parser + +### Overview + +- Simple ini file parser +- Just copy sc_ini.h and sc_ini.c to your project. + +#### Memory + +- No heap memory allocation internally. + +#### Features + +##### Comment example +```ini +#Comment +;Another comment + +[Network] +#This is comment +hostname = github.com #Line comments start with space. Either " ;" or " #" +``` + + +##### No section +Possible to use without sections + +```ini +key1 = value1 ;Comment x +key2 = value2 ;Comment y +key3 = value3 #Comment z +``` +``` +- Item 1 : ""(Section), "key1"(Key), "value1"(Value) +- Item 2 : ""(Section), "key2"(Key), "value2"(Value) +- Item 3 : ""(Section), "key3"(Key), "value3"(Value) +``` + +##### Multi-value +Values without keys in the next line will be reported as if belongs to previous +key. Those values should be indented at least with a single space character. + +```ini +#Comment +;Another comment + +[Network] +#This is comment +hostname = github.com + github.io + github.org +``` +``` +- Item 1 : "Network"(Section), "hostname"(Key), "github.com"(Value) +- Item 2 : "Network"(Section), "hostname"(Key), "github.io"(Value) +- Item 3 : "Network"(Section), "hostname"(Key), "github.org"(Value) +``` + +#### Usage + + +```c + +#include "sc_ini.h" + +#include +#include +#include + +const char *example_ini = "# My configuration" + "[Network] \n" + "hostname = github.com \n" + "port = 443 \n" + "protocol = https \n" + "repo = any"; + +int callback(int line, void *arg, const char *section, const char *key, + const char *value) +{ + printf("Line : %d, Section : %s, Key : %s, Value : %s \n", line, section, + key, value); + + return 0; +} + +void file_example(void) +{ + int rc; + + FILE *fp = fopen("my_config.ini", "w+"); + fwrite(example_ini, 1, strlen(example_ini), fp); + fclose(fp); + + printf(" \n Parse file \n"); + + rc = sc_ini_parse_file(NULL, callback, "my_config.ini"); + assert(rc == 0); +} + +void string_example(void) +{ + int rc; + + printf(" \n Parse string \n"); + + rc = sc_ini_parse_string(NULL, callback, example_ini); + assert(rc == 0); +} + +int main(int argc, char *argv[]) +{ + string_example(); + file_example(); +} + +``` \ No newline at end of file diff --git a/ini/ini_example.c b/ini/ini_example.c new file mode 100644 index 0000000..f2264ac --- /dev/null +++ b/ini/ini_example.c @@ -0,0 +1,51 @@ +#include "sc_ini.h" + +#include +#include +#include + +const char *example_ini = "# My configuration" + "[Network] \n" + "hostname = github.com \n" + "port = 443 \n" + "protocol = https \n" + "repo = any"; + +int callback(int line, void *arg, const char *section, const char *key, + const char *value) +{ + printf("Line : %d, Section : %s, Key : %s, Value : %s \n", line, section, + key, value); + + return 0; +} + +void file_example(void) +{ + int rc; + + FILE *fp = fopen("my_config.ini", "w+"); + fwrite(example_ini, 1, strlen(example_ini), fp); + fclose(fp); + + printf(" \n Parse file \n"); + + rc = sc_ini_parse_file(NULL, callback, "my_config.ini"); + assert(rc == 0); +} + +void string_example(void) +{ + int rc; + + printf(" \n Parse string \n"); + + rc = sc_ini_parse_string(NULL, callback, example_ini); + assert(rc == 0); +} + +int main(int argc, char *argv[]) +{ + string_example(); + file_example(); +} diff --git a/ini/ini_test.c b/ini/ini_test.c new file mode 100644 index 0000000..39e4c42 --- /dev/null +++ b/ini/ini_test.c @@ -0,0 +1,474 @@ +#include "sc_ini.h" + +#include +#include +#include +#include + +int cb(int line, void *arg, const char *section, const char *key, + const char *value) +{ + printf("%s %s %s \n", section, key, value); + return 0; +} + +void test1() +{ + int rc; + static const char *ini = "#Sample \n" + "[section \n"; + + rc = sc_ini_parse_string(NULL, cb, ini); + assert(rc == 2); +} + +int cb2(int line, void *arg, const char *section, const char *key, + const char *value) +{ + assert(strcmp(section, "section") == 0); + assert(strcmp(key, "key") == 0); + assert(strcmp(value, "value") == 0); + return 0; +} + +void test2() +{ + int rc; + int count = 0; + + static const char *ini = "#Sample \n" + "[section] \n" + "key = value \n" + "key : value "; + + rc = sc_ini_parse_string(&count, cb2, ini); + assert(rc == 0); +} + +int cb3(int line, void *arg, const char *section, const char *key, + const char *value) +{ + assert(strcmp(section, "") == 0); + assert(strcmp(key, "key") == 0); + assert(strcmp(value, "value") == 0); + + *(int *) arg = *(int *) arg + 1; + + return 0; +} + +void test3() +{ + int rc; + int count = 0; + + static const char *ini = " ;Sample \n" + "key = value \n" + "key : value "; + + rc = sc_ini_parse_string(&count, cb3, ini); + assert(rc == 0); + assert(count == 2); +} + +int cb4(int line, void *arg, const char *section, const char *key, + const char *value) +{ + char tmp[16]; + int count = *(int *) arg; + + assert(strcmp(section, "section") == 0); + assert(strcmp(key, "key") == 0); + + snprintf(tmp, 16, "value%d", count); + assert(strcmp(value, tmp) == 0); + + *(int *) arg = count + 1; + + return 0; +} + +void test4() +{ + int rc; + int count = 0; + + static const char *ini = " ;Sample \n" + " [section] \n" + "key = value0 \n" + " value1 \n" + " value2 "; + + rc = sc_ini_parse_string(&count, cb4, ini); + assert(rc == 0); + assert(count == 3); +} + +int cb5(int line, void *arg, const char *section, const char *key, + const char *value) +{ + char tmp[16]; + int count = *(int *) arg; + + assert(strcmp(section, "section") == 0); + assert(strcmp(key, "key") == 0); + + snprintf(tmp, 16, "value%d", count); + assert(strcmp(value, tmp) == 0); + + *(int *) arg = count + 1; + + return 0; +} + +void test5() +{ + int rc; + int count = 0; + + static const char *ini = " ;Sample \n" + " [section] \n" + "key = value0 \n" + "value1 \n" + "value2 "; + + rc = sc_ini_parse_string(&count, cb4, ini); + assert(rc == 4); + assert(count == 1); + + + count = 0; + static const char *ini2 = " ;Sample \n" + " [section] \n" + "key = value0 \n" + " value1 \n" + "value2 "; + rc = sc_ini_parse_string(&count, cb4, ini2); + assert(rc == 5); + assert(count == 2); +} + +int cb6(int line, void *arg, const char *section, const char *key, + const char *value) +{ + return -1; +} + +void test6() +{ + int rc; + int count = 0; + + static const char *ini = " ;Sample \n" + " [section] \n" + "key = value0 \n" + " value1 \n" + " value2 "; + + rc = sc_ini_parse_string(&count, cb6, ini); + assert(rc == 3); + assert(count == 0); +} + +int cb7(int line, void *arg, const char *section, const char *key, + const char *value) +{ + char tmp[16]; + int count = *(int *) arg; + if (count == 1) { + return -1; + } + + assert(strcmp(section, "section") == 0); + assert(strcmp(key, "key") == 0); + + snprintf(tmp, 16, "value%d", count); + assert(strcmp(value, tmp) == 0); + + *(int *) arg = count + 1; + + return 0; +} + +void test7() +{ + int rc; + int count = 0; + + static const char *ini = " ;Sample \n" + " [section] \n" + "key = value0 \n" + " value1 \n" + " value2 "; + + rc = sc_ini_parse_string(&count, cb7, ini); + assert(rc == 4); + assert(count == 1); +} + +int cb8(int line, void *arg, const char *section, const char *key, + const char *value) +{ + char tmp[16]; + int count = *(int *) arg; + + assert(strcmp(section, "section") == 0); + assert(strcmp(key, "key") == 0); + + snprintf(tmp, 16, "value%d", count); + assert(strcmp(value, tmp) == 0); + + *(int *) arg = count + 1; + + return 0; +} + +void test8() +{ + int rc; + int count = 0; + FILE *fp; + + static const char *ini = " ;Sample \n" + " [section] \n" + "key = value0 #;comment\n" + " value1 \n" + " value2 "; + + fp = fopen("config.ini", "w+"); + fwrite(ini, 1, strlen(ini), fp); + fclose(fp); + + rc = sc_ini_parse_file(&count, cb8, "config.ini"); + assert(rc == 0); + assert(count == 3); + remove("config.ini"); + + count = 0; + static const char *ini2 = " ;Sample \n" + " [section] \n" + "key = value0 ;comment\n" + " value1 #comment\n" + " value2 ;#comment\n"; + + fp = fopen("config.ini", "w+"); + fwrite(ini2, 1, strlen(ini2), fp); + fclose(fp); + + rc = sc_ini_parse_file(&count, cb8, "config.ini"); + assert(rc == 0); + assert(count == 3); + remove("config.ini"); + + + count = 0; + unsigned char bom[3] = {0xEF, 0xBB, 0xBF}; + static const char *ini3 = " ;Sample \n" + " [section] \n" + "key = value0 \n" + " value1 \n" + " value2 \n"; + + fp = fopen("config.ini", "w+"); + fwrite(bom, 1, sizeof(bom), fp); + fwrite(ini3, 1, strlen(ini3), fp); + fclose(fp); + + rc = sc_ini_parse_file(&count, cb8, "config.ini"); + assert(rc == 0); + assert(count == 3); + remove("config.ini"); + + rc = sc_ini_parse_file(&count, cb8, "config.ini"); + assert(rc == -1); +} + +int cb9(int line, void *arg, const char *section, const char *key, + const char *value) +{ + assert(strcmp(section, "section[test") == 0); + assert(strcmp(key, "key;;") == 0); + assert(strcmp(value, "value;;") == 0); + return 0; +} + +void test9() +{ + int rc; + int count = 0; + + static const char *ini = "#Sample \n" + "[section[test]] \n" + "key;; = value;; ;comment \n" + "key;; : value;; #comment "; + + rc = sc_ini_parse_string(&count, cb9, ini); + assert(rc == 0); +} + +void test10() +{ + int rc; + int count = 0; + + static const char *ini = "#Sample \n" + "section[test]] \n" + "key = value;; ;comment \n" + "key : value# #comment "; + + rc = sc_ini_parse_string(&count, cb9, ini); + assert(rc == 2); +} + +int cb11(int line, void *arg, const char *section, const char *key, + const char *value) +{ + assert(strcmp(section, "section") == 0); + assert(strcmp(key, "key#") == 0); + assert(strcmp(value, "") == 0); + return 0; +} + +void test11() +{ + int rc; + int count = 0; + + static const char *ini = "#Sample \n" + "[section] \n" + "#comment1 \n" + ";comment2 \n" + " #comment3 \n" + " ;comment44 \n" + "key# = ;comment \n" + "key# : #comment "; + + rc = sc_ini_parse_string(&count, cb11, ini); + assert(rc == 0); +} + +int cb12(int line, void *arg, const char *section, const char *key, + const char *value) +{ + assert(strcmp(section, "") == 0); + assert(strcmp(key, "") == 0); + assert(strcmp(value, "") == 0); + return 0; +} + +void test12() +{ + int rc; + int count = 0; + + static const char *ini = "#Sample \n" + "#[section] \n" + "#comment1 \n" + ";comment2 \n" + " #comment3 \n" + " ;comment44 \n" + " = ;comment \n" + " : #comment "; + + rc = sc_ini_parse_string(&count, cb12, ini); + assert(rc == 0); +} + +int cb13(int line, void *arg, const char *section, const char *key, + const char *value) +{ + char tmp[16]; + int count = *(int *) arg; + + assert(strcmp(section, "section") == 0); + assert(strcmp(key, "key") == 0); + + snprintf(tmp, 16, "value%d", count); + assert(strcmp(value, tmp) == 0); + + *(int *) arg = count + 1; + + return 0; +} + +void test13() +{ + int rc; + int count = 0; + + static const char *ini = " ;Sample \n" + " [section] \n" + "key = value0 \n" + " \n" + " value1 "; + + rc = sc_ini_parse_string(&count, cb13, ini); + assert(rc == 0); + assert(count == 2); +} + +const char *example_ini = "# My configuration" + "[Network] \n" + "hostname = github.com \n" + "port = 443 \n" + "protocol = https \n" + "repo = any"; + +int callback(int line, void *arg, const char *section, const char *key, + const char *value) +{ + printf("Line : %d, Section : %s, Key : %s, Value : %s \n", line, section, + key, value); + + return 0; +} + +void file_example(void) +{ + int rc; + + FILE *fp = fopen("my_config.ini", "w+"); + fwrite(example_ini, 1, strlen(example_ini), fp); + fclose(fp); + + printf(" \n Parse file \n"); + + rc = sc_ini_parse_file(NULL, callback, "my_config.ini"); + assert(rc == 0); +} + +void string_example(void) +{ + int rc; + + printf(" \n Parse string \n"); + + rc = sc_ini_parse_string(NULL, callback, example_ini); + assert(rc == 0); +} + +void example(void) +{ + string_example(); + file_example(); +} + +int main(int argc, char *argv[]) +{ + example(); + test1(); + test2(); + test3(); + test4(); + test5(); + test6(); + test7(); + test8(); + test9(); + test10(); + test11(); + test12(); + test13(); + + return 0; +} diff --git a/ini/sc_ini.c b/ini/sc_ini.c new file mode 100644 index 0000000..1e55cda --- /dev/null +++ b/ini/sc_ini.c @@ -0,0 +1,184 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "sc_ini.h" + +#include +#include +#include + +static char *trim_space(char *str) +{ + char *end; + + while (isspace(*str)) { + str++; + } + + end = str + strlen(str) - 1; + while (end > str && isspace(*end)) { + end--; + } + + end[1] = '\0'; + + return str; +} + +static char *trim_comment(char *str) +{ + char *s = str; + + if (*s == '\0' || *s == ';' || *s == '#') { + *s = '\0'; + return str; + } + + while (*s && (s = strchr(s, ' ')) != NULL) { + s++; + if (*s == ';' || *s == '#') { + *s = '\0'; + break; + } + } + + return str; +} + +static char *trim_bom(char *str) +{ + if (str != NULL && strlen(str)) { + if ((uint8_t) str[0] == 0xEF && (uint8_t) str[1] == 0xBB && + (uint8_t) str[2] == 0xBF) { + str += 3; + } + } + + return str; +} + +static int sc_ini_parse(void *arg, sc_ini_on_item on_item, void *arg1, + char *(*next_line)(void *arg, char *buf, size_t size)) +{ + int rc = 0, line = 0; + char buf[SC_INI_MAX_LINE_LEN]; + char section[256] = {0}, prev_key[256] = {0}; + char *head, *end; + + while ((head = next_line(arg1, buf, sizeof(buf) - 1)) != NULL) { + if (++line == 1) { + // Skip byte order mark + head = trim_bom(head); + } + + head = trim_space(trim_comment(head)); + if (*head == '\0') { + continue; + } + + if (head > buf && *prev_key) { + // Multi-line case. This line is another value to previous key. + rc = on_item(line, arg, section, prev_key, head); + } else if (*head == '[') { + if ((end = strchr(head, ']')) == NULL) { + return line; + } + + *prev_key = '\0'; + *end = '\0'; + strncpy(section, head + 1, sizeof(section) - 1); + } else { + if ((end = strpbrk(head, "=:")) == NULL) { + return line; + } + + *end = '\0'; + trim_space(head); + strncpy(prev_key, head, sizeof(prev_key) - 1); + rc = on_item(line, arg, section, head, trim_space(end + 1)); + } + + if (rc != 0) { + return line; + } + } + + return 0; +} + +static char *file_next_line(void *p, char *buf, size_t size) +{ + return fgets(buf, size, (FILE *) p); +} + +static char *string_next_line(void *p, char *buf, size_t size) +{ + size_t len; + char *t; + char *str = (*(char **) p); + + if (str == NULL || *str == '\0') { + return NULL; + } + + t = strchr(str, '\n'); + if (t == NULL) { + t = str + strlen(str); + } + + len = (t - str) < size ? (t - str) : size; + memcpy(buf, str, len); + buf[len] = '\0'; + + *(char **) p = *t == '\0' ? '\0' : t + 1; + + return buf; +} + +int sc_ini_parse_file(void *arg, sc_ini_on_item on_item, const char *filename) +{ + int rc; + FILE *file; + + file = fopen(filename, "rb"); + if (!file) { + return -1; + } + + rc = sc_ini_parse(arg, on_item, file, file_next_line); + if (rc == 0) { + rc = ferror(file) != 0 ? -1 : 0; + } + + fclose(file); + + return rc; +} + +int sc_ini_parse_string(void *arg, sc_ini_on_item on_item, const char *str) +{ + char *ptr = (char *) str; + + return sc_ini_parse(arg, on_item, &ptr, string_next_line); +} diff --git a/ini/sc_ini.h b/ini/sc_ini.h new file mode 100644 index 0000000..14eda48 --- /dev/null +++ b/ini/sc_ini.h @@ -0,0 +1,71 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SC_INI_H +#define SC_INI_H + +#include +#include +#include + +/** + * Adjust max line length here. If a line is larger than this, it will be + * truncated silently. + */ +#define SC_INI_MAX_LINE_LEN 1024 + +/** + * @param line current line number + * @param arg user arg. + * @param section section. + * @param key key. + * @param value value. + * @return Return '0' on success, any other value will make parser + * stop and return error. + */ +typedef int (*sc_ini_on_item)(int line, void *arg, const char *section, + const char *key, const char *value); + +/** + * @param arg User data to be passed to 'on_item' callback. + * @param on_item Callback. + * @param filename File name. + * @return - '0' on success, + * - '-1' on file IO error. + * - positive line number on parsing error + * - 'on_item' return value if it returns other than '0' + */ +int sc_ini_parse_file(void *arg, sc_ini_on_item on_item, const char *filename); + +/** + * @param arg User data to be passed to 'on_item' callback. + * @param on_item Callback + * @param str String to parse + * @return - '0' on success, + * - positive line number on parsing error + * - 'on_item' return value if it returns other than '0' + */ +int sc_ini_parse_string(void *arg, sc_ini_on_item on_item, const char *str); + +#endif diff --git a/linked-list/CMakeLists.txt b/linked-list/CMakeLists.txt new file mode 100644 index 0000000..17113a7 --- /dev/null +++ b/linked-list/CMakeLists.txt @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_list C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(sc_list list_example.c sc_list.h sc_list.c) + +if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror") +endif () + + +# --------------------------------------------------------------------------- # +# --------------------- Test Configuration Start ---------------------------- # +# --------------------------------------------------------------------------- # + +include(CTest) +include(CheckCCompilerFlag) + +enable_testing() + +add_executable(${PROJECT_NAME}_test list_test.c sc_list.c) + +if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer) + + if (SANITIZER) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + endif () +endif () + +add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test) + +SET(MEMORYCHECK_COMMAND_OPTIONS + "-q --log-fd=2 --trace-children=yes --track-origins=yes \ + --leak-check=full --show-leak-kinds=all --show-reachable=yes \ + --error-exitcode=255") + +add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ + --overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS} + --verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ --verbose + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +# ----------------------- - Code Coverage Start ----------------------------- # + +if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage) + target_link_libraries(${PROJECT_NAME}_test gcov) + else() + message(FATAL_ERROR "Only GCC is supported for coverage") + endif() +endif () + +add_custom_target(coverage_${PROJECT_NAME}) +add_custom_command( + TARGET coverage_${PROJECT_NAME} + COMMAND lcov --capture --directory .. + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*' + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1 +) + +add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME}) + +# -------------------------- Code Coverage End ------------------------------ # + + +# ----------------------- Test Configuration End ---------------------------- # + diff --git a/linked-list/README.md b/linked-list/README.md new file mode 100644 index 0000000..5534f5f --- /dev/null +++ b/linked-list/README.md @@ -0,0 +1,66 @@ +# Linked List + +### Overview + +- Intrusive doubly linked-list implementation. +- Basically, same as adding next and prev pointers to your structs. +- Add/remove from head/tail is possible so it can be used as list, stack, + queue, dequeue etc. +- Just copy sc_list.h and sc_list.c to your project. + + +#### Memory + +- No heap memory allocation. + +#### Performance + +- Good fit if you already have structs allocated in memory and willing to put + them into a list without making extra allocations. + +#### Usage + + +```c + +#include "sc_list.h" + +#include +#include + + +int main(int argc, char *argv[]) +{ + struct user + { + char *name; + struct sc_list next; + }; + + struct user users[] = {{"first"}, + {"second"}, + {"third"}, + {"fourth"}, + {"fifth"}}; + + struct sc_list list; + + sc_list_init(&list); + + for (int i = 0; i < 5; i++) { + sc_list_add_tail(&list, &users[i].next); + } + + struct sc_list *it; + struct user *user; + + sc_list_foreach (&list, it) { + user = sc_list_entry(it, struct user, next); + printf("%s \n", user->name); + } + + return 0; +} + + +``` \ No newline at end of file diff --git a/linked-list/list_example.c b/linked-list/list_example.c new file mode 100644 index 0000000..f19e3e5 --- /dev/null +++ b/linked-list/list_example.c @@ -0,0 +1,38 @@ +#include "sc_list.h" + +#include +#include + + +int main(int argc, char *argv[]) +{ + struct user + { + char *name; + struct sc_list next; + }; + + struct user users[] = {{"first"}, + {"second"}, + {"third"}, + {"fourth"}, + {"fifth"}}; + + struct sc_list list; + + sc_list_init(&list); + + for (int i = 0; i < 5; i++) { + sc_list_add_tail(&list, &users[i].next); + } + + struct sc_list *it; + struct user *user; + + sc_list_foreach (&list, it) { + user = sc_list_entry(it, struct user, next); + printf("%s \n", user->name); + } + + return 0; +} diff --git a/linked-list/list_test.c b/linked-list/list_test.c new file mode 100644 index 0000000..3aea376 --- /dev/null +++ b/linked-list/list_test.c @@ -0,0 +1,149 @@ +#include "sc_list.h" + +#include + +struct elem +{ + int id; + struct sc_list list; +}; + +static void test1(void) +{ + int k, i; + struct elem a, b, c, d, e, *elem; + struct sc_list list, *item, *tmp; + + a.id = 1; + b.id = 2; + c.id = 3; + d.id = 4; + e.id = 5; + + sc_list_init(&list); + + assert(sc_list_count(&list) == 0); + tmp = sc_list_pop_head(&list); + assert(tmp == NULL); + + tmp = sc_list_pop_tail(&list); + assert(tmp == NULL); + + sc_list_add_tail(&list, &a.list); + sc_list_add_tail(&list, &b.list); + assert(sc_list_count(&list) == 2); + tmp = sc_list_pop_tail(&list); + elem = sc_list_entry(tmp, struct elem, list); + assert(elem->id == b.id); + + sc_list_add_after(&list, &a.list, &b.list); + assert(a.list.next == &b.list); + + sc_list_add_head(&list, &c.list); + tmp = sc_list_pop_head(&list); + elem = sc_list_entry(tmp, struct elem, list); + assert(elem->id == c.id); + + sc_list_foreach (&list, item) { + elem = sc_list_entry(item, struct elem, list); + } + + sc_list_add_before(&list, &b.list, &e.list); + assert(a.list.next == &e.list); + + sc_list_foreach (&list, item) { + elem = sc_list_entry(item, struct elem, list); + } + + sc_list_add_tail(&list, &c.list); + sc_list_add_tail(&list, &d.list); + + sc_list_foreach (&list, item) { + elem = sc_list_entry(item, struct elem, list); + } + + sc_list_foreach (&list, item) { + elem = sc_list_entry(item, struct elem, list); + } + + sc_list_del(&list, &e.list); + + sc_list_foreach (&list, item) { + elem = sc_list_entry(item, struct elem, list); + } + + i = 1; + sc_list_foreach (&list, item) { + elem = sc_list_entry(item, struct elem, list); + assert(elem->id == i); + i++; + } + + i = 4; + sc_list_foreach_r(&list, item) + { + elem = sc_list_entry(item, struct elem, list); + assert(elem->id == i); + i--; + } + + sc_list_clear(&list); + + assert(sc_list_is_empty(&list) == true); + assert(sc_list_count(&list) == 0); + assert(sc_list_head(&list) == NULL); + assert(sc_list_tail(&list) == NULL); + + sc_list_add_tail(&list, &a.list); + sc_list_add_tail(&list, &b.list); + sc_list_add_tail(&list, &c.list); + sc_list_add_tail(&list, &d.list); + sc_list_add_tail(&list, &e.list); + + assert(sc_list_head(&list) != NULL); + assert(sc_list_tail(&list) != NULL); + assert(sc_list_is_empty(&list) == false); + + k = 0; + sc_list_foreach_safe (&list, tmp, item) { + if (k == 0) { + sc_list_del(&list, &e.list); + } + + elem = sc_list_entry(item, struct elem, list); + + k++; + assert(elem->id == k); + } + + sc_list_clear(&list); + + + sc_list_add_tail(&list, &a.list); + sc_list_add_tail(&list, &b.list); + sc_list_add_tail(&list, &c.list); + sc_list_add_tail(&list, &d.list); + sc_list_add_tail(&list, &e.list); + + k = 6; + sc_list_foreach_safe_r(&list, tmp, item) + { + if (k == 3) { + sc_list_del(&list, &b.list); + k--; + continue; + } + + elem = sc_list_entry(item, struct elem, list); + + k--; + assert(elem->id == k); + } +} + +int main(int argc, char *argv[]) +{ + test1(); + + return 0; +} diff --git a/linked-list/sc_list.c b/linked-list/sc_list.c new file mode 100644 index 0000000..0816a97 --- /dev/null +++ b/linked-list/sc_list.c @@ -0,0 +1,156 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "sc_list.h" + +void sc_list_init(struct sc_list *list) +{ + list->next = list; + list->prev = list; +} + +void sc_list_clear(struct sc_list *list) +{ + list->next = list; + list->prev = list; +} + +bool sc_list_is_empty(struct sc_list *list) +{ + return list->next == list; +} + +size_t sc_list_count(struct sc_list *list) +{ + size_t count = 0; + struct sc_list *elem; + + sc_list_foreach (list, elem) { + count++; + } + + return count; +} + +struct sc_list *sc_list_head(struct sc_list *list) +{ + struct sc_list *elem; + + elem = list->next; + if (elem == list) { + return NULL; + } + + return elem; +} + +struct sc_list *sc_list_tail(struct sc_list *list) +{ + struct sc_list *elem; + + elem = list->prev; + if (elem == list) { + return NULL; + } + + return elem; +} + +void sc_list_add_tail(struct sc_list *list, struct sc_list *elem) +{ + struct sc_list *prev; + + prev = list->prev; + list->prev = elem; + elem->next = list; + elem->prev = prev; + prev->next = elem; +} + +struct sc_list *sc_list_pop_tail(struct sc_list *list) +{ + struct sc_list *tail; + + if (sc_list_is_empty(list)) { + return NULL; + } + + tail = list->prev; + sc_list_del(list, list->prev); + + return tail; +} + +void sc_list_add_head(struct sc_list *list, struct sc_list *elem) +{ + sc_list_add_before(list, list->next, elem); +} + +struct sc_list *sc_list_pop_head(struct sc_list *list) +{ + struct sc_list *head; + + if (sc_list_is_empty(list)) { + return NULL; + } + + head = list->next; + sc_list_del(list, list->next); + + return head; +} + +void sc_list_add_after(struct sc_list *list, struct sc_list *prev, + struct sc_list *elem) +{ + (void) list; + struct sc_list *next; + + next = prev->next; + prev->next = elem; + elem->next = next; + elem->prev = prev; + next->prev = elem; +} + +void sc_list_add_before(struct sc_list *list, struct sc_list *next, + struct sc_list *elem) +{ + (void) list; + struct sc_list *prev; + + prev = next->prev; + next->prev = elem; + elem->next = next; + elem->prev = prev; + prev->next = elem; +} + +void sc_list_del(struct sc_list *list, struct sc_list *elem) +{ + (void) (list); + + elem->prev->next = elem->next; + elem->next->prev = elem->prev; +} diff --git a/linked-list/sc_list.h b/linked-list/sc_list.h new file mode 100644 index 0000000..ffbea9d --- /dev/null +++ b/linked-list/sc_list.h @@ -0,0 +1,226 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SC_LIST_H +#define SC_LIST_H + +#include +#include + + +struct sc_list +{ + struct sc_list *next; + struct sc_list *prev; +}; + +#define sc_list_entry(ptr, type, elem) \ + ((type *) ((char *) (ptr) -offsetof(type, elem))) + +/** + * @param list list pointer + */ +void sc_list_init(struct sc_list *list); + +/** + * @param list list pointer + */ +void sc_list_clear(struct sc_list *list); + +/** + * @param list list pointer + * @return 'true' if empty, false otherwise + */ +bool sc_list_is_empty(struct sc_list *list); + +/** + * @param list list pointer + * @return element count in the list, beware this is a log(n) operation. + */ +size_t sc_list_count(struct sc_list *list); + +/** + * @param list list pointer + * @return returns head. If list is empty, returns NULL. + */ +struct sc_list *sc_list_head(struct sc_list *list); + +/** + * @param list list pointer + * @return returns tail. If list is empty, returns NULL. + */ +struct sc_list *sc_list_tail(struct sc_list *list); + +/** + * @param list list pointer + * @param elem elem to add to head + */ +void sc_list_add_head(struct sc_list *list, struct sc_list *elem); + +/** + * before : [head]item1 -> item2 -> item3 + * after : [head]item2 -> item3 + * + * @param list list pointer + * @return head element, if list is empty, returns NULL. + */ +struct sc_list *sc_list_pop_head(struct sc_list *list); + + +/** + * before : [head]item1 -> [tail]item2 + * after : [head]item1 -> item2 -> [tail]'elem' + * + * @param list + * @param elem + */ +void sc_list_add_tail(struct sc_list *list, struct sc_list *elem); + +/** + * before : [head]item1 -> item2 -> item3 + * after : [head]item2 -> item2 + * + * @param list list pointer + * @return head element, if list is empty, returns NULL. + */ +struct sc_list *sc_list_pop_tail(struct sc_list *list); + +/** + * before : item1 -> 'prev' -> item2 + * after : item1 -> 'prev'-> 'elem' -> item2 + * + * @param list list pointer + * @param prev previous element of the 'elem' + * @param elem elem to be added after 'prev' + */ +void sc_list_add_after(struct sc_list *list, struct sc_list *prev, + struct sc_list *elem); + +/** + * before : item1 -> 'next' -> item2 + * after : item1 -> 'elem'-> 'next' -> item2 + * + * @param list list pointer + * @param next next element of the 'elem' + * @param elem elem to be added before 'next' + */ +void sc_list_add_before(struct sc_list *list, struct sc_list *next, + struct sc_list *elem); + +/** + * before : item1 -> 'elem' -> item2 + * after : item1 -> item2 + * + * @param list list pointer + * @param elem elem to be deleted + */ +void sc_list_del(struct sc_list *list, struct sc_list *elem); + + +/** + * + * struct container { + * struct sc_list others; + * }; + * + * struct container *container; // User object + * struct sc_list *list; // List pointer, should already be initialized. + * struct sc_list *it; // Iterator + * + * sc_list_foreach(list, it) { + * container = sc_list_entry(it, struct container, others); + * } + * + */ +#define sc_list_foreach(list, elem) \ + for ((elem) = (list)->next; (elem) != (list); (elem) = (elem)->next) + +/** + * It is safe to delete items from the list while using + * this iterator. + * + * struct container { + * struct sc_list others; + * }; + * + * struct container *container; // User object + * struct sc_list *list; // List pointer, should already be initialized. + * struct sc_list *it; // Iterator + * + * sc_list_foreach(list, it) { + * container = sc_list_entry(it, struct container, others); + * sc_list_del(list, &container->others); + * } + * + */ +#define sc_list_foreach_safe(list, n, elem) \ + for ((elem) = (list)->next, (n) = (elem)->next; (elem) != (list); \ + (elem) = (n), (n) = (elem)->next) + +/** + * Reverse iterator + * + * struct container { + * struct sc_list others; + * }; + * + * struct container *container; // User object + * struct sc_list *list; // List pointer, should already be initialized. + * struct sc_list *it; // Iterator + * + * sc_list_foreach(list, it) { + * container = sc_list_entry(it, struct container, others); + * } + * + */ + +#define sc_list_foreach_r(list, elem) \ + for ((elem) = (list)->prev; (elem) != (list); (elem) = (elem)->prev) + + +/** + * Reverse iterator. + * + * It is safe to delete items from the list while using + * this iterator. + * + * struct container { + * struct sc_list others; + * }; + * + * struct container *container; // User object + * struct sc_list *list; // List pointer, should already be initialized. + * struct sc_list *it; // Iterator + * + * sc_list_foreach(list, it) { + * container = sc_list_entry(it, struct container, others); + * sc_list_del(list, &container->others); + * } + * + */ +#define sc_list_foreach_safe_r(list, n, elem) \ + for ((elem) = (list)->prev, (n) = (elem)->prev; (elem) != (list); \ + (elem) = (n), (n) = (elem)->prev) + +#endif diff --git a/logger/CMakeLists.txt b/logger/CMakeLists.txt new file mode 100644 index 0000000..496f5b6 --- /dev/null +++ b/logger/CMakeLists.txt @@ -0,0 +1,94 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_log C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(sc_log log_example.c sc_log.h sc_log.c) + +if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror -D_GNU_SOURCE -pthread") +endif () + + +# --------------------------------------------------------------------------- # +# --------------------- Test Configuration Start ---------------------------- # +# --------------------------------------------------------------------------- # + +include(CTest) +include(CheckCCompilerFlag) + +enable_testing() + +add_executable(${PROJECT_NAME}_test log_test.c sc_log.c) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin) + target_link_options(${PROJECT_NAME}_test PRIVATE + -Wl,--wrap=fprintf,--wrap=vfprintf,--wrap=fopen,--wrap=localtime,--wrap=pthread_mutexattr_init) + endif () +endif () + +if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer) + + if (SANITIZER) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + endif () +endif () + +add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test) + +SET(MEMORYCHECK_COMMAND_OPTIONS + "-q --log-fd=2 --trace-children=yes --track-origins=yes \ + --leak-check=full --show-leak-kinds=all --show-reachable=yes \ + --error-exitcode=255") + +add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ + --overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS} + --verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ --verbose + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +# ----------------------- - Code Coverage Start ----------------------------- # + +if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage) + target_link_libraries(${PROJECT_NAME}_test gcov) + else() + message(FATAL_ERROR "Only GCC is supported for coverage") + endif() +endif () + +add_custom_target(coverage_${PROJECT_NAME}) +add_custom_command( + TARGET coverage_${PROJECT_NAME} + COMMAND lcov --capture --directory .. + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*' + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1 +) + +add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME}) + +# -------------------------- Code Coverage End ------------------------------ # + + +# ----------------------- Test Configuration End ---------------------------- # + diff --git a/logger/README.md b/logger/README.md new file mode 100644 index 0000000..3c9d7c0 --- /dev/null +++ b/logger/README.md @@ -0,0 +1,59 @@ +# Logger + +### Overview + +- Log destination can be stdout, file and user callback. +- Possible to get logs to all destinations at the same time. +- Log files are rotated. +- Thread-safe. +- Just copy sc_log.h and sc_log.c to your project. + +#### Usage + + +```c +#include "sc_log.h" + +int log_callback(void *arg, enum sc_log_level level, + const char *fmt, va_list va) +{ + const char *my_app = arg; + const char *level_str = sc_log_levels[level].str; + + fprintf(stdout, " %s received log : level = [%s] ", my_app, level_str); + vfprintf(stdout, fmt, va); + + return 0; +} + +int main(int argc, char *argv[]) +{ + const char* my_app_name = "my app"; + + //sc_log_init(); is not thread-safe, it must be called by a single thread. + sc_log_init(); + + //Default log-level is 'info' and default destination is 'stdout' + sc_log_info("Hello world!"); + + //Enable logging to file. + sc_log_set_file("log.0.txt", "log-latest.txt"); + + //stdout and file will get the log line + sc_log_info("to stdout and file!"); + + //Enable callback + sc_log_set_callback(log_callback, (void*) my_app_name); + + //stdout, file and callback will get the log line + sc_log_info("to all!"); + sc_log_info("to all!"); + + //sc_log_term(); is not thread-safe, it must be called by a single thread. + sc_log_term(); + + return 0; +} + +``` + diff --git a/logger/log_example.c b/logger/log_example.c new file mode 100644 index 0000000..0dc9858 --- /dev/null +++ b/logger/log_example.c @@ -0,0 +1,42 @@ +#include "sc_log.h" + +int log_callback(void *arg, enum sc_log_level level, + const char *fmt, va_list va) +{ + const char *my_app = arg; + const char *level_str = sc_log_levels[level].str; + + fprintf(stdout, " %s received log : level = [%s] ", my_app, level_str); + vfprintf(stdout, fmt, va); + + return 0; +} + +int main(int argc, char *argv[]) +{ + const char* my_app_name = "my app"; + + //sc_log_init(); is not thread-safe, it must be called by a single thread. + sc_log_init(); + + //Default log-level is 'info' and default destination is 'stdout' + sc_log_info("Hello world!"); + + //Enable logging to file. + sc_log_set_file("log.0.txt", "log-latest.txt"); + + //stdout and file will get the log line + sc_log_info("to stdout and file!"); + + //Enable callback + sc_log_set_callback(log_callback, (void*) my_app_name); + + //stdout, file and callback will get the log line + sc_log_info("to all!"); + sc_log_info("to all!"); + + //sc_log_term(); is not thread-safe, it must be called by a single thread. + sc_log_term(); + + return 0; +} diff --git a/logger/log_test.c b/logger/log_test.c new file mode 100644 index 0000000..1069064 --- /dev/null +++ b/logger/log_test.c @@ -0,0 +1,305 @@ +#define SC_LOG_PRINT_FILE_NAME +#include "sc_log.h" + +#include +#include +#include + + +int callback(void *arg, enum sc_log_level level, const char *fmt, va_list va) +{ + *(int *) arg = *(int *) arg + 1; + return 0; +} + +void test1(void) +{ + int count = 0; + + sc_log_init(); + sc_log_set_callback(callback, &count); + assert(sc_log_set_level("errrorr") == -1); + sc_log_debug("test"); + assert(count == 0); + + sc_log_set_level("DEBUG"); + sc_log_debug("test"); + assert(count == 1); + sc_log_info("test"); + assert(count == 2); + sc_log_warn("test"); + assert(count == 3); + sc_log_error("test"); + assert(count == 4); + + count = 0; + sc_log_set_level("INFO"); + sc_log_debug("test"); + assert(count == 0); + sc_log_info("test"); + assert(count == 1); + sc_log_warn("test"); + assert(count == 2); + sc_log_error("test"); + assert(count == 3); + + count = 0; + sc_log_set_level("WARN"); + sc_log_debug("test"); + assert(count == 0); + sc_log_info("test"); + assert(count == 0); + sc_log_warn("test"); + assert(count == 1); + sc_log_error("test"); + assert(count == 2); + + count = 0; + sc_log_set_level("OFF"); + sc_log_debug("test"); + assert(count == 0); + sc_log_info("test"); + assert(count == 0); + sc_log_warn("test"); + assert(count == 0); + sc_log_error("test"); + assert(count == 0); + + sc_log_set_level("INFO"); + sc_log_set_stdout(false); + assert(sc_log_set_file("prev.txt", "current.txt") == 0); + for (int i = 0; i < 100000; i++) { + sc_log_error("testtesttesttesttesttesttesttesttesttesttesttest"); + } + + FILE *fp = fopen("prev.txt", "rb"); + assert(fp != NULL); + fseek(fp, 0, SEEK_END); + assert(ftell(fp) >= SC_LOG_FILE_SIZE); + fclose(fp); + + sc_log_term(); +} + +#ifdef SC_HAVE_WRAP + +#include + +int callback2(void *arg, enum sc_log_level level, const char *fmt, va_list va) +{ + vfprintf(stdout, fmt, va); + vfprintf(stdout, fmt, va); + + return 0; +} + +bool mock_fprintf = false; +FILE *fprintf_file; +int fprintf_count = 0; +int fprintf_ret = 0; +extern int __real_fprintf(FILE *stream, const char *format, ...); +int __wrap_fprintf(FILE *stream, const char *format, ...) +{ + int rc; + va_list va; + + if (!mock_fprintf) { + va_start(va, format); + rc = vfprintf(stream, format, va); + va_end(va); + + return rc; + } + + fprintf_file = stream; + fprintf_count++; + return fprintf_ret; +} + +bool mock_vfprintf = false; +FILE *vprintf_file; +int vfprintf_count = 0; +int vfprintf_ret = 0; +extern int __real_vfprintf(FILE *stream, const char *format, va_list arg); +int __wrap_vfprintf(FILE *stream, const char *format, va_list arg) +{ + if (!mock_vfprintf) { + return __real_vfprintf(stream, format, arg); + } + vprintf_file = stream; + vfprintf_count++; + return vfprintf_ret; +} + +bool mock_fopen = false; +extern FILE *__real_fopen(const char *filename, const char *format); +FILE *__wrap_fopen(const char *filename, const char *mode) +{ + if (!mock_fopen) { + return __real_fopen(filename, mode); + } + + return NULL; +} + +bool mock_localtime = false; +extern struct tm *__real_localtime(const time_t *timer); +struct tm *__wrap_localtime(const time_t *timer) +{ + if (!mock_localtime) { + return __real_localtime(timer); + } + + return NULL; +} + +bool mock_attrinit = false; +extern int __real_pthread_mutexattr_init(pthread_mutexattr_t *attr); +int __wrap_pthread_mutexattr_init(pthread_mutexattr_t *attr) +{ + if (!mock_attrinit) { + return __real_pthread_mutexattr_init(attr); + } + + return -1; +} + + +void fail_test(void) +{ + mock_attrinit = true; + assert(sc_log_init() < 0); + mock_attrinit = false; + assert(sc_log_init() == 0); + + mock_fprintf = true; + mock_vfprintf = true; + + vfprintf_count = 0; + sc_log_info("loggg"); + assert(vfprintf_count > 0); + assert(vprintf_file == stdout); + + vfprintf_count = 0; + sc_log_set_stdout(false); + sc_log_info("loggg"); + assert(vfprintf_count == 0); + + sc_log_set_stdout(true); + sc_log_set_file("tmp.txt", "tmp2.txt"); + sc_log_set_callback(callback2, NULL); + + fprintf_count = 0; + vfprintf_count = 0; + sc_log_info("loggg"); + assert(vfprintf_count + fprintf_count == 6); + sc_log_set_callback(NULL, NULL); + + fprintf_count = 0; + vfprintf_count = 0; + sc_log_set_stdout(false); + sc_log_set_file(NULL, NULL); + sc_log_info("loggg"); + assert(vfprintf_count + fprintf_count == 0); + + sc_log_set_stdout(true); + fprintf_ret = -1; + assert(sc_log_info("test") == -1); + fprintf_ret = 0; + assert(sc_log_info("test") == 0); + + sc_log_set_stdout(false); + sc_log_set_file("tmp.txt", "tmp2.txt"); + vfprintf_ret = -1; + assert(sc_log_info("test") == -1); + vfprintf_ret = 0; + assert(sc_log_info("test") == 0); + fprintf_ret = -1; + assert(sc_log_info("test") == -1); + fprintf_ret = 0; + assert(sc_log_info("test") == 0); + fprintf_ret = -1; + assert(sc_log_set_file("tmp.txt", "tmp2.txt") == -1); + fprintf_ret = 0; + + assert(sc_log_set_file(NULL, "test.txt") == 0); + mock_fopen = true; + assert(sc_log_set_file("prev.txt", "current.txt") == -1); + mock_fopen = false; + assert(sc_log_set_file("prev.txt", "current.txt") == 0); + mock_localtime = true; + assert(sc_log_error("test") == -1); + mock_localtime = false; + + mock_vfprintf = false; + mock_fprintf = false; + mock_fopen = true; + int failed = 0; + for (int i = 0; i < 40000; i++) { + failed = sc_log_error("testtesttesttesttesttesttesttesttesttestest"); + if (failed < 0) { + break; + } + } + assert(failed == -1); + mock_fopen = false; + + sc_log_term(); + mock_fprintf = false; + mock_vfprintf = false; + mock_localtime = false; + mock_fopen = false; +} +#else +void fail_test(void) +{ +} +#endif + +int log_callback(void *arg, enum sc_log_level level, + const char *fmt, va_list va) +{ + const char *my_app = arg; + const char *level_str = sc_log_levels[level].str; + + fprintf(stdout, " %s received log : level = [%s] ", my_app, level_str); + vfprintf(stdout, fmt, va); + + return 0; +} + +void example(void) +{ + const char* my_app_name = "my app"; + + //sc_log_init() is not thread-safe, it must be called by a single thread. + sc_log_init(); + + //Default log-level is 'info' and default destination is 'stdout' + sc_log_info("Hello world!"); + + //Enable logging to file. + sc_log_set_file("log.0.txt", "log-latest.txt"); + + //stdout and file will get the log line + sc_log_info("to stdout and file!"); + + //Enable callback + sc_log_set_callback(log_callback, (void*) my_app_name); + + //stdout, file and callback will get the log line + sc_log_info("to all!"); + sc_log_info("to all!"); + + //sc_log_term(); is not thread-safe, it must be called by a single thread. + sc_log_term(); +} + +int main(int argc, char *argv[]) +{ + fail_test(); + example(); + test1(); + + return 0; +} diff --git a/logger/sc_log.c b/logger/sc_log.c new file mode 100644 index 0000000..54712d5 --- /dev/null +++ b/logger/sc_log.c @@ -0,0 +1,326 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "sc_log.h" + +#include +#include +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) + + #pragma warning(disable : 4996) + #define strcasecmp _stricmp + + #include + +struct sc_log_mutex +{ + CRITICAL_SECTION mtx; +}; + +int sc_log_mutex_init(struct sc_log_mutex *mtx) +{ + InitializeCriticalSection(&mtx->mtx); + return 0; +} + +int sc_log_mutex_term(struct sc_log_mutex *mtx) +{ + DeleteCriticalSection(&mtx->mtx); + return 0; +} + +void sc_log_mutex_lock(struct sc_log_mutex *mtx) +{ + EnterCriticalSection(&mtx->mtx); +} + +void sc_log_mutex_unlock(struct sc_log_mutex *mtx) +{ + LeaveCriticalSection(&mtx->mtx); +} + +#else + + #include + +struct sc_log_mutex +{ + pthread_mutex_t mtx; +}; + +int sc_log_mutex_init(struct sc_log_mutex *mtx) +{ + pthread_mutexattr_t attr; + pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; + + mtx->mtx = mut; + + if (pthread_mutexattr_init(&attr) != 0 || + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL) != 0 || + pthread_mutex_init(&mtx->mtx, &attr) != 0) { + return -1; + } + + pthread_mutexattr_destroy(&attr); + return 0; +} + +int sc_log_mutex_term(struct sc_log_mutex *mtx) +{ + return pthread_mutex_destroy(&mtx->mtx); +} + +void sc_log_mutex_lock(struct sc_log_mutex *mtx) +{ + pthread_mutex_lock(&mtx->mtx); +} + +void sc_log_mutex_unlock(struct sc_log_mutex *mtx) +{ + pthread_mutex_unlock(&mtx->mtx); +} + +#endif + +struct sc_log +{ + FILE *fp; + const char *current_file; + const char *prev_file; + size_t file_size; + + struct sc_log_mutex mtx; + enum sc_log_level level; + + bool to_stdout; + sc_log_callback cb; + void *arg; +}; + +struct sc_log sc_log; + +int sc_log_init(void) +{ + int rc; + + rc = sc_log_mutex_init(&sc_log.mtx); + if (rc != 0) { + return -1; + } + + sc_log.level = SC_LOG_INFO; + sc_log.to_stdout = true; + + return 0; +} + +int sc_log_term(void) +{ + int rc = 0; + + if (sc_log.fp) { + rc = fclose(sc_log.fp); + } + + sc_log_mutex_term(&sc_log.mtx); + sc_log = (struct sc_log){0}; + + return rc; +} + +int sc_log_set_level(const char *str) +{ + size_t count = sizeof(sc_log_levels) / sizeof(struct sc_log_level_pair); + + for (size_t i = 0; i < count; i++) { + if (strcasecmp(str, sc_log_levels[i].str) == 0) { + sc_log_mutex_lock(&sc_log.mtx); + sc_log.level = sc_log_levels[i].id; + sc_log_mutex_unlock(&sc_log.mtx); + + return 0; + } + } + + return -1; +} + +int sc_log_set_stdout(bool enable) +{ + sc_log_mutex_lock(&sc_log.mtx); + sc_log.to_stdout = enable; + sc_log_mutex_unlock(&sc_log.mtx); + + return 0; +} + +int sc_log_set_file(const char *prev_file, const char *current_file) +{ + int rc = 0; + long size; + FILE *fp = NULL; + + sc_log_mutex_lock(&sc_log.mtx); + + if (sc_log.fp != NULL) { + rc = fclose(sc_log.fp); + sc_log.fp = NULL; + } + + sc_log.prev_file = prev_file; + sc_log.current_file = current_file; + + if (prev_file == NULL || current_file == NULL) { + goto out; + } + + fp = fopen(sc_log.current_file, "a+"); + if (fp == NULL || fprintf(fp, "\n") < 0 || (size = ftell(fp)) < 0) { + goto error; + } + + sc_log.file_size = size; + sc_log.fp = fp; + + goto out; + +error: + rc = -1; + if (fp != NULL) { + fclose(fp); + } +out: + sc_log_mutex_unlock(&sc_log.mtx); + + return rc; +} + +int sc_log_set_callback(sc_log_callback cb, void *arg) +{ + sc_log_mutex_lock(&sc_log.mtx); + sc_log.cb = cb; + sc_log.arg = arg; + sc_log_mutex_unlock(&sc_log.mtx); + + return 0; +} + +static int sc_log_print_header(FILE *fp, enum sc_log_level level) +{ + time_t t = time(NULL); + struct tm *tm = localtime(&t); + + if (tm == NULL) { + fprintf(fp, "[ERROR] localtime() returns null! \n"); + return -1; + } + + return fprintf(fp, "[%d-%02d-%02d %02d:%02d:%02d][%-5s] ", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, + tm->tm_min, tm->tm_sec, sc_log_levels[level].str); +} + +static int sc_log_stdout(enum sc_log_level level, const char *fmt, va_list va) +{ + int rc; + + rc = sc_log_print_header(stdout, level); + if (rc < 0) { + return -1; + } + + return vfprintf(stdout, fmt, va); +} + +static int sc_log_file(enum sc_log_level level, const char *fmt, va_list va) +{ + int rc, size; + + rc = sc_log_print_header(sc_log.fp, level); + if (rc < 0) { + return -1; + } + + size = vfprintf(sc_log.fp, fmt, va); + if (size < 0) { + return -1; + } + + sc_log.file_size += size; + + if (sc_log.file_size > SC_LOG_FILE_SIZE) { + fclose(sc_log.fp); + (void) rename(sc_log.current_file, sc_log.prev_file); + + sc_log.fp = fopen(sc_log.current_file, "w+"); + if (sc_log.fp == NULL) { + fprintf(stderr, "fopen() failed for [%s], (%s)\n", + sc_log.current_file, strerror(errno)); + return -1; + } + + sc_log.file_size = 0; + } + + return rc; +} + +int sc_log_log(enum sc_log_level level, const char *fmt, ...) +{ + int rc = 0; + va_list va; + + sc_log_mutex_lock(&sc_log.mtx); + + if (level < sc_log.level) { + sc_log_mutex_unlock(&sc_log.mtx); + return 0; + } + + if (sc_log.to_stdout) { + va_start(va, fmt); + rc |= sc_log_stdout(level, fmt, va); + va_end(va); + } + + if (sc_log.fp != NULL) { + va_start(va, fmt); + rc |= sc_log_file(level, fmt, va); + va_end(va); + } + + if (sc_log.cb) { + va_start(va, fmt); + rc |= sc_log.cb(sc_log.arg, level, fmt, va); + va_end(va); + } + + sc_log_mutex_unlock(&sc_log.mtx); + + return rc; +} diff --git a/logger/sc_log.h b/logger/sc_log.h new file mode 100644 index 0000000..0edefd5 --- /dev/null +++ b/logger/sc_log.h @@ -0,0 +1,157 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SC_LOG_H +#define SC_LOG_H + +#include +#include +#include +#include + +enum sc_log_level +{ + SC_LOG_DEBUG, + SC_LOG_INFO, + SC_LOG_WARN, + SC_LOG_ERROR, + SC_LOG_OFF, +}; + +// clang-format off +const static struct sc_log_level_pair +{ + const int id; + const char *str; +} sc_log_levels[] = { + {SC_LOG_DEBUG, "DEBUG"}, + {SC_LOG_INFO, "INFO" }, + {SC_LOG_WARN, "WARN" }, + {SC_LOG_ERROR, "ERROR"}, + {SC_LOG_OFF, "OFF" }, +}; +// clang-format on + +/** + * User callback + * + * @param arg user provided data + * @param level log level + * @param fmt format + * @param va arg list + */ +typedef int (*sc_log_callback)(void *arg, enum sc_log_level level, + const char *fmt, va_list va); + +/** + * Not thread-safe, should be called from a single thread. + * @return '0' on success, negative value on error + */ +int sc_log_init(void); + +/** + * Not thread-safe, should be called from a single thread. + * @return '0' on success, negative value on error + */ +int sc_log_term(void); + +/** + * Thread-safe. + * + * Valid values are 'DEBUG', 'INFO', 'WARN', 'ERROR', 'OFF' + * @param level_str level + * @return '0' on success, negative value on error + */ +int sc_log_set_level(const char *level_str); + +/** + * Thread-safe. + * + * @param enable 'true' to enable, 'false' will disable + * @return '0' on success, negative value on error + */ +int sc_log_set_stdout(bool enable); + +/** + * Thread-safe. + * + * Log file will be rotated. Logger will start writing into 'current_file', + * when it grows larger than 'SC_LOG_FILE_SIZE', logger will rename + * 'current_file' as 'prev_file' and create a new empty file at 'current_file' + * again. So, latest logs will always be in the 'current_file'. + * + * e.g sc_log_set_file("/tmp/log.0.txt", "/tmp/log-latest.txt"); + * + * To disable logging into file : + * + * sc_log_set_file(NULL, NULL); + * + * @param prev_file file path for previous log file, 'NULL' to disable + * @param current_file file path for latest log file, 'NULL' to disable + * @return + */ +int sc_log_set_file(const char *prev_file, const char *current_file); + +/** + * Thread-safe. + * Logs can be reported to callback as well. + * + * @param cb callback. + * @param arg user arg. + * @return '0' on success, negative value on error + */ +int sc_log_set_callback(sc_log_callback cb, void *arg); + +// Internal function +int sc_log_log(enum sc_log_level level, const char *fmt, ...); + +/** + * Max file size to rotate. + */ +#define SC_LOG_FILE_SIZE (2 * 1024 * 1024) + +/** + * Define SC_LOG_PRINT_FILE_NAME if you want to print file name and line number + * in the log line. + */ +#ifdef SC_LOG_PRINT_FILE_NAME + #define sc_log_ap(fmt, ...) \ + "(%s:%d) " fmt "\n", strrchr("/" __FILE__, '/') + 1, __LINE__, \ + __VA_ARGS__ +#else + #define sc_log_ap(fmt, ...) fmt "\n", __VA_ARGS__ +#endif + + +/** + * Printf-style format + * e.g + * sc_log_error("Errno : %d, reason : %s", errno, strerror(errno)); + */ +#define sc_log_debug(...) (sc_log_log(SC_LOG_DEBUG, sc_log_ap(__VA_ARGS__, ""))) +#define sc_log_info(...) (sc_log_log(SC_LOG_INFO, sc_log_ap(__VA_ARGS__, ""))) +#define sc_log_warn(...) (sc_log_log(SC_LOG_WARN, sc_log_ap(__VA_ARGS__, ""))) +#define sc_log_error(...) (sc_log_log(SC_LOG_ERROR, sc_log_ap(__VA_ARGS__, ""))) + +#endif diff --git a/map/CMakeLists.txt b/map/CMakeLists.txt new file mode 100644 index 0000000..5b94db5 --- /dev/null +++ b/map/CMakeLists.txt @@ -0,0 +1,103 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_map C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(sc_map map_example.c sc_map.h sc_map.c) + +if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror -D_GNU_SOURCE") +endif () + + +# --------------------------------------------------------------------------- # +# --------------------- Test Configuration Start ---------------------------- # +# --------------------------------------------------------------------------- # + +include(CTest) +include(CheckCCompilerFlag) + +enable_testing() + +add_executable(${PROJECT_NAME}_test map_test.c sc_map.c) + +target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=140000ul) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin) + target_link_options(${PROJECT_NAME}_test PRIVATE -Wl,--wrap=calloc) + + endif () +endif () + +if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer) + + if (SANITIZER) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + endif () +endif () + +add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test) + +SET(MEMORYCHECK_COMMAND_OPTIONS + "-q --log-fd=2 --trace-children=yes --track-origins=yes \ + --leak-check=full --show-leak-kinds=all --show-reachable=yes \ + --error-exitcode=255") + +add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ + --overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS} + --verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ --verbose + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +# ----------------------- - Code Coverage Start ----------------------------- # + +if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -fprofile-instr-generate) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fcoverage-mapping) + target_link_options(${PROJECT_NAME}_test PRIVATE -fprofile-instr-generate) + target_link_libraries(${PROJECT_NAME}_test PRIVATE clang_rt.profile-x86_64) + + elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage) + target_link_libraries(${PROJECT_NAME}_test gcov) + + endif () +endif () + +add_custom_target(coverage_${PROJECT_NAME}) +add_custom_command( + TARGET coverage_${PROJECT_NAME} + COMMAND lcov --capture --directory .. + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*' + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1 +) + +add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME}) + +# -------------------------- Code Coverage End ------------------------------ # + + +# ----------------------- Test Configuration End ---------------------------- # + diff --git a/map/README.md b/map/README.md new file mode 100644 index 0000000..2aa974d --- /dev/null +++ b/map/README.md @@ -0,0 +1,54 @@ +# Hashmap + +### Overview + +- Open addressing hashmap with linear probing. +- Just copy sc_map.h and sc_map.c to your project. + +#### Usage + + +```c +#include "sc_map.h" + +#include + +int main(int argc, char *argv[]) +{ + char *key, *value; + struct sc_map_str map; + + sc_map_init_str(&map, 0, 0); + + sc_map_put_str(&map, "jack", "chicago"); + sc_map_put_str(&map, "jane", "new york"); + sc_map_put_str(&map, "janie", "atlanta"); + + sc_map_foreach (&map, key, value) { + printf("Key:[%s], Value:[%s] \n", key, value); + } + + sc_map_term_str(&map); + + return 0; +} + +``` + +####Internals +##### Memory +- Entries are kept in a single array without additional bookkeeping data. + +- Single allocation for all the data. Underlying array size is always power + of two, so there are some overhead for allocated but yet not used entries. + e.g (If you have 9 entries, underlying array has capacity for 16 entries) + +##### Performance +- Hashmap is basically a map of 'uint64_t' keys to 'void*' values. + +- An entry is 16 bytes (64 bit systems), as this is an open addressing hashmap, +linear probing with 16 bytes entries plays nicely with cache lines and +hardware prefetcher. + +- It is intentionally not a 'generic' structure. Storing small key value pairs + provides really good performance. \ No newline at end of file diff --git a/map/map_example.c b/map/map_example.c new file mode 100644 index 0000000..b67475b --- /dev/null +++ b/map/map_example.c @@ -0,0 +1,23 @@ +#include "sc_map.h" + +#include + +int main(int argc, char *argv[]) +{ + char *key, *value; + struct sc_map_str map; + + sc_map_init_str(&map, 0, 0); + + sc_map_put_str(&map, "jack", "chicago"); + sc_map_put_str(&map, "jane", "new york"); + sc_map_put_str(&map, "janie", "atlanta"); + + sc_map_foreach (&map, key, value) { + printf("Key:[%s], Value:[%s] \n", key, value); + } + + sc_map_term_str(&map); + + return 0; +} diff --git a/map/map_test.c b/map/map_test.c new file mode 100644 index 0000000..64d3c57 --- /dev/null +++ b/map/map_test.c @@ -0,0 +1,469 @@ +#include "sc_map.h" + +#include +#include +#include +#include + +void example(void) +{ + char *key, *value; + struct sc_map_str map; + + sc_map_init_str(&map, 0, 0); + + sc_map_put_str(&map, "jack", "chicago"); + sc_map_put_str(&map, "jane", "new york"); + sc_map_put_str(&map, "janie", "atlanta"); + + sc_map_foreach (&map, key, value) { + printf("Key:[%s], Value:[%s] \n", key, value); + } + + sc_map_term_str(&map); + +} + +static char *str_random(size_t size) +{ + static char ch[] = "0123456789" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + uint32_t index; + char *dest = malloc(size + 1); + + for (int i = 0; i < size; ++i) { + index = (uint32_t)((double) rand() / RAND_MAX * (sizeof(ch) - 1)); + dest[i] = ch[index]; + } + + dest[size - 1] = '\0'; + + return dest; +} + +void test1() +{ + struct sc_map_str map; + char *keys[128]; + char *values[128]; + char *key, *value; + + for (int i = 0; i < 128; i++) { + keys[i] = str_random((rand() % 64) + 32); + values[i] = str_random((rand() % 64) + 32); + } + + assert(!sc_map_init_str(&map, 0, -1)); + assert(!sc_map_init_str(&map, 0, 24)); + assert(!sc_map_init_str(&map, 0, 96)); + assert(sc_map_init_str(&map, 0, 0)); + assert(sc_map_size_str(&map) == 0); + sc_map_clear_str(&map); + assert(sc_map_size_str(&map) == 0); + sc_map_term_str(&map); + assert(sc_map_init_str(&map, 0, 0)); + + sc_map_foreach (&map, key, value) { + assert(false); + } + + sc_map_foreach_key (&map, key) { + assert(false); + } + + sc_map_foreach_value (&map, value) { + assert(false); + } + + assert(sc_map_put_str(&map, "key", "value")); + assert(sc_map_put_str(&map, "key", "value2")); + assert(sc_map_get_str(&map, "key", &value)); + assert(strcmp(value, "value2") == 0); + + assert(sc_map_del_str(&map, "key", NULL)); + assert(!sc_map_get_str(&map, "key", &value)); + assert(sc_map_put_str(&map, "key", "value3")); + assert(sc_map_del_str(&map, "key", &value)); + assert(strcmp(value, "value3") == 0); + assert(!sc_map_del_str(&map, "key", &value)); + + assert(sc_map_put_str(&map, "key", "value")); + assert(sc_map_size_str(&map) == 1); + assert(sc_map_put_str(&map, NULL, "nullvalue")); + assert(sc_map_size_str(&map) == 2); + assert(sc_map_get_str(&map, NULL, &value)); + assert(strcmp(value, "nullvalue") == 0); + assert(sc_map_del_str(&map, NULL, NULL)); + assert(sc_map_size_str(&map) == 1); + + sc_map_clear_str(&map); + assert(sc_map_size_str(&map) == 0); + + for (int i = 0; i < 100; i++) { + assert(sc_map_put_str(&map, keys[i], values[i])); + } + + for (int i = 0; i < 100; i++) { + assert(sc_map_get_str(&map, keys[i], &value)); + assert(strcmp(value, values[i]) == 0); + } + + sc_map_put_str(&map, keys[0], values[101]); + assert(sc_map_size_str(&map) == 100); + sc_map_put_str(&map, keys[101], values[102]); + assert(sc_map_size_str(&map) == 101); + sc_map_clear_str(&map); + assert(sc_map_size_str(&map) == 0); + + for (int i = 0; i < 100; i++) { + assert(sc_map_put_str(&map, keys[i], values[i])); + } + + for (int i = 0; i < 100; i++) { + assert(sc_map_get_str(&map, keys[i], &value)); + assert(strcmp(value, values[i]) == 0); + } + + sc_map_term_str(&map); + + assert(sc_map_init_str(&map, 0, 0)); + for (int i = 0; i < 100; i++) { + assert(sc_map_put_str(&map, keys[i], values[i])); + } + + bool found; + sc_map_foreach (&map, key, value) { + found = false; + for (int j = 0; j < 100; j++) { + if (strcmp(key, keys[j]) == 0 && strcmp(value, values[j]) == 0) { + found = true; + break; + } + } + assert(found); + } + + sc_map_foreach_key (&map, key) { + found = false; + for (int j = 0; j < 100; j++) { + if (strcmp(key, keys[j]) == 0) { + found = true; + break; + } + } + assert(found); + } + + sc_map_foreach_value (&map, value) { + found = false; + for (int j = 0; j < 100; j++) { + if (strcmp(value, values[j]) == 0) { + found = true; + break; + } + } + assert(found); + } + + sc_map_term_str(&map); + for (int i = 0; i < 128; i++) { + free(keys[i]); + free(values[i]); + } +} + +void test2() +{ + struct sc_map_32 map; + uint32_t keys[128]; + uint32_t values[128]; + uint32_t key, value; + int random; + + for (int i = 0; i < 128; i++) { +retry: + random = rand(); + for (int j = 0; j < i; j++) { + if (keys[j] == random) { + goto retry; + } + } + + keys[i] = random; + values[i] = rand(); + } + + assert(sc_map_init_32(&map, 16, 50)); + assert(sc_map_size_32(&map) == 0); + assert(sc_map_put_32(&map, 0, 0)); + sc_map_clear_32(&map); + assert(sc_map_size_32(&map) == 0); + + for (int i = 0; i < 100; i++) { + assert(sc_map_put_32(&map, keys[i], values[i])); + assert(sc_map_get_32(&map, keys[i], &value)); + assert(value == values[i]); + assert(sc_map_put_32(&map, keys[i], values[i])); + assert(sc_map_del_32(&map, keys[i], &value)); + assert(value == values[i]); + } + + for (int i = 0; i < 128; i++) { + assert(sc_map_put_32(&map, keys[i], values[i])); + } + + assert(sc_map_size_32(&map) == 128); + + bool found; + sc_map_foreach (&map, key, value) { + found = false; + for (int j = 0; j < 128; j++) { + if (key == keys[j] && value == values[j]) { + found = true; + break; + } + } + assert(found); + } + + sc_map_foreach_key (&map, key) { + found = false; + for (int j = 0; j < 128; j++) { + if (key == keys[j]) { + found = true; + break; + } + } + assert(found); + } + + sc_map_foreach_value (&map, value) { + found = false; + for (int j = 0; j < 128; j++) { + if (value == values[j]) { + found = true; + break; + } + } + assert(found); + } + + sc_map_term_32(&map); +} + +void test3() +{ + struct sc_map_64 map; + uint64_t keys[128]; + uint64_t values[128]; + uint64_t key, value; + int random; + + for (int i = 0; i < 128; i++) { +retry: + random = rand(); + for (int j = 0; j < i; j++) { + if (keys[j] == random) { + goto retry; + } + } + + keys[i] = random; + values[i] = rand(); + } + + assert(sc_map_init_64(&map, 16, 50)); + assert(sc_map_size_64(&map) == 0); + assert(sc_map_put_64(&map, 0, 0)); + sc_map_clear_64(&map); + assert(sc_map_size_64(&map) == 0); + + for (int i = 0; i < 100; i++) { + assert(sc_map_put_64(&map, keys[i], values[i])); + assert(sc_map_get_64(&map, keys[i], &value)); + assert(value == values[i]); + assert(sc_map_put_64(&map, keys[i], values[i])); + assert(sc_map_del_64(&map, keys[i], &value)); + assert(value == values[i]); + } + + for (int i = 0; i < 128; i++) { + assert(sc_map_put_64(&map, keys[i], values[i])); + } + + assert(sc_map_size_64(&map) == 128); + + bool found; + sc_map_foreach (&map, key, value) { + found = false; + for (int j = 0; j < 128; j++) { + if (key == keys[j] && value == values[j]) { + found = true; + break; + } + } + assert(found); + } + + sc_map_foreach_key (&map, key) { + found = false; + for (int j = 0; j < 128; j++) { + if (key == keys[j]) { + found = true; + break; + } + } + assert(found); + } + + sc_map_foreach_value (&map, value) { + found = false; + for (int j = 0; j < 128; j++) { + if (value == values[j]) { + found = true; + break; + } + } + assert(found); + } + + sc_map_term_64(&map); +} + +void test4() +{ + char* c; + struct sc_map_64s map64s; + + assert(sc_map_init_64s(&map64s, 0, 87)); + for (int i = 0 ; i < 100; i++) { + assert(sc_map_put_64s(&map64s, i, NULL)); + assert(sc_map_get_64s(&map64s, i, &c)); + assert(c == NULL); + } + assert(sc_map_size_64s(&map64s) == 100); + for (int i = 0 ; i < 100; i++) { + assert(sc_map_del_64s(&map64s, i, &c)); + assert(c == NULL); + } + assert(sc_map_size_64s(&map64s) == 0); + assert(sc_map_put_64s(&map64s, 3, NULL)); + assert(sc_map_size_64s(&map64s) == 1); + sc_map_clear_64s(&map64s); + assert(sc_map_size_64s(&map64s) == 0); + + sc_map_term_64s(&map64s); + + void* v; + struct sc_map_64v map64v; + + assert(sc_map_init_64v(&map64v, 0, 87)); + for (int i = 0 ; i < 100; i++) { + assert(sc_map_put_64v(&map64v, i, NULL)); + assert(sc_map_get_64v(&map64v, i, &v)); + assert(c == NULL); + } + assert(sc_map_size_64v(&map64v) == 100); + for (int i = 0 ; i < 100; i++) { + assert(sc_map_del_64v(&map64v, i, &v)); + assert(v == NULL); + } + assert(sc_map_size_64v(&map64v) == 0); + assert(sc_map_put_64v(&map64v, 3, NULL)); + assert(sc_map_size_64v(&map64v) == 1); + sc_map_clear_64v(&map64v); + assert(sc_map_size_64v(&map64v) == 0); + + sc_map_term_64v(&map64v); + + struct sc_map_sv mapsv; + + assert(sc_map_init_sv(&mapsv, 0, 87)); + for (int i = 0 ; i < 100; i++) { + assert(sc_map_put_sv(&mapsv, "", NULL)); + assert(sc_map_get_sv(&mapsv, "", &v)); + assert(v == NULL); + } + assert(sc_map_size_sv(&mapsv) == 1); + assert(sc_map_del_sv(&mapsv, "", &v)); + assert(v == NULL); + assert(sc_map_size_sv(&mapsv) == 0); + assert(!sc_map_del_sv(&mapsv, "", &v)); + sc_map_clear_sv(&mapsv); + assert(sc_map_size_sv(&mapsv) == 0); + sc_map_term_sv(&mapsv); + + uint64_t val; + struct sc_map_s64 maps64; + + assert(sc_map_init_s64(&maps64, 0, 26)); + assert(sc_map_put_s64(&maps64, "", 511)); + assert(sc_map_put_s64(&maps64, "", 511)); + assert(sc_map_get_s64(&maps64, "", &val)); + assert(val == 511); + assert(sc_map_size_s64(&maps64) == 1); + assert(sc_map_del_s64(&maps64, "", &val)); + assert(val == 511); + assert(sc_map_size_s64(&maps64) == 0); + sc_map_clear_s64(&maps64); + sc_map_term_s64(&maps64); +} + + +#ifdef SC_HAVE_WRAP + +bool fail_calloc = false; +void *__real_calloc(size_t n, size_t size); +void *__wrap_calloc(size_t n, size_t size) +{ + if (fail_calloc) { + return NULL; + } + + return __real_calloc(n, size); +} + +void fail_test() +{ + struct sc_map_32 map; + + fail_calloc = true; + assert(!sc_map_init_32(&map, 10, 0)); + fail_calloc = false; + assert(sc_map_init_32(&map, 10, 0)); + + fail_calloc = true; + bool success = true; + for (int i =0 ; i < 20; i++) { + success = sc_map_put_32(&map, i, i); + } + assert(!success); + fail_calloc = false; + assert(sc_map_put_32(&map, 44444, 44444)); + + for (int i = 0 ; i < SC_SIZE_MAX; i++) { + success = sc_map_put_32(&map, i, i); + } + assert(!success); + fail_calloc = false; + + sc_map_term_32(&map); +} +#else +void fail_test(void) +{ +} +#endif + +int main(int argc, char *argv[]) +{ + example(); + fail_test(); + test1(); + test2(); + test3(); + test4(); + + return 0; +} diff --git a/map/sc_map.c b/map/sc_map.c new file mode 100644 index 0000000..5d84419 --- /dev/null +++ b/map/sc_map.c @@ -0,0 +1,356 @@ +#include "sc_map.h" + +#include +#include + +#ifndef SC_SIZE_MAX + #define SC_SIZE_MAX UINT32_MAX +#endif + +#define sc_map_impl_of_strkey(name, K, V, cmp, hash_fn) \ + bool sc_map_cmp_##name(struct sc_map_item_##name *t, K key, uint32_t hash) \ + { \ + return t->hash == hash && cmp(t->key, key); \ + } \ + \ + void sc_map_assign_##name(struct sc_map_item_##name *t, K key, V value, \ + uint32_t hash) \ + { \ + t->key = key; \ + t->value = value; \ + t->hash = hash; \ + } \ + \ + uint32_t sc_map_hashof_##name(struct sc_map_item_##name *t) \ + { \ + return t->hash; \ + } \ + \ + sc_map_impl_of(name, K, V, cmp, hash_fn) + +#define sc_map_impl_of_scalar(name, K, V, cmp, hash_fn) \ + bool sc_map_cmp_##name(struct sc_map_item_##name *t, K key, uint32_t hash) \ + { \ + return cmp(t->key, key); \ + } \ + \ + void sc_map_assign_##name(struct sc_map_item_##name *t, K key, V value, \ + uint32_t hash) \ + { \ + t->key = key; \ + t->value = value; \ + } \ + \ + uint32_t sc_map_hashof_##name(struct sc_map_item_##name *t) \ + { \ + return hash_fn(t->key); \ + } \ + \ + sc_map_impl_of(name, K, V, cmp, hash_fn) + +#define sc_map_impl_of(name, K, V, cmp, hash_fn) \ + \ + static const struct sc_map_##name sc_map_empty_##name = { \ + .cap = 1, \ + .mem = &(struct sc_map_item_##name){.key = (0)}}; \ + \ + static void *sc_map_alloc_##name(uint32_t *cap, uint32_t factor) \ + { \ + uint32_t v = *cap; \ + struct sc_map_item_##name *t; \ + \ + if (*cap > SC_SIZE_MAX / factor) { \ + return NULL; \ + } \ + \ + /* Find next power of two */ \ + v = v < 8 ? 8 : (v * factor); \ + v--; \ + for (uint32_t i = 1; i < sizeof(v) * 8; i *= 2) { \ + v |= v >> i; \ + } \ + v++; \ + \ + *cap = v; \ + return sc_map_calloc(sizeof(*t), v); \ + } \ + \ + bool sc_map_init_##name(struct sc_map_##name *map, uint32_t cap, \ + uint32_t load_factor) \ + { \ + void *t; \ + uint32_t f = (load_factor == 0) ? 75 : load_factor; \ + \ + if (f > 95 || f < 25) { \ + return false; \ + } \ + \ + if (cap == 0) { \ + *map = sc_map_empty_##name; \ + map->load_factor = f; \ + return true; \ + } \ + \ + t = sc_map_alloc_##name(&cap, 1); \ + if (t == NULL) { \ + return false; \ + } \ + \ + map->mem = t; \ + map->size = 0; \ + map->used = false; \ + map->cap = cap; \ + map->load_factor = f; \ + map->remap = (uint32_t)(map->cap * ((double) map->load_factor / 100)); \ + \ + return true; \ + } \ + \ + void sc_map_term_##name(struct sc_map_##name *map) \ + { \ + if (map->mem != sc_map_empty_##name.mem) { \ + sc_map_free(map->mem); \ + } \ + } \ + \ + uint32_t sc_map_size_##name(struct sc_map_##name *map) \ + { \ + return map->size; \ + } \ + \ + void sc_map_clear_##name(struct sc_map_##name *map) \ + { \ + if (map->size > 0) { \ + for (uint32_t i = 0; i < map->cap; i++) { \ + map->mem[i].key = 0; \ + } \ + \ + map->size = 0; \ + } \ + } \ + \ + static bool sc_map_remap_##name(struct sc_map_##name *map) \ + { \ + uint32_t pos, cap, mod; \ + struct sc_map_item_##name *new; \ + \ + if (map->size < map->remap) { \ + return true; \ + } \ + \ + cap = map->cap; \ + new = sc_map_alloc_##name(&cap, 2); \ + if (new == NULL) { \ + return false; \ + } \ + \ + mod = cap - 1; \ + \ + for (uint32_t i = 0; i < map->cap; i++) { \ + if (map->mem[i].key != 0) { \ + pos = sc_map_hashof_##name(&map->mem[i]) & (mod); \ + \ + while (true) { \ + if (new[pos].key == 0) { \ + new[pos] = map->mem[i]; \ + break; \ + } \ + \ + pos = (pos + 1) & (mod); \ + } \ + } \ + } \ + \ + if (map->mem != sc_map_empty_##name.mem) { \ + sc_map_free(map->mem); \ + } \ + \ + map->mem = new; \ + map->cap = cap; \ + map->remap = (uint32_t)(map->cap * ((double) map->load_factor / 100)); \ + \ + return true; \ + } \ + \ + bool sc_map_put_##name(struct sc_map_##name *map, K key, V value) \ + { \ + uint32_t pos, mod, hash; \ + \ + if (key == 0) { \ + map->size += !map->used; \ + map->used = 1; \ + map->value = value; \ + \ + return true; \ + } \ + \ + if (!sc_map_remap_##name(map)) { \ + return false; \ + } \ + \ + mod = map->cap - 1; \ + hash = hash_fn(key); \ + pos = hash & (mod); \ + \ + while (true) { \ + if (map->mem[pos].key == 0) { \ + map->size++; \ + } else if (sc_map_cmp_##name(&map->mem[pos], key, hash) != true) { \ + pos = (pos + 1) & (mod); \ + continue; \ + } \ + \ + sc_map_assign_##name(&map->mem[pos], key, value, hash); \ + return true; \ + } \ + } \ + \ + bool sc_map_get_##name(struct sc_map_##name *map, K key, V *value) \ + { \ + const uint32_t mod = map->cap - 1; \ + uint32_t hash, pos; \ + \ + if (key == 0) { \ + *value = map->value; \ + return map->used; \ + } \ + \ + hash = hash_fn(key); \ + pos = hash & mod; \ + \ + while (true) { \ + if (map->mem[pos].key == 0) { \ + return false; \ + } else if (sc_map_cmp_##name(&map->mem[pos], key, hash) != true) { \ + pos = (pos + 1) & (mod); \ + continue; \ + } \ + \ + *value = map->mem[pos].value; \ + return true; \ + } \ + } \ + \ + bool sc_map_del_##name(struct sc_map_##name *map, K key, V *value) \ + { \ + const uint32_t mod = map->cap - 1; \ + uint32_t pos, prev_elem, curr, curr_orig, hash; \ + \ + if (key == 0) { \ + bool ret = map->used; \ + map->size -= map->used; \ + map->used = false; \ + \ + if (value != NULL) { \ + *value = map->value; \ + } \ + \ + return ret; \ + } \ + \ + hash = hash_fn(key); \ + pos = hash & (mod); \ + \ + while (true) { \ + if (map->mem[pos].key == 0) { \ + return false; \ + } else if (sc_map_cmp_##name(&map->mem[pos], key, hash) != true) { \ + pos = (pos + 1) & (mod); \ + continue; \ + } \ + \ + if (value != NULL) { \ + *value = map->mem[pos].value; \ + } \ + \ + map->size--; \ + map->mem[pos].key = 0; \ + prev_elem = pos; \ + curr = pos; \ + \ + while (true) { \ + curr = (curr + 1) & (mod); \ + if (map->mem[curr].key == 0) { \ + break; \ + } \ + \ + curr_orig = sc_map_hashof_##name(&map->mem[curr]) & (mod); \ + \ + if ((curr_orig > curr && \ + (curr_orig <= prev_elem || curr >= prev_elem)) || \ + (curr_orig <= prev_elem && curr >= prev_elem)) { \ + \ + map->mem[prev_elem] = map->mem[curr]; \ + map->mem[curr].key = 0; \ + prev_elem = curr; \ + } \ + } \ + \ + return true; \ + } \ + } + + +static uint32_t sc_map_hash_32(uint32_t a) +{ + return a; +} + +static uint32_t sc_map_hash_64(uint64_t a) +{ + return ((uint32_t) a) ^ (uint32_t)(a >> 32u); +} + +// clang-format off +uint32_t murmurhash(const char *key) +{ + const uint64_t m = UINT64_C(0xc6a4a7935bd1e995); + const size_t len = strlen(key); + const char *end = key + (len & ~(uint64_t) 0x7); + uint64_t h = (len * m); + + while (key != end) { + uint64_t k; + memcpy(&k, key, sizeof(k)); + + k *= m; + k ^= k >> 47u; + k *= m; + + h ^= k; + h *= m; + key += 8; + } + + switch (len & 7u) { + case 7: h ^= (uint64_t) key[6] << 48ul; + case 6: h ^= (uint64_t) key[5] << 40ul; + case 5: h ^= (uint64_t) key[4] << 32ul; + case 4: h ^= (uint64_t) key[3] << 24ul; + case 3: h ^= (uint64_t) key[2] << 16ul; + case 2: h ^= (uint64_t) key[1] << 8ul; + case 1: h ^= (uint64_t) key[0]; + h *= m; + }; + + h ^= h >> 47u; + h *= m; + h ^= h >> 47u; + + return h; +} +// clang-format off + +#define sc_map_varcmp(a, b) ((a) == (b)) +#define sc_map_strcmp(a, b) (!strcmp(a, b)) + +// name, key type, value type, cmp hash +sc_map_impl_of_scalar(32, uint32_t, uint32_t, sc_map_varcmp, sc_map_hash_32) +sc_map_impl_of_scalar(64, uint64_t, uint64_t, sc_map_varcmp, sc_map_hash_64) +sc_map_impl_of_scalar(64v, uint64_t, void *, sc_map_varcmp, sc_map_hash_64) +sc_map_impl_of_scalar(64s, uint64_t, char *, sc_map_varcmp, sc_map_hash_64) +sc_map_impl_of_strkey(str, char *, char *, sc_map_strcmp, murmurhash) +sc_map_impl_of_strkey(sv, char *, void *, sc_map_strcmp, murmurhash) +sc_map_impl_of_strkey(s64, char *, uint64_t, sc_map_strcmp, murmurhash) + + // clang-format on diff --git a/map/sc_map.h b/map/sc_map.h new file mode 100644 index 0000000..5aa57e6 --- /dev/null +++ b/map/sc_map.h @@ -0,0 +1,102 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SC_MAP_H +#define SC_MAP_H + +#include +#include +#include +#include +#include + +#define sc_map_of_strkey(name, K, V) \ + struct sc_map_item_##name \ + { \ + K key; \ + V value; \ + uint32_t hash; \ + }; \ + \ + sc_map_of(name, K, V) + +#define sc_map_of_scalar(name, K, V) \ + struct sc_map_item_##name \ + { \ + K key; \ + V value; \ + }; \ + \ + sc_map_of(name, K, V) + +#define sc_map_of(name, K, V) \ + struct sc_map_##name \ + { \ + struct sc_map_item_##name *mem; \ + uint32_t cap; \ + uint32_t size; \ + uint32_t load_factor; \ + uint32_t remap; \ + V value; \ + bool used; \ + }; \ + \ + bool sc_map_init_##name(struct sc_map_##name *map, uint32_t cap, \ + uint32_t load_factor); \ + void sc_map_term_##name(struct sc_map_##name *map); \ + uint32_t sc_map_size_##name(struct sc_map_##name *map); \ + void sc_map_clear_##name(struct sc_map_##name *map); \ + bool sc_map_put_##name(struct sc_map_##name *map, K key, V val); \ + bool sc_map_get_##name(struct sc_map_##name *map, K key, V *value); \ + bool sc_map_del_##name(struct sc_map_##name *map, K key, V* value); + +#define sc_map_foreach(map, K, V) \ + for (uint32_t __i = 0, __b = 0; __i < (map)->cap; __i++) \ + for ((V) = (map)->mem[__i].value, (K) = (map)->mem[__i].key, __b = 1; \ + __b && (V) != 0; __b = 0) + +#define sc_map_foreach_key(map, K) \ + for (uint32_t __i = 0, __b = 0; __i < (map)->cap; __i++) \ + for ((K) = (map)->mem[__i].key, __b = 1; __b && (K) != 0; __b = 0) + +#define sc_map_foreach_value(map, V) \ + for (uint32_t __i = 0, __b = 0; __i < (map)->cap; __i++) \ + for ((V) = (map)->mem[__i].value, __b = 1; __b && (V) != 0; __b = 0) + +#define sc_map_calloc calloc +#define sc_map_free free + +// clang-format off + +// name key type value type); +sc_map_of_scalar(32, uint32_t, uint32_t) +sc_map_of_scalar(64, uint64_t, uint64_t) +sc_map_of_scalar(64v, uint64_t, void *) +sc_map_of_scalar(64s, uint64_t, char *) +sc_map_of_strkey(str, char *, char *) +sc_map_of_strkey(sv, char *, void*) +sc_map_of_strkey(s64, char *, uint64_t) + +// clang-format on +#endif diff --git a/mutex/CMakeLists.txt b/mutex/CMakeLists.txt new file mode 100644 index 0000000..3bf338a --- /dev/null +++ b/mutex/CMakeLists.txt @@ -0,0 +1,96 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_mutex C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(sc_mutex mutex_example.c sc_mutex.h sc_mutex.c) + +if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror -D_GNU_SOURCE -pthread") +endif () + + +# --------------------------------------------------------------------------- # +# --------------------- Test Configuration Start ---------------------------- # +# --------------------------------------------------------------------------- # + +include(CTest) +include(CheckCCompilerFlag) + +enable_testing() + +add_executable(${PROJECT_NAME}_test mutex_test.c sc_mutex.c) + +target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=1400000ul) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin) + target_link_options(${PROJECT_NAME}_test PRIVATE + -Wl,--wrap=pthread_mutexattr_init) + endif () +endif () + +if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer) + + if (SANITIZER) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + endif () +endif () + +add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test) + +SET(MEMORYCHECK_COMMAND_OPTIONS + "-q --log-fd=2 --trace-children=yes --track-origins=yes \ + --leak-check=full --show-leak-kinds=all --show-reachable=yes \ + --error-exitcode=255") + +add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ + --overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS} + --verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ --verbose + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +# ----------------------- - Code Coverage Start ----------------------------- # + +if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage) + target_link_libraries(${PROJECT_NAME}_test gcov) + else() + message(FATAL_ERROR "Only GCC is supported for coverage") + endif() +endif () + +add_custom_target(coverage_${PROJECT_NAME}) +add_custom_command( + TARGET coverage_${PROJECT_NAME} + COMMAND lcov --capture --directory .. + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*' + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1 +) + +add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME}) + +# -------------------------- Code Coverage End ------------------------------ # + + +# ----------------------- Test Configuration End ---------------------------- # + diff --git a/mutex/README.md b/mutex/README.md new file mode 100644 index 0000000..09fd376 --- /dev/null +++ b/mutex/README.md @@ -0,0 +1,27 @@ +# Mutex + +### Overview + +- Mutex wrapper for Windows and POSIX systems. +- Just copy sc_mutex.h and sc_mutex.c to your project. + +#### Usage + + +```c +#include "sc_mutex.h" + +int main(int argc, char *argv[]) +{ + struct sc_mutex mutex; + + sc_mutex_init(&mutex); + + sc_mutex_lock(&mutex); + // Exclusive area. + sc_mutex_unlock(&mutex); + + sc_mutex_term(&mutex); + + return 0; +} diff --git a/mutex/mutex_example.c b/mutex/mutex_example.c new file mode 100644 index 0000000..78be2e9 --- /dev/null +++ b/mutex/mutex_example.c @@ -0,0 +1,15 @@ +#include "sc_mutex.h" + +int main(int argc, char *argv[]) +{ + struct sc_mutex mutex; + + sc_mutex_init(&mutex); + + sc_mutex_lock(&mutex); + sc_mutex_unlock(&mutex); + + sc_mutex_term(&mutex); + + return 0; +} diff --git a/mutex/mutex_test.c b/mutex/mutex_test.c new file mode 100644 index 0000000..0a63ef7 --- /dev/null +++ b/mutex/mutex_test.c @@ -0,0 +1,57 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "sc_mutex.h" + +#include +#include + +#ifdef SC_HAVE_WRAP +bool mock_attrinit = false; +extern int __real_pthread_mutexattr_init(pthread_mutexattr_t *attr); +int __wrap_pthread_mutexattr_init(pthread_mutexattr_t *attr) +{ + if (!mock_attrinit) { + return __real_pthread_mutexattr_init(attr); + } + + return -1; +} +#endif + +int main(int argc, char *argv[]) +{ + struct sc_mutex mutex; +#ifdef SC_HAVE_WRAP + mock_attrinit = true; + assert(sc_mutex_init(&mutex) != 0); + mock_attrinit = false; +#endif + assert(sc_mutex_init(&mutex) == 0); + sc_mutex_lock(&mutex); + sc_mutex_unlock(&mutex); + assert(sc_mutex_term(&mutex) == 0); + + return 0; +} diff --git a/mutex/sc_mutex.c b/mutex/sc_mutex.c new file mode 100644 index 0000000..963f6c6 --- /dev/null +++ b/mutex/sc_mutex.c @@ -0,0 +1,98 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "sc_mutex.h" + +#include + +#if defined(_WIN32) || defined(_WIN64) + +int sc_mutex_init(struct sc_mutex *mtx) +{ + InitializeCriticalSection(&mtx->mtx); + return 0; +} + +int sc_mutex_term(struct sc_mutex *mtx) +{ + DeleteCriticalSection(&mtx->mtx); + return 0; +} + +void sc_mutex_lock(struct sc_mutex *mtx) +{ + EnterCriticalSection(&mtx->mtx); +} + +void sc_mutex_unlock(struct sc_mutex *mtx) +{ + LeaveCriticalSection(&mtx->mtx); +} + +#else + +int sc_mutex_init(struct sc_mutex *mtx) +{ + int rc; + pthread_mutexattr_t attr; + pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; + + mtx->mtx = mut; + + // May fail on OOM + rc = pthread_mutexattr_init(&attr); + if (rc != 0) { + return rc; + } + + // This won't fail as long as we pass correct params. + rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + assert (rc == 0); + + // May fail on OOM + rc = pthread_mutex_init(&mtx->mtx, &attr); + + // This won't fail as long as we pass correct param. + pthread_mutexattr_destroy(&attr); + return rc; +} + +int sc_mutex_term(struct sc_mutex *mtx) +{ + return pthread_mutex_destroy(&mtx->mtx); +} + +void sc_mutex_lock(struct sc_mutex *mtx) +{ + // This won't fail as long as we pass correct param. + pthread_mutex_lock(&mtx->mtx); +} + +void sc_mutex_unlock(struct sc_mutex *mtx) +{ + // This won't fail as long as we pass correct param. + pthread_mutex_unlock(&mtx->mtx); +} + +#endif diff --git a/mutex/sc_mutex.h b/mutex/sc_mutex.h new file mode 100644 index 0000000..cdeab57 --- /dev/null +++ b/mutex/sc_mutex.h @@ -0,0 +1,48 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SC_MUTEX_H +#define SC_MUTEX_H + +#if defined(_WIN32) || defined(_WIN64) + #include +#else + #include +#endif + +struct sc_mutex +{ +#if defined(_WIN32) || defined(_WIN64) + CRITICAL_SECTION mtx; +#else + pthread_mutex_t mtx; +#endif +}; + +int sc_mutex_init(struct sc_mutex *mtx); +int sc_mutex_term(struct sc_mutex *mtx); +void sc_mutex_lock(struct sc_mutex *mtx); +void sc_mutex_unlock(struct sc_mutex *mtx); + +#endif diff --git a/perf/CMakeLists.txt b/perf/CMakeLists.txt new file mode 100644 index 0000000..4fa8e6c --- /dev/null +++ b/perf/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_perf C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + add_executable(sc_perf perf_example.c sc_perf.h sc_perf.c) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -pedantic -Werror -D_GNU_SOURCE") +endif () + diff --git a/perf/README.md b/perf/README.md new file mode 100644 index 0000000..25782ad --- /dev/null +++ b/perf/README.md @@ -0,0 +1,73 @@ +# Perf benchmark + +### Overview + +- Using perf_event_open to get hardware and software counters while you + are still inside your code. +- Only useful when you want to measure something inside the code really quick. + Otherwise, use perf itself. +- Linux only. +- All hardware and software counters are generated in the header file, you can + uncomment counters as you wish. + +#### Usage + +```c + +#include "sc_perf.h" + +int main(int argc, char *argv[]) +{ + sc_perf_start(); + + long_running_operation(); + + sc_perf_end(); + + return 0; +} + +``` +##### Output will be like +``` +| Event | Value | Measurement time +--------------------------------------------------------------- +| time (seconds) | 0.66 | (100,00%) +| cpu-clock | 654075766.00 | (100.00%) +| task-clock | 654077198.00 | (100.00%) +| page-faults | 3.00 | (100.00%) +| context-switches | 46.00 | (100.00%) +| cpu-migrations | 0.00 | (100.00%) +| page-fault-minor | 3.00 | (100.00%) +| cpu-cycles | 2656529748.00 | (100.00%) +| instructions | 7589235720.00 | (100.00%) +| cache-misses | 28715.00 | (100.00%) +| L1D-read-miss | 34124.00 | (100.00%) +| L1I-read-miss | 121958.00 | (100.00%) +``` + +##### Pause example +```c + +#include "sc_perf.h" + +int main(int argc, char *argv[]) +{ + sc_perf_start(); + + long_running_operation(); + + //Will stop counters. + sc_perf_pause(); + operation_you_dont_want_to_measure(); + + //Start counters again. + sc_perf_start(); + another_long_running_operation(); + + sc_perf_end(); + + return 0; +} + +``` \ No newline at end of file diff --git a/perf/perf_example.c b/perf/perf_example.c new file mode 100644 index 0000000..fa6cab1 --- /dev/null +++ b/perf/perf_example.c @@ -0,0 +1,21 @@ +#include "sc_perf.h" +#include + +int main(int argc, char *argv[]) +{ + size_t total = 0; + + sc_perf_start(); + for (int i = 0; i < 100000000; i++) { + total += (rand() % 331) ^ 33; + } + sc_perf_pause(); + + for (int i = 0; i < 100000000; i++) { + total += (rand() % 327) ^ 37; + } + + sc_perf_end(); + + return total; +} diff --git a/perf/sc_perf.c b/perf/sc_perf.c new file mode 100644 index 0000000..71ae7ef --- /dev/null +++ b/perf/sc_perf.c @@ -0,0 +1,188 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "sc_perf.h" + +#include + +#define ITEMS_SIZE (sizeof(sc_perf_hw) / sizeof(struct sc_perf_event)) + +static int initialized = 0; +static int running = 0; +static uint64_t total = 0; +static uint64_t start = 0; + +struct sc_perf_item +{ + struct sc_perf_event event; + double value; + double active; + int fd; +}; + + +static struct sc_perf_item sc_perf_items[ITEMS_SIZE]; + +#define sc_perf_assert(val) \ + do { \ + if (!(val)) { \ + fprintf(stderr, "%s:%d: error", __FILE__, __LINE__); \ + if (errno) { \ + fprintf(stderr, " (%s)", strerror(errno)); \ + } \ + abort(); \ + } \ + } while (0) + +static void sc_perf_set(struct sc_perf_item *items, size_t size) +{ + const uint64_t flags = + PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING; + int fd; + + for (int i = 0; i < size; i++) { + struct perf_event_attr p = {.size = sizeof(struct perf_event_attr), + .read_format = flags, + .type = items[i].event.type, + .config = items[i].event.config, + .disabled = 1, + .inherit = 1, + .inherit_stat = 0, + .exclude_kernel = false, + .exclude_hv = false}; + + fd = syscall(__NR_perf_event_open, &p, 0, -1, -1, PERF_FLAG_FD_CLOEXEC); + if (fd == -1) { + fprintf(stderr, + "Failed to set counter : %s , probably your system does " + "not support it! \n", + items[i].event.name); + abort(); + } + + items[i].fd = fd; + } +} + +static void sc_read(struct sc_perf_item *items, size_t size) +{ + struct read_format + { + uint64_t value; + uint64_t time_enabled; + uint64_t time_running; + } fmt; + + for (int i = 0; i < size; i++) { + double n = 1.0; + + sc_perf_assert(read(items[i].fd, &fmt, sizeof(fmt)) == sizeof(fmt)); + + if (fmt.time_enabled > 0 && fmt.time_running > 0) { + n = (double) fmt.time_running / (double) fmt.time_enabled; + items[i].active = n; + } + + items[i].value += fmt.value * n; + } +} + +static void sc_perf_clear(void) +{ + total = 0; + start = 0; + running = 0; + initialized = 0; + + for (int i = 0; i < ITEMS_SIZE; i++) { + sc_perf_items[i].event = sc_perf_hw[i]; + sc_perf_items[i].value = 0; + sc_perf_items[i].active = 0; + sc_perf_items[i].fd = -1; + } +} + +static uint64_t sy_time_nano(void) +{ + int rc; + struct timespec ts; + + rc = clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + if (rc == -1) { + abort(); + } + + return ((uint64_t)(ts.tv_nsec + (ts.tv_sec * 1000 * 1000 * 1000))); +} + +void sc_perf_start(void) +{ + if (!initialized) { + sc_perf_clear(); + sc_perf_set(sc_perf_items, ITEMS_SIZE); + initialized = 1; + } + + sc_perf_assert(prctl(PR_TASK_PERF_EVENTS_ENABLE) == 0); + + start = sy_time_nano(); + running = 1; +} + +void sc_perf_pause(void) +{ + sc_perf_assert(initialized); + + if (!running) { + return; + } + + sc_perf_assert(prctl(PR_TASK_PERF_EVENTS_DISABLE) == 0); + + total += sy_time_nano() - start; + running = 0; +} + +void sc_perf_end(void) +{ + sc_perf_assert(initialized); + + sc_perf_pause(); + sc_read(sc_perf_items, ITEMS_SIZE); + + for (int i = 0; i < ITEMS_SIZE; i++) { + close(sc_perf_items[i].fd); + } + + printf("\n| %-25s | %-18s | %s \n", "Event", "Value", "Measurement time"); + printf("---------------------------------------------------------------\n"); + printf("| %-25s | %-18.2f | %s \n", "time (seconds)", + ((double) total / 1e9), "(100,00%)"); + + for (int i = 0; i < ITEMS_SIZE; i++) { + printf("| %-25s | %-18.2f | (%.2f%%) \n", sc_perf_items[i].event.name, + sc_perf_items[i].value, sc_perf_items[i].active * 100); + } + + sc_perf_clear(); +} diff --git a/perf/sc_perf.h b/perf/sc_perf.h new file mode 100644 index 0000000..a3703c3 --- /dev/null +++ b/perf/sc_perf.h @@ -0,0 +1,127 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SC_PERF_H +#define SC_PERF_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SC_PERF_HW_CACHE(CACHE, OP, RESULT) \ + ((PERF_COUNT_HW_CACHE_##CACHE) | (PERF_COUNT_HW_CACHE_OP_##OP << 8u) | \ + (PERF_COUNT_HW_CACHE_RESULT_##RESULT << 16u)) + +struct sc_perf_event +{ + char *name; + uint64_t type; + uint64_t config; +}; + +// clang-format off +static const struct sc_perf_event sc_perf_hw[] = { + {"cpu-clock", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_CLOCK }, + {"task-clock", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_TASK_CLOCK }, + {"page-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS }, + {"context-switches", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CONTEXT_SWITCHES }, + {"cpu-migrations", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_MIGRATIONS }, + {"page-fault-minor", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MIN }, + // {"page-fault-major", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MAJ }, + // {"alignment-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_ALIGNMENT_FAULTS }, + // {"emulation-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_EMULATION_FAULTS }, + + {"cpu-cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES }, + {"instructions", PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS }, + // {"cache-references", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES }, + {"cache-misses", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES }, + // {"branch-instructions", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS }, + // {"branch-misses", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES }, + // {"bus-cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BUS_CYCLES }, + // {"stalled-cycles-frontend", PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_FRONTEND }, + // {"stalled-cycles-backend", PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_BACKEND }, + // {"ref-cpu-cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_REF_CPU_CYCLES }, + + // {"L1D-read-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1D, READ, ACCESS) }, + {"L1D-read-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1D, READ, MISS) }, + // {"L1D-write-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1D, WRITE, ACCESS) }, + // {"L1D-write-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1D, WRITE, MISS) }, + // {"L1D-prefetch-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1D, PREFETCH, ACCESS) }, + // {"L1D-prefetch-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1D, PREFETCH, MISS) }, + // {"L1I-read-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1I, READ, ACCESS) }, + {"L1I-read-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1I, READ, MISS) }, + // {"L1I-write-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1I, WRITE, ACCESS) }, + // {"L1I-write-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1I, WRITE, MISS) }, + // {"L1I-prefetch-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1I, PREFETCH, ACCESS) }, + // {"L1I-prefetch-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1I, PREFETCH, MISS) }, + // {"LL-read-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(LL, READ, ACCESS) }, + // {"LL-read-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(LL, READ, MISS) }, + // {"LL-write-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(LL, WRITE, ACCESS) }, + // {"LL-write-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(LL, WRITE, MISS) }, + // {"LL-prefetch-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(LL, PREFETCH, ACCESS) }, + // {"LL-prefetch-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(LL, PREFETCH, MISS) }, + // {"DTLB-read-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(DTLB, READ, ACCESS) }, + // {"DTLB-read-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(DTLB, READ, MISS) }, + // {"DTLB-write-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(DTLB, WRITE, ACCESS) }, + // {"DTLB-write-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(DTLB, WRITE, MISS) }, + // {"DTLB-prefetch-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(DTLB, PREFETCH, ACCESS) }, + // {"DTLB-prefetch-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(DTLB, PREFETCH, MISS) }, + // {"ITLB-read-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(ITLB, READ, ACCESS) }, + // {"ITLB-read-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(ITLB, READ, MISS) }, + // {"ITLB-write-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(ITLB, WRITE, ACCESS) }, + // {"ITLB-write-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(ITLB, WRITE, MISS) }, + // {"ITLB-prefetch-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(ITLB, PREFETCH, ACCESS) }, + // {"ITLB-prefetch-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(ITLB, PREFETCH, MISS) }, + // {"BPU-read-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(BPU, READ, ACCESS) }, + // {"BPU-read-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(BPU, READ, MISS) }, + // {"BPU-write-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(BPU, WRITE, ACCESS) }, + // {"BPU-write-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(BPU, WRITE, MISS) }, + // {"BPU-prefetch-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(BPU, PREFETCH, ACCESS) }, + // {"BPU-prefetch-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(BPU, PREFETCH, MISS) }, + // {"NODE-read-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(NODE, READ, ACCESS) }, + // {"NODE-read-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(NODE, READ, MISS) }, + // {"NODE-write-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(NODE, WRITE, ACCESS) }, + // {"NODE-write-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(NODE, WRITE, MISS) }, + // {"NODE-prefetch-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(NODE, PREFETCH, ACCESS) }, + // {"NODE-prefetch-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(NODE, PREFETCH, MISS) }, +}; + +// clang-format on + + +void sc_perf_start(); +void sc_perf_pause(); +void sc_perf_end(); + +#endif diff --git a/queue/CMakeLists.txt b/queue/CMakeLists.txt new file mode 100644 index 0000000..3ae63d1 --- /dev/null +++ b/queue/CMakeLists.txt @@ -0,0 +1,97 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_queue C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(sc_queue queue_example.c sc_queue.h sc_queue.c) + +if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror -D_GNU_SOURCE") +endif () + + +# --------------------------------------------------------------------------- # +# --------------------- Test Configuration Start ---------------------------- # +# --------------------------------------------------------------------------- # + +include(CTest) +include(CheckCCompilerFlag) + +enable_testing() + +add_executable(${PROJECT_NAME}_test queue_test.c sc_queue.c) + +target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=1400000ul) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer) + target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin) + target_link_options(${PROJECT_NAME}_test PRIVATE -Wl,--wrap=realloc) + endif () +endif () + +if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer) + + if (SANITIZER) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + endif () +endif () + + +add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test) + +SET(MEMORYCHECK_COMMAND_OPTIONS + "-q --log-fd=2 --trace-children=yes --track-origins=yes \ + --leak-check=full --show-leak-kinds=all --show-reachable=yes \ + --error-exitcode=255") + +add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ + --overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS} + --verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ --verbose + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +# ----------------------- - Code Coverage Start ----------------------------- # + +if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage) + target_link_libraries(${PROJECT_NAME}_test gcov) + else() + message(FATAL_ERROR "Only GCC is supported for coverage") + endif() +endif () + +add_custom_target(coverage_${PROJECT_NAME}) +add_custom_command( + TARGET coverage_${PROJECT_NAME} + COMMAND lcov --capture --directory .. + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*' + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1 +) + +add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME}) + +# -------------------------- Code Coverage End ------------------------------ # + + +# ----------------------- Test Configuration End ---------------------------- # + diff --git a/queue/README.md b/queue/README.md new file mode 100644 index 0000000..6072bce --- /dev/null +++ b/queue/README.md @@ -0,0 +1,81 @@ +# Generic queue + +#### Overview + +- Type generic queue which grows when you add elements. +- Add/remove from head/tail is possible so it can be used as list, stack, + queue, dequeue etc. +- Just copy sc_queue.h and sc_queue.c to your project. + + +##### Usage + + +```c +#include "sc_queue.h" + +#include + +int main(int argc, char *argv[]) +{ + int *queue; + int elem; + + sc_queue_create(queue, 0); + + sc_queue_add_last(queue, 2); + sc_queue_add_last(queue, 3); + sc_queue_add_last(queue, 4); + sc_queue_add_first(queue, 1); + + sc_queue_foreach (queue, elem) { + printf("elem = [%d] \n", elem); + } + + elem = sc_queue_remove_last(queue); + printf("Last element was : [%d] \n", elem); + + elem = sc_queue_remove_first(queue); + printf("First element was : [%d] \n", elem); + + sc_queue_destroy(queue); + + return 0; +} +``` +#### Internals + +##### Memory +- Single allocation for all the data. +- Lazy allocation. No memory allocation until first 'add'. + +##### Performance +- As all the items are in a single contiguous memory, it gives the best +performance you can expect. +- Keeps separate first and last element indexes, when you remove an element, + it doesn't move elements to fill the space. + +##### Note + +Queue pointer is not stable, it may change if we expand the memory. If you +pass the queue to another function which can add items, do it by passing +reference of the queue pointer : + +```c +void some_function_to_add_elems(long **q) +{ + sc_queue_add_last(*q, 500); +} + +int main(int argc, char *argv[]) +{ + long *q; + + sc_queue_create(q, 0); + sc_queue_add_last(q, 300); + + some_function_to_add_elems(&q); + sc_array_destroy(q); +} + +``` \ No newline at end of file diff --git a/queue/queue_example.c b/queue/queue_example.c new file mode 100644 index 0000000..d73d346 --- /dev/null +++ b/queue/queue_example.c @@ -0,0 +1,30 @@ +#include "sc_queue.h" + +#include + +int main(int argc, char *argv[]) +{ + int *queue; + int elem; + + sc_queue_create(queue, 0); + + sc_queue_add_last(queue, 2); + sc_queue_add_last(queue, 3); + sc_queue_add_last(queue, 4); + sc_queue_add_first(queue, 1); + + sc_queue_foreach (queue, elem) { + printf("elem = [%d] \n", elem); + } + + elem = sc_queue_remove_last(queue); + printf("Last element was : [%d] \n", elem); + + elem = sc_queue_remove_first(queue); + printf("First element was : [%d] \n", elem); + + sc_queue_destroy(queue); + + return 0; +} diff --git a/queue/queue_test.c b/queue/queue_test.c new file mode 100644 index 0000000..5eabb70 --- /dev/null +++ b/queue/queue_test.c @@ -0,0 +1,159 @@ +#include "sc_queue.h" + +#include +#include + +#ifdef SC_HAVE_WRAP + +bool fail_realloc = false; +void *__real_realloc(void *p, size_t size); +void *__wrap_realloc(void *p, size_t n) +{ + if (fail_realloc) { + return NULL; + } + + return __real_realloc(p, n); +} + +void fail_test(void) +{ + double *q; + + fail_realloc = true; + assert(sc_queue_create(q, 1000) == false); + fail_realloc = false; + assert(sc_queue_create(q, 1000)); + + fail_realloc = true; + bool success = false; + for (int i = 0; i < 1024; i++) { + success = sc_queue_add_last(q, i); + if (!success) { + break; + } + } + + assert(!success); + assert(sc_queue_size(q) == 1023); + fail_realloc = false; + assert(sc_queue_add_last(q, 1023) == true); + for (int i = 0; i < 1024; i++) { + assert(sc_queue_remove_first(q) == i); + } + assert(sc_queue_size(q) == 0); + assert(sc_queue_cap(q) == 2048); + + fail_realloc = false; + + size_t max = SC_SIZE_MAX / 2; + success = true; + for (size_t i = 0; i < max + 500; i++) { + success = sc_queue_add_last(q, i); + if (!success) { + break; + } + } + + assert(!success); + sc_queue_clear(q); + assert(sc_queue_size(q) == 0); + sc_queue_destroy(q); + assert(sc_queue_create(q, max + 100) == false); + fail_realloc = false; +} +#else +void fail_test(void) +{ +} + +#endif + +void example(void) +{ + int *queue; + int elem; + + sc_queue_create(queue, 0); + + sc_queue_add_last(queue, 2); + sc_queue_add_last(queue, 3); + sc_queue_add_last(queue, 4); + sc_queue_add_first(queue, 1); + + sc_queue_foreach (queue, elem) { + printf("elem = [%d] \n", elem); + } + + elem = sc_queue_remove_last(queue); + printf("Last element was : [%d] \n", elem); + + elem = sc_queue_remove_first(queue); + printf("First element was : [%d] \n", elem); + + sc_queue_destroy(queue); +} + +void test1(void) +{ + int count = 0; + int t; + int i = 0; + int *p; + + assert(sc_queue_create(p, 0) == true); + + sc_queue_foreach (p, t) { + (void) t; + count++; + } + assert(count == 0); + assert(sc_queue_empty(p) == true); + assert(sc_queue_size(p) == 0); + + assert(sc_queue_add_first(p, 2) == true); + assert(sc_queue_add_first(p, 3) == true); + assert(sc_queue_add_first(p, 4) == true); + assert(sc_queue_add_first(p, 5) == true); + assert(sc_queue_add_first(p, 6) == true); + assert(sc_queue_add_last(p, 1) == true); + assert(sc_queue_add_last(p, 0) == true); + + assert(sc_queue_empty(p) == false); + + i = 6; + sc_queue_foreach (p, t) { + assert(t == i--); + count += t; + } + assert(count == 6 * 7 / 2); + assert(sc_queue_size(p) == 7); + + assert(sc_queue_peek_first(p) == 6); + assert(sc_queue_size(p) == 7); + assert(sc_queue_peek_last(p) == 0); + assert(sc_queue_size(p) == 7); + + t = sc_queue_remove_first(p); + assert(t == 6); + assert(sc_queue_size(p) == 6); + + t = sc_queue_remove_last(p); + assert(t == 0); + assert(sc_queue_size(p) == 5); + + sc_queue_clear(p); + assert(sc_queue_size(p) == 0); + assert(sc_queue_cap(p) == 8); + assert(sc_queue_empty(p) == true); + + sc_queue_destroy(p); +} + +int main(int argc, char *argv[]) +{ + fail_test(); + example(); + test1(); + return 0; +} diff --git a/queue/sc_queue.c b/queue/sc_queue.c new file mode 100644 index 0000000..4296361 --- /dev/null +++ b/queue/sc_queue.c @@ -0,0 +1,145 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "sc_queue.h" + +#include + +#ifndef SC_SIZE_MAX + #define SC_SIZE_MAX SIZE_MAX +#endif + +#define SC_MAX_CAP ((SC_SIZE_MAX - sizeof(struct sc_queue)) / 2ul) + +static const struct sc_queue sc_empty = {.cap = 1, .first = 0, .last = 0}; + +static void *queue_alloc(void *prev, size_t elem_size, size_t *cap) +{ + size_t v = *cap; + void *t; + + if (*cap > SC_MAX_CAP) { + return NULL; + } + + // Find next power of two. + v = v < 4 ? 4 : v; + v--; + for (size_t i = 1; i < sizeof(v) * 8; i *= 2) { + v |= v >> i; + } + v++; + + t = sc_queue_realloc(prev, sizeof(struct sc_queue) + (elem_size * v)); + *cap = v; + + return t; +} + +bool sc_queue_init(void **q, size_t elem_size, size_t cap) +{ + size_t p = cap; + struct sc_queue *meta; + + if (cap == 0) { + *q = (void *) sc_empty.elems; + return true; + } + + meta = queue_alloc(NULL, elem_size, &p); + if (meta == NULL) { + *q = NULL; + return false; + } + + meta->cap = p; + meta->first = 0; + meta->last = 0; + *q = meta->elems; + + return true; +} + +void sc_queue_term(void **q) +{ + struct sc_queue *meta = sc_queue_meta(*q); + + if (meta != &sc_empty) { + sc_queue_free(meta); + } + + *q = NULL; +} + +bool sc_queue_expand(void **q, size_t elem_size) +{ + struct sc_queue *tmp; + struct sc_queue *meta = sc_queue_meta(*q); + size_t cap, count, size; + size_t pos = (meta->last + 1) & (meta->cap - 1); + uint8_t *e; + + if (pos == meta->first) { + if (meta == &sc_empty) { + return sc_queue_init(q, elem_size, 4); + } + + cap = meta->cap * 2; + tmp = queue_alloc(meta, elem_size, &cap); + if (tmp == NULL) { + return false; + } + + /** + * Move items to make empty slots at the end. + * e.g : + * last first + * | | + * Step 0 : | 2 | 3 | - | 1 | // tmp->cap : 4 + * Step 1 : | 2 | 3 | - | 1 | - | - | - | - | // realloc + * Step 2 : | 2 | 3 | - | 1 | 1 | - | - | - | // mempcy + * Step 3 : | 2 | 2 | 3 | 1 | 1 | - | - | - | // memmove + * Step 4 : | 1 | 2 | 3 | 1 | 1 | - | - | - | // mempcy + * Step 5 : | 1 | 2 | 3 | - | - | - | - | - | // tmp->last = cap - 1; + * | | + * first last + * + */ + + e = tmp->elems; + count = tmp->cap - tmp->first; + size = elem_size; + + memcpy(e + (size * tmp->cap), e + (size * tmp->first), count * size); + memmove(e + (count * size), e, tmp->first * size); + memcpy(e, e + (size * tmp->cap), count * size); + + tmp->last = tmp->cap - 1; + tmp->first = 0; + tmp->cap = cap; + *q = tmp->elems; + } + + return true; +} diff --git a/queue/sc_queue.h b/queue/sc_queue.h new file mode 100644 index 0000000..7c41aff --- /dev/null +++ b/queue/sc_queue.h @@ -0,0 +1,236 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SC_QUEUE_H +#define SC_QUEUE_H + +#include +#include +#include +#include +#include +#include + +/** + * Internals, do not use + */ +struct sc_queue +{ + size_t cap; + size_t first; + size_t last; + uint8_t elems[]; +}; + +#define sc_queue_meta(q) \ + ((struct sc_queue *) ((char *) (q) -offsetof(struct sc_queue, elems))) + +static inline size_t sc_queue_inc_first(void *q) +{ + struct sc_queue *meta = sc_queue_meta(q); + size_t tmp = meta->first; + + meta->first = (meta->first + 1) & (meta->cap - 1); + return tmp; +} + +static inline size_t sc_queue_inc_last(void *q) +{ + struct sc_queue *meta = sc_queue_meta(q); + size_t tmp = meta->last; + + meta->last = (meta->last + 1) & (meta->cap - 1); + return tmp; +} + +static inline size_t sc_queue_dec_first(void *q) +{ + struct sc_queue *meta = sc_queue_meta(q); + + meta->first = (meta->first - 1) & (meta->cap - 1); + return meta->first; +} + +static inline size_t sc_queue_dec_last(void *q) +{ + struct sc_queue *meta = sc_queue_meta(q); + + meta->last = (meta->last - 1) & (meta->cap - 1); + return meta->last; +} + +bool sc_queue_init(void **q, size_t elem_size, size_t cap); +void sc_queue_term(void **q); +bool sc_queue_expand(void **q, size_t elem_size); + + +/** + * Plug your allocator if you want. + */ +#define sc_queue_realloc realloc +#define sc_queue_free free + +/** + * @param q Queue pointer + * @param count Initial capacity, '0' is a valid value if you don't want to + * allocate memory immediately. + */ +#define sc_queue_create(q, count) \ + sc_queue_init((void **) &(q), sizeof(*(q)), count) + +/** + * Deallocate underlying memory. + * @param q Queue pointer + */ +#define sc_queue_destroy(q) sc_queue_term(((void **) &(q))) + +/** + * @param q Queue pointer + * @return Current capacity + */ +#define sc_queue_cap(q) (sc_queue_meta((q))->cap) + +/** + * @param q Queue pointer + * @return Element count + */ +#define sc_queue_size(q) \ + ((sc_queue_meta(q)->last - sc_queue_meta(q)->first) & \ + (sc_queue_meta(q)->cap - 1)) + +/** + * @param q Queue pointer + * @return true if queue is empty + */ +#define sc_queue_empty(q) ((sc_queue_meta(q)->last == sc_queue_meta(q)->first)) + +/** + * Clear the queue without deallocating underlying memory. + * @param q Queue pointer + */ +#define sc_queue_clear(q) \ + do { \ + sc_queue_meta(q)->first = 0; \ + sc_queue_meta(q)->last = 0; \ + } while (0) + +/** + * @param q Queue pointer + * @return Index of the first element. If queue is empty, result is undefined. + */ +#define sc_queue_first(q) (sc_queue_meta(q)->first) + +/** + * @param q Queue pointer + * @return Index of the last element. If queue is empty, result is undefined. + */ +#define sc_queue_last(q) (sc_queue_meta(q)->last) + +/** + * @return Index of the next element after i, if there is no element after i + * result is undefined. + */ +#define sc_queue_next(q, i) (((i) + 1) & (sc_queue_meta(q)->cap - 1)) + +/** + * Returns element at 'i'th position, so regular loops are possible : + * + * for (size_t i = 0; i < sc_queue_size(q); i++) { + * printf("%d" \n, sc_queue_at(q, i)); + * } + * + * @param q Queue pointer + * @return element at 'i'th position + */ +#define sc_queue_at(q, i) \ + (q)[((sc_queue_meta(q)->first) + (i)) & (sc_queue_cap(q) - 1)] + +/** + * @param q Queue pointer + * @return First element without removing from the queue. + * If queue is empty, result is undefined + */ +#define sc_queue_peek_first(q) ((q)[sc_queue_meta(q)->first]) + +/** + * @param q Queue pointer + * @return Last element without removing from the queue. + * If queue is empty, result is undefined + */ +#define sc_queue_peek_last(q) \ + (q)[(sc_queue_meta(q)->last - 1) & (sc_queue_meta(q)->cap - 1)] + +/** + * @param q Queue pointer + * @param elem Elem to be added at the end of the list + * @return 'true' on success, 'false' on out of memory. + */ +#define sc_queue_add_last(q, elem) \ + sc_queue_expand((void **) &(q), sizeof(*(q))) == true ? \ + (q)[sc_queue_inc_last((q))] = (elem), \ + true : false + +/** + * @param q Queue pointer + * @return Remove the last element from the queue and return its value. + * If queue is empty, result is undefined. + */ +#define sc_queue_remove_last(q) ((q)[sc_queue_dec_last((q))]) + +/** + * @param q Queue pointer. + * @param elem Elem to be added at the head of the list. + * @return 'true' on success, 'false' on out of memory. + */ +#define sc_queue_add_first(q, elem) \ + sc_queue_expand((void **) &(q), sizeof(*(q))) == true ? \ + (q)[sc_queue_dec_first((q))] = (elem), \ + true : false + +/** + * @param q Queue pointer + * @return Remove the first element from the queue and return its value. + * If queue is empty, result is undefined. + */ +#define sc_queue_remove_first(q) (q)[sc_queue_inc_first((q))] + +/** + * For each loop, + * + * int *queue; + * sc_queue_create(queue, 4); + * + * int elem; + * sc_queue_foreach(queue, elem) { + * printf("Elem : %d \n, elem); + * } + */ +#define sc_queue_foreach(q, elem) \ + if (!sc_queue_empty(q)) { \ + (elem) = (q)[sc_queue_first(q)]; \ + } \ + for (size_t _i = sc_queue_first(q); _i != sc_queue_last(q); \ + _i = sc_queue_next(q, _i), (elem) = (q)[_i]) + +#endif diff --git a/string/CMakeLists.txt b/string/CMakeLists.txt new file mode 100644 index 0000000..c52a4b1 --- /dev/null +++ b/string/CMakeLists.txt @@ -0,0 +1,98 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_str C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(sc_str str_example.c sc_str.h sc_str.c) + +if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror -D_GNU_SOURCE") +endif () + + +# --------------------------------------------------------------------------- # +# --------------------- Test Configuration Start ---------------------------- # +# --------------------------------------------------------------------------- # + +include(CTest) +include(CheckCCompilerFlag) + +enable_testing() + +add_executable(${PROJECT_NAME}_test str_test.c sc_str.c) + +target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=4000ul) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin) + target_link_options(${PROJECT_NAME}_test PRIVATE + -Wl,--wrap=malloc,--wrap=vsnprintf,--wrap=realloc) + + target_link_options(${PROJECT_NAME}_test PRIVATE + -Wl,--wrap=malloc,--wrap=vsnprintf,--wrap=realloc) + endif () +endif () +if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer) + + if (SANITIZER) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + endif () +endif () + +add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test) + +SET(MEMORYCHECK_COMMAND_OPTIONS + "-q --log-fd=2 --trace-children=yes --track-origins=yes \ + --leak-check=full --show-leak-kinds=all --show-reachable=yes \ + --error-exitcode=255") + +add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ + --overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS} + --verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ --verbose + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +# ----------------------- - Code Coverage Start ----------------------------- # + +if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage) + target_link_libraries(${PROJECT_NAME}_test gcov) + else() + message(FATAL_ERROR "Only GCC is supported for coverage") + endif() +endif () + +add_custom_target(coverage_${PROJECT_NAME}) +add_custom_command( + TARGET coverage_${PROJECT_NAME} + COMMAND lcov --capture --directory .. + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*' + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1 +) + +add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME}) + +# -------------------------- Code Coverage End ------------------------------ # + + +# ----------------------- Test Configuration End ---------------------------- # + diff --git a/string/README.md b/string/README.md new file mode 100644 index 0000000..f073726 --- /dev/null +++ b/string/README.md @@ -0,0 +1,39 @@ +# Length prefixed string + +Length prefixed C strings, length is at the start of the allocated memory + + e.g : + ----------------------------------------------- + | 0 | 0 | 0 | 4 | 'T' | 'E' | 'S' | 'T' | '\0'| + ----------------------------------------------- + ^ + return + User can keep pointer to first character, so it's like C style strings with + additional functionality when it's used with these functions here. + +##### Pros +- User gets a null terminated `char*`, so it still works with c style string + functions, e.g printf, strcmp. +- Faster length access and copy. +- Provides a few more functions to make easier create/append/trim/substring + operations. + +##### Cons +- 4 bytes fixed overhead per string. + +##### Memory +- 4 bytes fixed overhead per string. + +##### Performance +- Faster length access and copy. +- When you create/set/append a string new memory is allocated. If you are + modifying strings a lot, consider using buffer-like implementation for that if + performance is critical for your use-case. I modify strings rarely but access + a lot (copy/move etc.), so ease of use and read/copy/move performance was + primary goal for this implementation. + + +```c + + +``` \ No newline at end of file diff --git a/string/sc_str.c b/string/sc_str.c new file mode 100644 index 0000000..af77b37 --- /dev/null +++ b/string/sc_str.c @@ -0,0 +1,369 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "sc_str.h" + +#include +#include +#include +#include +#include + +/** + * String with 'length' at the start of the allocated memory + * e.g : + * ----------------------------------------------- + * | 0 | 0 | 0 | 4 | 'T' | 'E' | 'S' | 'T' | '\0'| + * ----------------------------------------------- + * + * User can keep pointer to first character, so it's like C style strings with + * additional functionality when it's used with these functions here. + */ +struct sc_str +{ + uint32_t len; + char buf[]; +}; + +#define sc_str_meta(str) \ + ((struct sc_str *) ((char *) (str) -offsetof(struct sc_str, buf))) + +#define sc_str_bytes(n) ((n) + sizeof(struct sc_str) + 1) +#ifndef SC_SIZE_MAX + #define SC_SIZE_MAX (UINT32_MAX - sizeof(struct sc_str) - 1) +#endif + +char *sc_str_create(const char *str) +{ + assert(str != NULL); + + size_t size = strlen(str); + if (size > SC_SIZE_MAX) { + return NULL; + } + + return sc_str_create_len(str, size); +} + +char *sc_str_create_len(const char *str, uint32_t len) +{ + assert(str != NULL); + + struct sc_str *copy; + + copy = sc_str_malloc(sc_str_bytes(len)); + if (copy == NULL) { + return NULL; + } + + memcpy(copy->buf, str, len); + copy->buf[len] = '\0'; + copy->len = len; + + return copy->buf; +} + +char *sc_str_create_va(const char *fmt, va_list va) +{ + int rc; + char tmp[1024]; + struct sc_str *str; + va_list args; + + va_copy(args, va); + rc = vsnprintf(tmp, sizeof(tmp), fmt, args); + if (rc < 0) { + return NULL; + } + va_end(args); + + str = sc_str_malloc(sc_str_bytes(rc)); + if (str == NULL) { + return NULL; + } + + str->len = rc; + + if (rc < sizeof(tmp)) { + memcpy(str->buf, tmp, str->len + 1); + } else { + va_copy(args, va); + rc = vsnprintf(str->buf, str->len, fmt, args); + va_end(args); + + if (rc < 0 || rc > str->len) { + sc_str_free(str); + return NULL; + } + } + + return str->buf; +} + +char *sc_str_create_fmt(const char *fmt, ...) +{ + char *str; + va_list args; + + va_start(args, fmt); + str = sc_str_create_va(fmt, args); + va_end(args); + + return str; +} + +void sc_str_destroy(char *str) +{ + if (str == NULL) { + return; + } + + sc_str_free(sc_str_meta(str)); +} + +int64_t sc_str_len(const char *str) +{ + if (str == NULL) { + return -1; + } + + return sc_str_meta(str)->len; +} + +char *sc_str_dup(const char *str) +{ + if (str == NULL) { + return NULL; + } + + return sc_str_create_len(str, sc_str_meta(str)->len); +} + +bool sc_str_set(char **str, const char *param) +{ + char *copy = sc_str_create(param); + if (copy == NULL) { + return false; + } + + sc_str_destroy(*str); + *str = copy; + + return true; +} + +bool sc_str_set_fmt(char **str, const char *fmt, ...) +{ + char *ret; + va_list args; + + va_start(args, fmt); + ret = sc_str_create_va(fmt, args); + va_end(args); + + if (ret != NULL) { + sc_str_destroy(*str); + *str = ret; + } + + return ret != NULL; +} + +bool sc_str_append(char **str, const char *param) +{ + struct sc_str *meta = sc_str_meta(*str); + size_t len = strlen(param); + size_t alloc = sc_str_bytes(meta->len + len); + + if (alloc > SC_SIZE_MAX || (meta = sc_str_realloc(meta, alloc)) == NULL) { + return false; + } + + memcpy(&meta->buf[meta->len], param, len); + meta->len += len; + meta->buf[meta->len] = '\0'; + *str = meta->buf; + + return true; +} + +bool sc_str_cmp(const char *str, const char *other) +{ + assert(str != NULL); + assert(other != NULL); + + struct sc_str *s1 = sc_str_meta(str); + struct sc_str *s2 = sc_str_meta(other); + + return s1->len == s2->len && !memcmp(s1->buf, s2->buf, s1->len); +} + +static void swap(char *str, char *d) +{ + char tmp; + char *c = str + sc_str_meta(str)->len; + + tmp = *c; + *c = *d; + *d = tmp; +} + +const char *sc_str_token_begin(char *str, char **save, const char *delim) +{ + char *it = str; + + if (*save != NULL) { + it = *save; + swap(str, it); + if (*it == '\0') { + return NULL; + } + it++; + } + + *save = it + strcspn(it, delim); + swap(str, *save); + + return it; +} + +void sc_str_token_end(char *str, char **save) +{ + char *end = str + sc_str_meta(str)->len; + + if (*end == '\0') { + return; + } + + swap(str, (save != NULL && *save != NULL) ? *save : str + strlen(str)); +} + +bool sc_str_trim(char **str, char *list) +{ + char *start = *str + strspn(*str, list); + char *end = start + strcspn(start, list); + + if (start != *str || end != *str) { + start = sc_str_create_len(start, end - start); + if (start == NULL) { + return false; + } + + sc_str_destroy(*str); + *str = start; + } + + return true; +} + +bool sc_str_substring(char **str, uint32_t start, uint32_t end) +{ + char *c; + struct sc_str *meta = sc_str_meta(*str); + + if (start > meta->len || end > meta->len || start > end) { + return false; + } + + c = sc_str_create_len(*str + start, end - start); + if (c == NULL) { + return false; + } + + sc_str_destroy(*str); + *str = c; + + return true; +} + +bool sc_str_replace(char **str, const char *replace, const char *with) +{ + assert(replace != NULL && with != NULL); + assert(str != NULL && *str != NULL); + + size_t replace_len = strlen(replace); + size_t with_len = strlen(with); + if (replace_len > UINT32_MAX || with_len > UINT32_MAX) { + return false; + } + int64_t diff = (int64_t)with_len - replace_len; + size_t len_unmatch; + size_t count, size; + struct sc_str *dest; + struct sc_str *meta = sc_str_meta(*str); + char *orig = *str; + char *orig_end = *str + meta->len; + char *tmp; + + // Fast path, same size replacement. + if (diff == 0) { + while ((orig = strstr(orig, replace)) != NULL) { + memcpy(orig, with, replace_len); + orig += replace_len; + } + + return true; + } + + // Calculate new string size. + tmp = orig; + size = meta->len; + for (count = 0; (tmp = strstr(tmp, replace)) != NULL; count++) { + tmp += replace_len; + // Check overflow. + if (size > SC_SIZE_MAX - diff) { + return false; + } + size += diff; + } + + // No match. + if (count == 0) { + return true; + } + + dest = sc_str_malloc(sc_str_bytes(size)); + if (!dest) { + return false; + } + dest->len = size; + tmp = dest->buf; + + while (count--) { + len_unmatch = strstr(orig, replace) - orig; + memcpy(tmp, orig, len_unmatch); + tmp += len_unmatch; + + memcpy(tmp, with, with_len); + tmp += with_len; + orig += len_unmatch + replace_len; + } + + memcpy(tmp, orig, orig_end - orig + 1); + + sc_str_destroy(*str); + *str = dest->buf; + + return true; +} diff --git a/string/sc_str.h b/string/sc_str.h new file mode 100644 index 0000000..24a8f2b --- /dev/null +++ b/string/sc_str.h @@ -0,0 +1,198 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SC_STR_H +#define SC_STR_H + +#include +#include +#include +#include + +/** + * Length prefixed C strings, length is at the start of the allocated memory + * e.g : + * ----------------------------------------------- + * | 0 | 0 | 0 | 4 | 'T' | 'E' | 'S' | 'T' | '\0'| + * ----------------------------------------------- + * ^ + * return + * User can keep pointer to first character, so it's like C style strings with + * additional functionality when it's used with these functions here. + */ + +/** + * Plug your allocators. + */ +#define sc_str_malloc malloc +#define sc_str_realloc realloc +#define sc_str_free free + +/** + * @param str '\0' terminated C string, must not be NULL. + * @return Length prefixed string. NULL on out of memory. + */ +char *sc_str_create(const char *str); + +/** + * @param str C string, no need for '\0' termination, must not be NULL. + * @param len Length of the 'str'. + * @return Length prefixed string. NULL on out of memory. + */ +char *sc_str_create_len(const char *str, uint32_t len); + +/** + * printf-style string creation. + * + * @param fmt Format + * @param ... Arguments + * @return Length prefixed string. NULL on out of memory. + */ +char *sc_str_create_fmt(const char *fmt, ...); + +/** + * vprintf-style string creation. + * + * @param fmt Format + * @param va va_list + * @return Length prefixed string. NULL on out of memory. + */ +char *sc_str_create_va(const char *fmt, va_list va); + +/** + * Deallocate length prefixed string. + * + * @param str Length prefixed string. NULL values are accepted. + */ +void sc_str_destroy(char *str); + +/** + * @param str Length prefixed string. NULL values are accepted. + * @return Length of the string. If NULL, '-1' will be returned. + */ +int64_t sc_str_len(const char *str); + +/** + * @param str Length prefixed string. NULL values are accepted. + * @return Length of the duplicate. NULL on out of memory. + */ +char *sc_str_dup(const char *str); + +/** + * @param str Pointer to length prefixed string. + * @param param New value to set. + * @return 'false' on out of memory. + * 'true' on success, '*str' may change. + */ +bool sc_str_set(char **str, const char *param); + +/** + * @param str Pointer to length prefixed string. + * @param fmt Format + * @param ... Arguments + * @return 'false' on out of memory, previous value will remain intact. + * 'true' on success, '*str' may change. + */ +bool sc_str_set_fmt(char **str, const char *fmt, ...); + +/** + * @param str Pointer to length prefixed string. + * @param text Text to append. + * @return 'false' on out of memory, previous value will remain intact. + * 'true' on success, '*str' may change. + */ +bool sc_str_append(char **str, const char *text); + +/** + * @param str Pointer to length prefixed string. (char**). + * @param fmt Format + * @param ... Arguments + * @return 'false' on out of memory, previous value will remain intact. + * 'true' on success, '*str' may change. + */ +#define sc_str_append_fmt(str, fmt, ...) \ + sc_str_set_fmt(str, "%s" fmt, *str, __VA_ARGS__) + +/** + * Compare two length prefixed string. If you want to compare with regular + * C string, use strcmp(). + * + * @param str Length prefixed string, must not be NULL. + * @param other Length prefixed string, must not be NULL. + * @return 'true' if equals. + */ +bool sc_str_cmp(const char *str, const char *other); + +/** + * Tokenization is zero-copy but a bit tricky. This function will mutate 'str', + * but it is temporary. On each 'sc_str_token_begin' call, this function will + * place '\0' character at the end of a token and put delimiter at the end of + * the 'str'. + * e.g user1,user2\0 after first iteration will be user1\0user2, + * + * sc_str_token_end() will fix original string if necessary. + * + * usage: + * + * char *str = sc_str_create("user1,user2,user3"); + * char *save = NULL; // Must be initialized with NULL. + * const char *token; + * + * while ((token = sc_str_token_begin(str, &save, ",") != NULL) { + * printf("token : %s \n", token); + * } + * + * sc_str_token_end(str, &save); + * + * + * @param str Length prefixed string, must not be NULL. + * @param save Helper variable for tokenizer code. + * @param delim Delimiter list. + * @return Token. + */ +const char *sc_str_token_begin(char *str, char **save, const char *delim); +void sc_str_token_end(char *str, char **save); + +/** + * @param str Length prefixed string, must not be NULL. + * @param list Character list to trim. + * @return 'false' on out of memory, previous value will remain intact. + * 'true' on success, '*str' may change. + */ +bool sc_str_trim(char **str, char *list); + +/** + * @param str Length prefixed string, must not be NULL. + * @param start Start index. + * @param end End index. + * @return 'false' on out of range. + * 'false' on out of memory, previous value will remain intact. + * 'true' on success, '*str' may change. + */ +bool sc_str_substring(char **str, uint32_t start, uint32_t end); + +bool sc_str_replace(char **str, const char *rep, const char *with); + + +#endif diff --git a/string/str_example.c b/string/str_example.c new file mode 100644 index 0000000..b8284af --- /dev/null +++ b/string/str_example.c @@ -0,0 +1,25 @@ +#include "sc_str.h" + +#include + + +int main(int argc, char *argv[]) +{ + char* s1; + + s1 = sc_str_create("**hello**"); + printf("%s \n", s1); // prints **hello** + + sc_str_append_fmt(&s1, " %s", "world--"); + printf("%s \n", s1); // prints **hello**world-- + + sc_str_trim(&s1, "*-"); + printf("%s \n", s1); // prints **hello**world-- + + sc_str_substring(&s1, 6, 11); + printf("%s \n", s1); // world + + sc_str_destroy(s1); + + return 0; +} diff --git a/string/str_test.c b/string/str_test.c new file mode 100644 index 0000000..6b19a7d --- /dev/null +++ b/string/str_test.c @@ -0,0 +1,356 @@ +#include "sc_str.h" + +#include +#include +#include + +#ifdef SC_HAVE_WRAP + +bool fail_malloc = false; +void *__real_malloc(size_t n); +void *__wrap_malloc(size_t n) +{ + if (fail_malloc) { + return NULL; + } + + return __real_malloc(n); +} + +bool fail_realloc = false; +void *__real_realloc(void *p, size_t size); +void *__wrap_realloc(void *p, size_t n) +{ + if (fail_realloc) { + return NULL; + } + + return __real_realloc(p, n); +} + +bool fail_vsnprintf; +int fail_vsnprintf_at = -1; +extern int __real_vsnprintf(char *str, size_t size, const char *format, + va_list ap); +int __wrap_vsnprintf(char *str, size_t size, const char *format, va_list ap) +{ + fail_vsnprintf_at--; + if (!fail_vsnprintf && (fail_vsnprintf_at) != 0) { + return __real_vsnprintf(str, size, format, ap); + } + + return -1; +} + +void test1() +{ + assert(sc_str_len(NULL) == -1); + sc_str_destroy(NULL); + assert(sc_str_dup(NULL) == NULL); + + char *s1 = sc_str_create("test"); + char *s2 = sc_str_create("test"); + assert(sc_str_len(s1) == 4); + assert(sc_str_cmp(s1, s2)); + sc_str_set(&s2, "tett"); + assert(sc_str_cmp(s1, s2) == false); + assert(sc_str_set_fmt(&s1, "test%d", 3) == true); + assert(strcmp(s1, "test3") == 0); + fail_malloc = true; + assert(sc_str_set_fmt(&s1, "test%d", 5) == false); + assert(strcmp(s1, "test3") == 0); + fail_malloc = false; + + sc_str_destroy(s1); + sc_str_destroy(s2); + + fail_malloc = true; + assert(sc_str_create("test") == NULL); + fail_malloc = false; + + s1 = malloc(SC_SIZE_MAX + 2); + memset(s1, 'c', SC_SIZE_MAX + 1); + s1[SC_SIZE_MAX + 1] = '\0'; + assert(sc_str_create(s1) == NULL); + free(s1); + + s1 = sc_str_create_fmt("%dtest%d", 5, 5); + assert(strcmp(s1, "5test5") == 0); + s2 = sc_str_dup(s1); + assert(sc_str_cmp(s1, s2) == true); + sc_str_destroy(s1); + sc_str_destroy(s2); + + fail_malloc = true; + s1 = sc_str_create_fmt("%dtest%d", 5, 5); + assert(s1 == NULL); + fail_malloc = false; + + s1 = sc_str_create_len("test", 4); + assert(strcmp(s1, "test") == 0); + assert(sc_str_len(s1) == 4); + assert(sc_str_set(&s1, "testtest") == true); + assert(strcmp(s1, "testtest") == 0); + assert(sc_str_len(s1) == 8); + fail_malloc = true; + fail_realloc = true; + assert(sc_str_set(&s1, "test") == false); + assert(strcmp(s1, "testtest") == 0); + assert(sc_str_len(s1) == 8); + assert(sc_str_append(&s1, "test") == false); + assert(sc_str_append_fmt(&s1, "%s", "test") == false); + assert(strcmp(s1, "testtest") == 0); + assert(sc_str_len(s1) == 8); + fail_malloc = false; + fail_realloc = false; + + sc_str_set(&s1, "text"); + sc_str_append(&s1, "2"); + sc_str_append_fmt(&s1, "%d", 3); + assert(strcmp(s1, "text23") == 0); + sc_str_set(&s1, " \n\n;;;;*test ;------;"); + sc_str_trim(&s1, " \n;*-"); + assert(strcmp(s1, "test") == 0); + sc_str_substring(&s1, 2, 4); + assert(strcmp(s1, "st") == 0); + assert(sc_str_substring(&s1, 4, 5) == false); + assert(sc_str_substring(&s1, 1, 5) == false); + + sc_str_set(&s1, "test"); + fail_malloc = true; + assert(sc_str_substring(&s1, 1, 2) == false); + fail_malloc = false; + + fail_vsnprintf = true; + assert(sc_str_set_fmt(&s1, "test%d", 3) == false); + assert(strcmp(s1, "test") == 0); + fail_vsnprintf = false; + + + fail_vsnprintf_at = 2; + s2 = malloc(2000 + 2); + memset(s2, 'c', 2000 + 1); + s2[2000 + 1] = '\0'; + assert(sc_str_set_fmt(&s1, "test%s", s2) == false); + assert(strcmp(s1, "test") == 0); + fail_vsnprintf_at = -1; + free(s2); + sc_str_destroy(s1); + + fail_malloc = false; + fail_realloc = false; + fail_vsnprintf = false; +} + +void test2() +{ + char buf[4000]; + memset(buf, 'x', 4000); + buf[3999] = '\0'; + + char *c = sc_str_create("--a**00"); + fail_malloc = true; + assert(sc_str_trim(&c, "-*0") == false); + fail_malloc = false; + sc_str_trim(&c, "-*0"); + assert(*c == 'a'); + sc_str_trim(&c, "a"); + assert(*c == '\0'); + + sc_str_set(&c, "x003"); + sc_str_trim(&c, "03"); + assert(strcmp(c, "x") == 0); + sc_str_set(&c, "\n\r\nx"); + sc_str_trim(&c, "\n\r"); + assert(strcmp(c, "x") == 0); + + sc_str_set(&c, "test****"); + fail_malloc = true; + assert(sc_str_replace(&c, "*", "-")); + assert(strcmp(c, "test----") == 0); + assert(!sc_str_replace(&c, "-", "")); + assert(strcmp(c, "test----") == 0); + fail_malloc = false; + assert(!sc_str_replace(&c, "----", buf)); + assert(strcmp(c, "test----") == 0); + sc_str_replace(&c, "--", "0"); + assert(strcmp(c, "test00") == 0); + sc_str_destroy(c); +} + +#endif + +void test3() +{ + const char *tokens = "token;token;token;token"; + char *save = NULL; + const char *token; + char *str = sc_str_create(tokens); + int count = 0; + + while ((token = sc_str_token_begin(str, &save, ";")) != NULL) { + assert(strcmp(token, "token") == 0); + count++; + } + + assert(count == 4); + assert(strcmp(str, tokens) == 0); + assert(sc_str_len(str) == strlen(tokens)); + + count = 0; + save = NULL; + while ((token = sc_str_token_begin(str, &save, ";")) != NULL) { + assert(strcmp(token, "token") == 0); + count++; + } + sc_str_token_end(str, &save); + + assert(count == 4); + assert(strcmp(str, tokens) == 0); + assert(sc_str_len(str) == strlen(tokens)); + + count = 0; + save = NULL; + while ((token = sc_str_token_begin(str, &save, ";")) != NULL) { + assert(strcmp(token, "token") == 0); + count++; + if (count == 4) { + break; + } + } + sc_str_token_end(str, &save); + + assert(count == 4); + assert(strcmp(str, tokens) == 0); + assert(sc_str_len(str) == strlen(tokens)); + sc_str_destroy(str); + + +} + +void test4() +{ + char *save = NULL; + const char *token; + char *str; + char list[10][10]; + int count = 0; + + str = sc_str_create(";;;"); + while ((token = sc_str_token_begin(str, &save, ";")) != NULL) { + assert(strcmp(token, "") == 0); + count++; + } + assert(count == 4); + + save = NULL; + count = 0; + sc_str_set(&str, "tk1;tk2-tk3 tk4 tk5*tk6"); + while ((token = sc_str_token_begin(str, &save, ";- *")) != NULL) { + strcpy(list[count], token); + count++; + } + + assert(count == 7); + assert(strcmp(list[0], "tk1") == 0); + assert(strcmp(list[1], "tk2") == 0); + assert(strcmp(list[2], "tk3") == 0); + assert(strcmp(list[3], "tk4") == 0); + assert(strcmp(list[4], "") == 0); + assert(strcmp(list[5], "tk5") == 0); + assert(strcmp(list[6], "tk6") == 0); + + sc_str_token_end(str, &save); + + + save = NULL; + count = 0; + sc_str_set(&str, "tk1;tk2-tk3 tk4 tk5*tk6"); + while ((token = sc_str_token_begin(str, &save, ";- *")) != NULL) { + strcpy(list[count], token); + count++; + if (count == 3) { + break; + } + } + + sc_str_token_end(str, &save); + assert(strcmp(str, "tk1;tk2-tk3 tk4 tk5*tk6") == 0); + + save = NULL; + count = 0; + sc_str_set(&str, "tk1;tk2-tk3 tk4 tk5*tk6"); + while ((token = sc_str_token_begin(str, &save, ";- *")) != NULL) { + strcpy(list[count], token); + count++; + if (count == 3) { + break; + } + } + + sc_str_token_end(str, NULL); + assert(strcmp(str, "tk1;tk2-tk3 tk4 tk5*tk6") == 0); + + sc_str_destroy(str); +} + +void test5() +{ + char* s1, *s2; + + s1 = sc_str_create("test"); + assert(strcmp(s1, "test") == 0); + assert(sc_str_len(s1) == 4); + s2 = sc_str_dup(s1); + assert(strcmp(s2, "test") == 0); + assert(sc_str_len(s2) == 4); + assert(strcmp(s1, s2) == 0); + sc_str_destroy(s2); + + sc_str_set(&s1, "test2"); + assert(strcmp(s1, "test2") == 0); + sc_str_set_fmt(&s1, "test%d", 3); + assert(strcmp(s1, "test3") == 0); + sc_str_append(&s1, "5"); + assert(strcmp(s1, "test35") == 0); + sc_str_append_fmt(&s1, "%d", 7); + assert(strcmp(s1, "test357") == 0); + sc_str_substring(&s1, 0, 4); + assert(strcmp(s1, "test") == 0); + sc_str_trim(&s1, "tes"); + assert(strcmp(s1, "") == 0); + sc_str_set_fmt(&s1, "-;;;- \n \n \n 351234"); + sc_str_trim(&s1, "-; 34\n"); + assert(strcmp(s1, "512") == 0); + + sc_str_set(&s1, "test t1t1 test"); + sc_str_replace(&s1, "t1t1", "-"); + assert(strcmp(s1, "test - test") == 0); + sc_str_replace(&s1, "test", "longer"); + assert(strcmp(s1, "longer - longer") == 0); + sc_str_replace(&s1, "-", ""); + sc_str_replace(&s1, " ", ""); + assert(strcmp(s1, "longerlonger") == 0); + assert(sc_str_len(s1) == strlen("longerlonger")); + assert(sc_str_replace(&s1, "as", "r")); + assert(strcmp(s1, "longerlonger") == 0); + assert(sc_str_replace(&s1, "r", "R")); + assert(strcmp(s1, "longeRlongeR") == 0); + assert(sc_str_replace(&s1, "longeR", "")); + assert(strcmp(s1, "") == 0); + sc_str_destroy(s1); +} + + +int main(int argc, char *argv[]) +{ + +#ifdef SC_HAVE_WRAP + test1(); + test2(); +#endif + test3(); + test4(); + test5(); + return 0; +} diff --git a/time/CMakeLists.txt b/time/CMakeLists.txt new file mode 100644 index 0000000..4688a6c --- /dev/null +++ b/time/CMakeLists.txt @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_time C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(sc_time time_example.c sc_time.h sc_time.c) + +if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror -D_GNU_SOURCE") +endif () + + +# --------------------------------------------------------------------------- # +# --------------------- Test Configuration Start ---------------------------- # +# --------------------------------------------------------------------------- # + +include(CTest) +include(CheckCCompilerFlag) + +enable_testing() + +add_executable(${PROJECT_NAME}_test time_test.c sc_time.c) + +if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer) + + if (SANITIZER) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + endif () +endif () + +add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test) + +SET(MEMORYCHECK_COMMAND_OPTIONS + "-q --log-fd=2 --trace-children=yes --track-origins=yes \ + --leak-check=full --show-leak-kinds=all --show-reachable=yes \ + --error-exitcode=255") + +add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ + --overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS} + --verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ --verbose + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +# ----------------------- - Code Coverage Start ----------------------------- # + +if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage) + target_link_libraries(${PROJECT_NAME}_test gcov) + else() + message(FATAL_ERROR "Only GCC is supported for coverage") + endif() +endif () + +add_custom_target(coverage_${PROJECT_NAME}) +add_custom_command( + TARGET coverage_${PROJECT_NAME} + COMMAND lcov --capture --directory .. + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*' + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1 +) + +add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME}) + +# -------------------------- Code Coverage End ------------------------------ # + + +# ----------------------- Test Configuration End ---------------------------- # + diff --git a/time/README.md b/time/README.md new file mode 100644 index 0000000..3d3ce75 --- /dev/null +++ b/time/README.md @@ -0,0 +1,6 @@ +# Time and sleep functions + +#### Overview + +- Time and sleep functions for Posix and Windows systems. +- Just copy sc_time.h and sc_time.c to your project. diff --git a/time/sc_time.c b/time/sc_time.c new file mode 100644 index 0000000..b09bbf8 --- /dev/null +++ b/time/sc_time.c @@ -0,0 +1,147 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "sc_time.h" + +#if defined(_WIN32) || defined(_WIN64) + #include + #include +#else + #include + #include + #include + #include +#endif + +uint64_t sc_time_ms() +{ +#if defined(_WIN32) || defined(_WIN64) + FILETIME ft; + ULARGE_INTEGER dateTime; + + GetSystemTimeAsFileTime(&ft); + dateTime.LowPart = ft.dwLowDateTime; + dateTime.HighPart = ft.dwHighDateTime; + + return (dateTime.QuadPart / 10000); +#else + int rc; + struct timespec ts; + + rc = clock_gettime(CLOCK_REALTIME, &ts); + assert(rc == 0); + + return ts.tv_sec * 1000 + (uint64_t)(ts.tv_nsec / 10e6); +#endif +} + +uint64_t sc_time_ns() +{ +#if defined(_WIN32) || defined(_WIN64) + FILETIME ft; + ULARGE_INTEGER dateTime; + + GetSystemTimeAsFileTime(&ft); + dateTime.LowPart = ft.dwLowDateTime; + dateTime.HighPart = ft.dwHighDateTime; + + return (dateTime.QuadPart * 100); +#else + int rc; + struct timespec ts; + + rc = clock_gettime(CLOCK_REALTIME, &ts); + assert(rc == 0); + + return ts.tv_sec * 1000000000 + ts.tv_nsec; +#endif +} + +uint64_t sc_time_mono_ms() +{ +#if defined(_WIN32) || defined(_WIN64) + // System frequency does not change at run-time, cache it + static int64_t frequency = 0; + if (frequency == 0) { + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + assert(freq.QuadPart != 0); + frequency = freq.QuadPart; + } + LARGE_INTEGER count; + QueryPerformanceCounter(&count); + return (int64_t)(count.QuadPart * 1000) / frequency; +#else + int rc; + struct timespec ts; + + rc = clock_gettime(CLOCK_MONOTONIC, &ts); + assert(rc == 0); + + return (int64_t)((int64_t) ts.tv_sec * 1000 + + (int64_t) ts.tv_nsec / 1000000); +#endif +} + +uint64_t sc_time_mono_ns() +{ +#if defined(_WIN32) || defined(_WIN64) + static int64_t frequency = 0; + if (frequency == 0) { + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + assert(freq.QuadPart != 0); + frequency = freq.QuadPart; + } + LARGE_INTEGER count; + QueryPerformanceCounter(&count); + return (uint64_t)(count.QuadPart * 1000000000) / frequency; +#else + int rc; + struct timespec ts; + + rc = clock_gettime(CLOCK_MONOTONIC, &ts); + assert(rc == 0); + + return ((uint64_t) ts.tv_sec * 1000000000 + (uint64_t) ts.tv_nsec); +#endif +} + +void sc_time_sleep(uint64_t milliseconds) +{ +#if defined(_WIN32) || defined(_WIN64) + Sleep(milliseconds); +#else + int rc; + struct timespec t; + + t.tv_sec = milliseconds / 1000; + t.tv_nsec = (milliseconds % 1000) * 1000000; + + do { + rc = nanosleep(&t, NULL); + } while (rc != 0 && errno != EINTR); + + assert(rc == 0); +#endif +} diff --git a/time/sc_time.h b/time/sc_time.h new file mode 100644 index 0000000..d50a92b --- /dev/null +++ b/time/sc_time.h @@ -0,0 +1,58 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef SC_TIME_H +#define SC_TIME_H + +#include + +/** + * This is not a monotonic timer. + * @return Current timestamp in milliseconds. + */ +uint64_t sc_time_ms(); + +/** + * This is not a monotonic timer. + * @return Current timestamp in nanoseconds. + */ +uint64_t sc_time_ns(); + +/** + * Monotonic timer. + * @return Current timestamp in milliseconds. + */ +uint64_t sc_time_mono_ms(); + +/** + * Monotonic timer. + * @return Current timestamp in nanoseconds. + */ +uint64_t sc_time_mono_ns(); + +/** + * @param milliseconds Milliseconds to sleep. + */ +void sc_time_sleep(uint64_t milliseconds); + +#endif diff --git a/time/time_example.c b/time/time_example.c new file mode 100644 index 0000000..714985c --- /dev/null +++ b/time/time_example.c @@ -0,0 +1,11 @@ +#include "sc_time.h" +#include + +int main(int argc, char *argv[]) +{ + uint64_t t = sc_time_ms(); + sc_time_sleep(1000); + printf("%lu \n", (unsigned long) (sc_time_ms() - t)); + + return 0; +} diff --git a/time/time_test.c b/time/time_test.c new file mode 100644 index 0000000..e0297a7 --- /dev/null +++ b/time/time_test.c @@ -0,0 +1,47 @@ +#include "sc_time.h" + +#include + +void test1(void) +{ + assert(sc_time_ns() != 0); + assert(sc_time_ms() != 0); + + for (int i = 0; i < 100000; i++) { + uint64_t t1 = sc_time_mono_ms(); + uint64_t t2 = sc_time_mono_ms(); + + assert(t2 >= t1); + } + + for (int i = 0; i < 100000; i++) { + uint64_t t1 = sc_time_mono_ns(); + uint64_t t2 = sc_time_mono_ns(); + + assert(t2 >= t1); + } +} + +void test2(void) +{ + uint64_t t1, t2; + + t1 = sc_time_mono_ms(); + sc_time_sleep(1000); + t2 = sc_time_mono_ms(); + + assert(t2 > t1); + + t1 = sc_time_mono_ns(); + sc_time_sleep(1000); + t2 = sc_time_mono_ns(); + + assert(t2 > t1); +} + +int main(int argc, char *argv[]) +{ + test1(); + test2(); + return 0; +} diff --git a/timer/CMakeLists.txt b/timer/CMakeLists.txt new file mode 100644 index 0000000..a1ce6b2 --- /dev/null +++ b/timer/CMakeLists.txt @@ -0,0 +1,96 @@ +cmake_minimum_required(VERSION 3.5.1) +project(sc_timer C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(sc_timer timer_example.c sc_timer.h sc_timer.c) + +if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -g -pedantic -Werror -D_GNU_SOURCE") +endif () + + +# --------------------------------------------------------------------------- # +# --------------------- Test Configuration Start ---------------------------- # +# --------------------------------------------------------------------------- # + +include(CTest) +include(CheckCCompilerFlag) + +enable_testing() + +add_executable(${PROJECT_NAME}_test timer_test.c sc_timer.c) + +target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=1400000ul) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin) + target_link_options(${PROJECT_NAME}_test PRIVATE -Wl,--wrap=malloc) + + endif () +endif () + +if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR + "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + + target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer) + + if (SANITIZER) + target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER}) + endif () +endif () + +add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test) + +SET(MEMORYCHECK_COMMAND_OPTIONS + "-q --log-fd=2 --trace-children=yes --track-origins=yes \ + --leak-check=full --show-leak-kinds=all --show-reachable=yes \ + --error-exitcode=255") + +add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ + --overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS} + --verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND} + -E env CTEST_OUTPUT_ON_FAILURE=1 + ${CMAKE_CTEST_COMMAND} -C $ --verbose + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +# ----------------------- - Code Coverage Start ----------------------------- # + +if (${CMAKE_BUILD_TYPE} MATCHES "Coverage") + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage) + target_link_libraries(${PROJECT_NAME}_test gcov) + else() + message(FATAL_ERROR "Only GCC is supported for coverage") + endif() +endif () + +add_custom_target(coverage_${PROJECT_NAME}) +add_custom_command( + TARGET coverage_${PROJECT_NAME} + COMMAND lcov --capture --directory .. + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*' + --output-file coverage.info --rc lcov_branch_coverage=1 + COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1 +) + +add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME}) + +# -------------------------- Code Coverage End ------------------------------ # + + +# ----------------------- Test Configuration End ---------------------------- # + diff --git a/timer/README.md b/timer/README.md new file mode 100644 index 0000000..83c296e --- /dev/null +++ b/timer/README.md @@ -0,0 +1,110 @@ +# Timer + +#### Overview + +- Hashed timer wheel implementation. +- Provides fast cancel and poll operations compared to a priority queue. +- Timers in the same hash slot are not ordered between each other. So, basically + this data structure trades accuracy for performance. Schedule a timer for + 10000ms and another for 10001ms and you might see 10001ms timer expires + just before 10000ms timer. +- Just copy sc_timer.h and sc_timer.c to your project. + + +##### Usage + + +```c +#include "sc_timer.h" + +#include +#include +#include + +uint64_t time_ms(); +void sleep_ms(uint64_t milliseconds); + + +void callback(void *arg, uint64_t timeout, void *data) +{ + struct sc_timer *timer = arg; + char *timer_name = data; + + printf("timeout : %zu, data : %s \n", timeout, timer_name); + // Schedule back + sc_timer_add(timer, "timer1", 1000); +} + +int main(int argc, char *argv[]) +{ + uint64_t next_timeout; + struct sc_timer timer; + + sc_timer_init(&timer, time_ms()); + sc_timer_add(&timer, "timer1", 1000); + + while (true) { + next_timeout = sc_timer_timeout(&timer, time_ms(), &timer, callback); + sleep_ms(next_timeout); + } + + return 0; +} + +/** + * + * sleep() and time() functions for Windows and Posix. + * These functions are here just to make this example run on your platform. + * + * Use your monotonic timer and sleep function of your target platform. + * + */ + +#if defined(_WIN32) || defined(_WIN64) + #include +#else + #include +#endif + +uint64_t time_ms() +{ +#if defined(_WIN32) || defined(_WIN64) + // System frequency does not change at run-time, cache it + static int64_t frequency = 0; + if (frequency == 0) { + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + assert(freq.QuadPart != 0); + frequency = freq.QuadPart; + } + LARGE_INTEGER count; + QueryPerformanceCounter(&count); + return (int64_t)(count.QuadPart * 1000) / frequency; +#else + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + return (int64_t)((int64_t) ts.tv_sec * 1000 + + (int64_t) ts.tv_nsec / 1000000); +#endif +} + +void sleep_ms(uint64_t milliseconds) +{ +#if defined(_WIN32) || defined(_WIN64) + Sleep(milliseconds); +#else + int rc; + struct timespec t; + + t.tv_sec = milliseconds / 1000; + t.tv_nsec = (milliseconds % 1000) * 1000000; + + do { + rc = nanosleep(&t, NULL); + } while (rc != 0 && errno != EINTR); +#endif +} + +``` \ No newline at end of file diff --git a/timer/sc_timer.c b/timer/sc_timer.c new file mode 100644 index 0000000..33d6611 --- /dev/null +++ b/timer/sc_timer.c @@ -0,0 +1,214 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "sc_timer.h" + +#include +#include +#include + +#define TICK 16u +#define WHEEL_COUNT 16u + +#ifndef SC_SIZE_MAX + #define SC_SIZE_MAX UINT32_MAX +#endif + +#define SC_CAP_MAX (SC_SIZE_MAX / sizeof(struct sc_timer_data)) / WHEEL_COUNT + +bool sc_timer_init(struct sc_timer *timer, uint64_t timestamp) +{ + const size_t wheel_cap = 4; + const size_t cap = WHEEL_COUNT * wheel_cap; + const size_t size = cap * sizeof(struct sc_timer_data); + + timer->count = 0; + timer->head = 0; + timer->wheel = wheel_cap; + timer->timestamp = timestamp; + + timer->list = sc_timer_malloc(size); + if (timer->list == NULL) { + return false; + } + + for (size_t i = 0; i < cap; i++) { + timer->list[i].timeout = UINT64_MAX; + timer->list[i].data = NULL; + } + + return true; +} + +void sc_timer_term(struct sc_timer *timer) +{ + sc_timer_free(timer->list); +} + +void sc_timer_clear(struct sc_timer *timer) +{ + const size_t cap = timer->wheel * WHEEL_COUNT; + + timer->count = 0; + timer->head = 0; + + for (size_t i = 0; i < cap; i++) { + timer->list[i].timeout = UINT64_MAX; + timer->list[i].data = NULL; + } +} + +static bool expand(struct sc_timer *timer) +{ + size_t cap = timer->wheel * WHEEL_COUNT * 2; + size_t size = cap * sizeof(struct sc_timer_data); + struct sc_timer_data *alloc; + + // Check overflow + if (timer->wheel > SC_CAP_MAX / 2) { + return false; + } + + alloc = sc_timer_malloc(size); + if (alloc == NULL) { + return false; + } + + for (size_t i = 0; i < cap; i++) { + alloc[i].timeout = UINT64_MAX; + alloc[i].data = NULL; + } + + // Copy from old list to new list + for (uint32_t i = 0; i < WHEEL_COUNT; i++) { + void *dest = &alloc[(i * timer->wheel * 2)]; + void *src = &timer->list[(i * timer->wheel)]; + size_t copy = sizeof(struct sc_timer_data) * timer->wheel; + + memcpy(dest, src, copy); + } + + sc_timer_free(timer->list); + + timer->list = alloc; + timer->wheel *= 2; + + return true; +} + +uint64_t sc_timer_add(struct sc_timer *timer, void *data, uint64_t timeout) +{ + const size_t pos = (timeout / TICK + timer->head) & (WHEEL_COUNT - 1); + uint64_t id; + size_t seq, index, wheel_pos; + + assert(timeout < UINT64_MAX); + + timer->count++; + + wheel_pos = (pos * timer->wheel); + for (seq = 0; seq < timer->wheel; seq++) { + index = wheel_pos + seq; + if (timer->list[index].timeout == UINT64_MAX) { + goto out; + } + } + + if (!expand(timer)) { + return SC_TIMER_INVALID; + } + + index = (pos * timer->wheel) + (seq); + assert(timer->list[index].timeout == UINT64_MAX); + +out: + timer->list[index].timeout = timeout + timer->timestamp; + timer->list[index].data = data; + + id = (((uint64_t) seq) << 32u) | pos; + assert(id != SC_TIMER_INVALID); + + return id; +} + +void sc_timer_cancel(struct sc_timer *timer, uint64_t *id) +{ + size_t pos; + + if (*id == SC_TIMER_INVALID) { + return; + } + + timer->count--; + pos = (((uint32_t) *id) * timer->wheel) + (*id >> 32u); + + assert(timer->list[pos].timeout != UINT64_MAX); + timer->list[pos].timeout = UINT64_MAX; + *id = SC_TIMER_INVALID; +} + +uint64_t sc_timer_timeout(struct sc_timer *timer, uint64_t timestamp, void *arg, + void (*callback)(void *, uint64_t, void *)) +{ +#define min(a, b) (a) < (b) ? (a) : (b) + + const uint64_t time = timestamp - timer->timestamp; + uint32_t wheel, base; + uint32_t head = timer->head; + uint32_t wheels = min(time / TICK, WHEEL_COUNT); + + if (wheels == 0) { + return min(TICK - time, TICK); + } + + timer->timestamp = timestamp; + timer->head = (timer->head + wheels) & (WHEEL_COUNT - 1); + + while (wheels-- > 0) { + wheel = timer->wheel; + base = wheel * head; + + for (uint32_t i = 0; i < wheel; i++) { + struct sc_timer_data *item = &timer->list[base + i]; + + if (item->timeout <= timer->timestamp) { + uint64_t timeout = item->timeout; + item->timeout = UINT64_MAX; + + timer->count--; + callback(arg, timeout, item->data); + + // Recalculates position each time because there might be newly + // added timers in the callback and it might require expansion + // of the list. + base = timer->wheel * head; + } + } + + head = (head + 1) & (WHEEL_COUNT - 1); + } + + return min(TICK - time, TICK); +} + diff --git a/timer/sc_timer.h b/timer/sc_timer.h new file mode 100644 index 0000000..a75c6f6 --- /dev/null +++ b/timer/sc_timer.h @@ -0,0 +1,125 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ozan Tezcan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SC_TIMER_H +#define SC_TIMER_H + +#include +#include +#include +#include + +#define SC_TIMER_INVALID UINT64_MAX + +struct sc_timer_data +{ + uint64_t timeout; + void *data; +}; + +struct sc_timer +{ + uint64_t timestamp; + size_t head; + size_t wheel; + size_t count; + struct sc_timer_data *list; +}; + +#define sc_timer_malloc malloc +#define sc_timer_free free + +/** + * @param timer Timer + * @param timestamp Current timestamp. Use monotonic timer source. + * @return 'false' on out of memory. + */ +bool sc_timer_init(struct sc_timer *timer, uint64_t timestamp); + +/** + * Destroy timer. + * @param timer Timer + */ +void sc_timer_term(struct sc_timer *timer); + +/** + * Remove all timers without deallocating underlying memory. + * @param timer + */ +void sc_timer_clear(struct sc_timer *timer); + +/** + * Add timer + * 'timeout' is relative to latest 'timestamp' value given to 'timer' object. + * + * e.g sc_timer_init(&timer, 1000); // Current timestamp is 1000. + * sc_timer_add(&timer, arg, 10); // Timeout will be at 1010. + * sc_timer_timeout(&timer, 2000, arg, callback); // Timestamp is now 2000. + * sc_timer_add(&timer, arg, 10); // Timeout will be at 2010. + * + * + * @param timer Timer + * @param data User data to pass into callback on 'sc_timer_timeout' call. + * @param timeout Timeout value, this is relative to 'sc_timer_init's timer. + * e.g sc_timer_init(&timer, 10); // say, start time 10 milliseconds + * @return SC_TIMER_INVALID on out of memory. Otherwise, timer id. You + * can cancel this timer via this id. + */ +uint64_t sc_timer_add(struct sc_timer *timer, void *data, uint64_t timeout); + +/** + * uint64_t id = sc_timer_add(&timer, arg, 10); + * sc_timer_cancel(&timer, &id); + * + * @param timer Timer + * @param id Timer id + */ +void sc_timer_cancel(struct sc_timer *timer, uint64_t *id); + +/** + * Logical pattern is : + * + * e.g: + * struct sc_timer timer; + * sc_timer_init(&timer, time_ms()); + * sc_timer_add(&timer, data, 100); + * + * while (true) { + * uint64_t timeout = sc_timer_timeout(&timer, time_ms(), arg, callback); + * sleep(timeout); // select(timeout), epoll_wait(timeout) etc.. + * } + * + * + * @param timer Timer + * @param timestamp Current timestamp + * @param arg User data to user callback + * @param callback 'arg' is user data. + * 'timeout' is scheduled timeout for that timer. + * 'data' is what user passed on 'sc_timer_add'. + * @return next timeout. + */ +uint64_t sc_timer_timeout(struct sc_timer *timer, uint64_t timestamp, void *arg, + void (*callback)(void *arg, uint64_t timeout, + void *data)); +#endif diff --git a/timer/timer_example.c b/timer/timer_example.c new file mode 100644 index 0000000..4269669 --- /dev/null +++ b/timer/timer_example.c @@ -0,0 +1,82 @@ +#include "sc_timer.h" + +#include +#include +#include + +uint64_t time_ms(); +void sleep_ms(uint64_t milliseconds); + + +void callback(void *arg, uint64_t timeout, void *data) +{ + struct sc_timer *timer = arg; + char *timer_name = data; + + printf("timeout : %lu, data : %s \n", (unsigned long) timeout, timer_name); + // Schedule back + sc_timer_add(timer, "timer1", 1000); +} + +int main(int argc, char *argv[]) +{ + uint64_t next_timeout; + struct sc_timer timer; + + sc_timer_init(&timer, time_ms()); + sc_timer_add(&timer, "timer1", 1000); + + while (true) { + next_timeout = sc_timer_timeout(&timer, time_ms(), &timer, callback); + sleep_ms(next_timeout); + } + + return 0; +} + + +#if defined(_WIN32) || defined(_WIN64) + #include +#else + #include +#endif + +uint64_t time_ms() +{ +#if defined(_WIN32) || defined(_WIN64) + // System frequency does not change at run-time, cache it + static int64_t frequency = 0; + if (frequency == 0) { + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + frequency = freq.QuadPart; + } + LARGE_INTEGER count; + QueryPerformanceCounter(&count); + return (int64_t)(count.QuadPart * 1000) / frequency; +#else + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + return (int64_t)((int64_t) ts.tv_sec * 1000 + + (int64_t) ts.tv_nsec / 1000000); +#endif +} + +void sleep_ms(uint64_t milliseconds) +{ +#if defined(_WIN32) || defined(_WIN64) + Sleep(milliseconds); +#else + int rc; + struct timespec t; + + t.tv_sec = milliseconds / 1000; + t.tv_nsec = (milliseconds % 1000) * 1000000; + + do { + rc = nanosleep(&t, NULL); + } while (rc != 0 && errno != EINTR); +#endif +} diff --git a/timer/timer_test.c b/timer/timer_test.c new file mode 100644 index 0000000..578a808 --- /dev/null +++ b/timer/timer_test.c @@ -0,0 +1,257 @@ +#include "sc_timer.h" + +#include +#include +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) + #include +#else + #include + #include +#endif + +uint64_t time_ms() +{ +#if defined(_WIN32) || defined(_WIN64) + // System frequency does not change at run-time, cache it + static int64_t frequency = 0; + if (frequency == 0) { + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + assert(freq.QuadPart != 0); + frequency = freq.QuadPart; + } + LARGE_INTEGER count; + QueryPerformanceCounter(&count); + return (int64_t)(count.QuadPart * 1000) / frequency; +#else + int rc; + struct timespec ts; + + rc = clock_gettime(CLOCK_MONOTONIC, &ts); + assert(rc == 0); + + return (int64_t)((int64_t) ts.tv_sec * 1000 + + (int64_t) ts.tv_nsec / 1000000); +#endif +} + +void sleep_ms(uint64_t milliseconds) +{ +#if defined(_WIN32) || defined(_WIN64) + Sleep(milliseconds); +#else + int rc; + struct timespec t; + + t.tv_sec = milliseconds / 1000; + t.tv_nsec = (milliseconds % 1000) * 1000000; + + do { + rc = nanosleep(&t, NULL); + } while (rc != 0 && errno != EINTR); +#endif +} + +uint64_t ids[1000]; + +void callback(void *arg, uint64_t timeout, void *data) +{ + static int idx = 0; + + uint64_t id = (uintptr_t) data; + assert(ids[id] != SC_TIMER_INVALID); + ids[id] = SC_TIMER_INVALID; + assert((int) (uintptr_t) arg == 333); + + idx++; +} + +void test1(void) +{ + struct sc_timer timer; + + assert(sc_timer_init(&timer, time_ms())); + for (int i = 0; i < 1000; i++) { + ids[i] = sc_timer_add(&timer, (void *) (uintptr_t) i, rand() % 100); + assert(ids[i] != SC_TIMER_INVALID); + } + + int t = 10000; + uint32_t n; + while (t > 0) { + n = sc_timer_timeout(&timer, time_ms(), (void *) (uintptr_t) 333, + callback); + if (timer.count == 0) { + break; + } + t -= n; + sleep_ms(n); + } + + for (int i = 0; i < 1000; i++) { + assert(ids[i] == SC_TIMER_INVALID); + } + + sc_timer_term(&timer); +} + +void test2(void) +{ + struct sc_timer timer; + + assert(sc_timer_init(&timer, time_ms())); + for (int i = 0; i < 1000; i++) { + ids[i] = SC_TIMER_INVALID; + sc_timer_add(&timer, (void *) (uintptr_t) i, rand() % 100); + } + + sc_timer_clear(&timer); + + int t = 10000; + uint32_t n; + while (t > 0) { + n = sc_timer_timeout(&timer, time_ms(), (void *) (uintptr_t) 333, + callback); + if (timer.count == 0) { + break; + } + + t -= n; + sleep_ms(n); + } + + for (int i = 0; i < 1000; i++) { + assert(ids[i] == SC_TIMER_INVALID); + } + + for (int i = 0; i < 1000; i++) { + ids[i] = sc_timer_add(&timer, (void *) (uintptr_t) i, rand() % 100); + assert(ids[i] != SC_TIMER_INVALID); + } + + t = 10000; + while (t > 0) { + n = sc_timer_timeout(&timer, time_ms(), (void *) (uintptr_t) 333, + callback); + if (timer.count == 0) { + break; + } + t -= n; + sleep_ms(n); + } + + for (int i = 0; i < 1000; i++) { + assert(ids[i] == SC_TIMER_INVALID); + } + + + sc_timer_term(&timer); +} + + +void test3(void) +{ + struct sc_timer timer; + + assert(sc_timer_init(&timer, time_ms())); + for (int i = 0; i < 1000; i++) { + ids[i] = sc_timer_add(&timer, (void *) (uintptr_t) i, rand() % 20); + assert(ids[i] != SC_TIMER_INVALID); + } + + for (int i = 0; i < 1000; i += 2) { + sc_timer_cancel(&timer, &ids[i]); + } + + assert(timer.count == 500); + sc_timer_cancel(&timer, &ids[0]); + assert(timer.count == 500); + + int t = 10000; + uint32_t n; + while (t > 0) { + n = sc_timer_timeout(&timer, time_ms(), (void *) (uintptr_t) 333, + callback); + if (timer.count == 0) { + break; + } + t -= n; + sleep_ms(n); + } + + for (int i = 0; i < 500; i++) { + assert(ids[i] == SC_TIMER_INVALID); + } + + sc_timer_term(&timer); +} + +#ifdef SC_HAVE_WRAP + +bool fail_malloc = false; +void *__real_malloc(size_t n); +void *__wrap_malloc(size_t n) +{ + if (fail_malloc) { + return NULL; + } + + return __real_malloc(n); +} + +void fail_test(void) +{ + size_t max = SC_SIZE_MAX / sizeof(struct sc_timer_data); + struct sc_timer timer; + + fail_malloc = true; + assert(sc_timer_init(&timer, time_ms()) == false); + fail_malloc = false; + assert(sc_timer_init(&timer, time_ms()) == true); + + uint64_t id; + for (size_t i = 0; i < max + 100; i++) { + id = sc_timer_add(&timer, 0, i); + if (id == SC_TIMER_INVALID) { + break; + } + } + + assert(id == SC_TIMER_INVALID); + + sc_timer_term(&timer); + + sc_timer_init(&timer, time_ms()); + fail_malloc = true; + + for (size_t i = 0; i < 65; i++) { + id = sc_timer_add(&timer, 0, i); + if (id == SC_TIMER_INVALID) { + break; + } + } + + assert(id == SC_TIMER_INVALID); + fail_malloc = false; + + sc_timer_term(&timer); +} +#else +void fail_test(void) +{ +} +#endif + +int main(int argc, char *argv[]) +{ + fail_test(); + test1(); + test2(); + test3(); + + return 0; +}