thread & url (#3)

* thread & url

* fix cirrus

* fix thread

* fix thread

* split actions

* test

* fix test

* fix windows thread

* cond & fail-fast
This commit is contained in:
Tezc 2020-11-16 03:58:37 +03:00 committed by GitHub
parent e3d95ee816
commit 42383fd590
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 2059 additions and 458 deletions

View File

@ -1,6 +1,4 @@
freebsd_task: freebsd_task:
only_if: $CIRRUS_BRANCH == 'master'
use_compute_credits: $CIRRUS_USER_COLLABORATOR == 'true'
freebsd_instance: freebsd_instance:
image_family: freebsd-12-1 image_family: freebsd-12-1
cpu: 1 cpu: 1
@ -8,5 +6,5 @@ freebsd_task:
test_script: test_script:
- pkg install -y git cmake - pkg install -y git cmake
- mkdir build && cd build - mkdir build && cd build
- cmake -DSANITIZER=address .. && make -j && make check && rm -rf * - cmake -DSANITIZER=address .. && make && make check && rm -rf *
- cmake -DSANITIZER=undefined .. && make -j && make check && rm -rf * - cmake -DSANITIZER=undefined .. && make && make check && rm -rf *

34
.github/workflows/.aarch64.yml vendored Normal file
View File

@ -0,0 +1,34 @@
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
archs:
# The host should always be linux
runs-on: ubuntu-18.04
name: Build on aarch64
steps:
- uses: actions/checkout@v2.1.0
- uses: uraimo/run-on-arch-action@v2.0.7
name: Build artifact
id: build
with:
arch: aarch64
distro: ubuntu20.04
# Not required, but speeds up builds
githubToken: ${{ github.token }}
# The shell to run commands with in the container
shell: /bin/sh
# Produce a binary artifact and place it in the mounted volume
run: |
apt-get update -q -y
apt-get install -q -y build-essential git gcc valgrind cmake
uname -a;id;uname -m;lscpu | grep Endian
mkdir build && cd build
cmake .. && make -j && make check

View File

@ -1,152 +0,0 @@
on:
push:
branches:
- master
pull_request:
branches:
- master
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 }}

34
.github/workflows/.armv6.yml vendored Normal file
View File

@ -0,0 +1,34 @@
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
archs:
# The host should always be linux
runs-on: ubuntu-18.04
name: Build on armv6
steps:
- uses: actions/checkout@v2.1.0
- uses: uraimo/run-on-arch-action@v2.0.7
name: Build artifact
id: build
with:
arch: armv6
distro: buster
# Not required, but speeds up builds
githubToken: ${{ github.token }}
# The shell to run commands with in the container
shell: /bin/sh
# Produce a binary artifact and place it in the mounted volume
run: |
apt-get update -q -y
apt-get install -q -y build-essential git gcc valgrind cmake
uname -a;id;uname -m;lscpu | grep Endian
mkdir build && cd build
cmake .. && make -j && make check

34
.github/workflows/.armv7.yml vendored Normal file
View File

@ -0,0 +1,34 @@
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
archs:
# The host should always be linux
runs-on: ubuntu-18.04
name: Build on armv7
steps:
- uses: actions/checkout@v2.1.0
- uses: uraimo/run-on-arch-action@v2.0.7
name: Build artifact
id: build
with:
arch: armv7
distro: buster
# Not required, but speeds up builds
githubToken: ${{ github.token }}
# The shell to run commands with in the container
shell: /bin/sh
# Produce a binary artifact and place it in the mounted volume
run: |
apt-get update -q -y
apt-get install -q -y build-essential git gcc valgrind cmake
uname -a;id;uname -m;lscpu | grep Endian
mkdir build && cd build
cmake .. && make -j && make check

21
.github/workflows/.coverage.yml vendored Normal file
View File

@ -0,0 +1,21 @@
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
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 }}

31
.github/workflows/.macos.yml vendored Normal file
View File

@ -0,0 +1,31 @@
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
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

34
.github/workflows/.ppc64le.yml vendored Normal file
View File

@ -0,0 +1,34 @@
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
archs:
# The host should always be linux
runs-on: ubuntu-18.04
name: Build on ppc64le
steps:
- uses: actions/checkout@v2.1.0
- uses: uraimo/run-on-arch-action@v2.0.7
name: Build artifact
id: build
with:
arch: ppc64le
distro: ubuntu20.04
# Not required, but speeds up builds
githubToken: ${{ github.token }}
# The shell to run commands with in the container
shell: /bin/sh
# Produce a binary artifact and place it in the mounted volume
run: |
apt-get update -q -y
apt-get install -q -y build-essential git gcc valgrind cmake
uname -a;id;uname -m;lscpu | grep Endian
mkdir build && cd build
cmake .. && make -j && make check

35
.github/workflows/.s390x.yml vendored Normal file
View File

@ -0,0 +1,35 @@
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
archs:
# The host should always be linux
runs-on: ubuntu-18.04
name: Build on s390x
steps:
- uses: actions/checkout@v2.1.0
- uses: uraimo/run-on-arch-action@v2.0.7
name: Build artifact
id: build
with:
arch: s390x
distro: ubuntu20.04
# Not required, but speeds up builds
githubToken: ${{ github.token }}
# The shell to run commands with in the container
shell: /bin/sh
# Produce a binary artifact and place it in the mounted volume
run: |
apt-get update -q -y
apt-get install -q -y build-essential git gcc valgrind cmake
uname -a;id;uname -m;lscpu | grep Endian
mkdir build && cd build
cmake .. && make -j && make check

50
.github/workflows/.ubuntu.yml vendored Normal file
View File

@ -0,0 +1,50 @@
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
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
ubuntu-gcc-thread:
runs-on: ubuntu-latest
name: Thread sanitizer
steps:
- uses: actions/checkout@v2.1.0
- name: build
env:
CC: gcc
run: |
sudo apt-get install cmake
mkdir build-debug && cd build-debug
cmake .. -DSANITIZER=thread
make -j
make check

20
.github/workflows/.windows.yml vendored Normal file
View File

@ -0,0 +1,20 @@
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
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

View File

@ -12,6 +12,7 @@ endif ()
message(STATUS "Build type ${CMAKE_BUILD_TYPE}") message(STATUS "Build type ${CMAKE_BUILD_TYPE}")
add_subdirectory(array) add_subdirectory(array)
add_subdirectory(condition)
add_subdirectory(crc32) add_subdirectory(crc32)
add_subdirectory(heap) add_subdirectory(heap)
add_subdirectory(ini) add_subdirectory(ini)
@ -25,6 +26,7 @@ add_subdirectory(pipe)
add_subdirectory(string) add_subdirectory(string)
add_subdirectory(time) add_subdirectory(time)
add_subdirectory(timer) add_subdirectory(timer)
add_subdirectory(thread)
add_subdirectory(url) add_subdirectory(url)

View File

@ -41,6 +41,8 @@ static const struct sc_array sc_empty = {.size = 0, .cap = 0};
bool sc_array_init(void **arr, size_t elem_size, size_t cap) bool sc_array_init(void **arr, size_t elem_size, size_t cap)
{ {
const size_t max = SC_SIZE_MAX / elem_size;
size_t bytes;
struct sc_array *meta; struct sc_array *meta;
if (cap == 0) { if (cap == 0) {
@ -49,13 +51,16 @@ bool sc_array_init(void **arr, size_t elem_size, size_t cap)
} }
// Check overflow // Check overflow
if (cap > SC_SIZE_MAX / elem_size) { if (cap > max) {
sc_array_on_error("Max capacity(%zu) has been reached. ", max);
*arr = NULL; *arr = NULL;
return false; return false;
} }
meta = sc_array_realloc(NULL, sizeof(*meta) + (elem_size * cap)); bytes = sizeof(*meta) + (elem_size * cap);
meta = sc_array_realloc(NULL, bytes);
if (meta == NULL) { if (meta == NULL) {
sc_array_on_error("Failed to allocate %zu bytes. ", bytes);
*arr = NULL; *arr = NULL;
return false; return false;
} }
@ -80,13 +85,15 @@ void sc_array_term(void **arr)
bool sc_array_expand(void **arr, size_t elem_size) bool sc_array_expand(void **arr, size_t elem_size)
{ {
size_t size, cap; const size_t max = SC_SIZE_MAX / elem_size;
size_t size, cap, bytes;
struct sc_array *prev, *meta = sc_array_meta(*arr); struct sc_array *prev, *meta = sc_array_meta(*arr);
if (meta->size == meta->cap) { if (meta->size == meta->cap) {
// Check overflow // Check overflow
if (meta->cap > SC_SIZE_MAX / elem_size / 2) { if (meta->cap > max / 2) {
sc_array_on_error("Max capacity(%zu) has been reached. ", max / 2);
return false; return false;
} }
@ -94,8 +101,10 @@ bool sc_array_expand(void **arr, size_t elem_size)
cap = (meta != &sc_empty) ? meta->cap * 2 : 2; cap = (meta != &sc_empty) ? meta->cap * 2 : 2;
prev = (meta != &sc_empty) ? meta : NULL; prev = (meta != &sc_empty) ? meta : NULL;
meta = sc_array_realloc(prev, sizeof(*meta) + (elem_size * cap)); bytes = sizeof(*meta) + (elem_size * cap);
meta = sc_array_realloc(prev, bytes);
if (meta == NULL) { if (meta == NULL) {
sc_array_on_error("Failed to allocate %zu bytes. ", bytes);
return false; return false;
} }

View File

@ -54,6 +54,14 @@ bool sc_array_expand(void **arr, size_t elem_size);
*/ */
/**
* If you want to log or abort on errors like out of memory,
* put your error function here. It will be called with printf like error msg.
*
* my_on_error(const char* fmt, ...);
*/
#define sc_array_on_error(...)
/** /**
* Configure memory allocators here. You can plug your allocator if you want, * Configure memory allocators here. You can plug your allocator if you want,
* replace 'realloc' and 'free' with your allocator, make sure you include * replace 'realloc' and 'free' with your allocator, make sure you include

94
condition/CMakeLists.txt Normal file
View File

@ -0,0 +1,94 @@
cmake_minimum_required(VERSION 3.5.1)
project(sc_cond C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
add_executable(sc_cond cond_example.c sc_cond.h sc_cond.c)
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -pthread -pedantic -Werror -D_GNU_SOURCE")
endif ()
# --------------------------------------------------------------------------- #
# --------------------- Test Configuration Start ---------------------------- #
# --------------------------------------------------------------------------- #
include(CTest)
include(CheckCCompilerFlag)
enable_testing()
add_executable(${PROJECT_NAME}_test cond_test.c sc_cond.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=pthread_mutexattr_init,--wrap=pthread_mutex_init,--wrap=pthread_cond_wait)
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 $<CONFIG>
--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 $<CONFIG> --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 ---------------------------- #

6
condition/cond_example.c Normal file
View File

@ -0,0 +1,6 @@
int main()
{
return 0;
}

252
condition/cond_test.c Normal file
View File

@ -0,0 +1,252 @@
#include "sc_cond.h"
#include <string.h>
#include <assert.h>
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
struct sc_thread
{
HANDLE id;
void* (*fn)(void*);
void* arg;
void* ret;
};
#else
#include <pthread.h>
#include <unistd.h>
struct sc_thread
{
pthread_t id;
};
#endif
void sc_thread_init(struct sc_thread* thread);
int sc_thread_term(struct sc_thread* thread);
int sc_thread_start(struct sc_thread* thread, void* (*fn)(void*), void* arg);
int sc_thread_stop(struct sc_thread* thread, void** ret);
void sc_thread_init(struct sc_thread* thread)
{
thread->id = 0;
}
#if defined(_WIN32) || defined(_WIN64)
#include <process.h>
unsigned int __stdcall sc_thread_fn(void* arg)
{
struct sc_thread* thread = arg;
thread->ret = thread->fn(thread->arg);
return 0;
}
int sc_thread_start(struct sc_thread* thread, void* (*fn)(void*), void* arg)
{
int rc;
thread->fn = fn;
thread->arg = arg;
thread->id = (HANDLE)_beginthreadex(NULL, 0, sc_thread_fn, thread, 0, NULL);
rc = thread->id == 0 ? -1 : 0;
return rc;
}
int sc_thread_stop(struct sc_thread* thread, void** ret)
{
int rc = 0;
DWORD rv;
BOOL brc;
if (thread->id == 0) {
return -1;
}
rv = WaitForSingleObject(thread->id, INFINITE);
if (rv == WAIT_FAILED) {
rc = -1;
}
brc = CloseHandle(thread->id);
if (!brc) {
rc = -1;
}
thread->id = 0;
if (ret != NULL) {
*ret = thread->ret;
}
return rc;
}
#else
int sc_thread_start(struct sc_thread* thread, void* (*fn)(void*), void* arg)
{
int rc;
pthread_attr_t hndl;
rc = pthread_attr_init(&hndl);
if (rc != 0) {
return -1;
}
// This may only fail with EINVAL.
pthread_attr_setdetachstate(&hndl, PTHREAD_CREATE_JOINABLE);
rc = pthread_create(&thread->id, &hndl, fn, arg);
// This may only fail with EINVAL.
pthread_attr_destroy(&hndl);
return rc;
}
int sc_thread_stop(struct sc_thread* thread, void** ret)
{
int rc;
void* val;
if (thread->id == 0) {
return -1;
}
rc = pthread_join(thread->id, &val);
thread->id = 0;
if (ret != NULL) {
*ret = val;
}
return rc;
}
#endif
int sc_thread_term(struct sc_thread* thread)
{
return sc_thread_stop(thread, NULL);
}
void* thread1_fn(void* arg)
{
char* data;
struct sc_cond* cond = arg;
assert(sc_cond_sync(cond, (void**)&data) == 0);
assert(strcmp(data, "finish") == 0);
return NULL;
}
void* thread2_fn(void* arg)
{
struct sc_cond* cond = arg;
assert(sc_cond_finish(cond, "finish") == 0);
return NULL;
}
#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;
}
bool mock_mutexinit = false;
extern int __real_pthread_mutex_init(pthread_mutex_t *__mutex,
const pthread_mutexattr_t *__mutexattr);
int __wrap_pthread_mutex_init(pthread_mutex_t *__mutex,
const pthread_mutexattr_t *__mutexattr)
{
if (!mock_mutexinit) {
return __real_pthread_mutex_init(__mutex, __mutexattr);
}
return -1;
}
bool mock_condwait = false;
extern int __real_pthread_cond_wait (pthread_cond_t *__restrict __cond,
pthread_mutex_t *__restrict __mutex);
int __wrap_pthread_cond_wait (pthread_cond_t *__restrict __cond,
pthread_mutex_t *__restrict __mutex)
{
if (!mock_condwait) {
return __real_pthread_cond_wait(__cond, __mutex);
}
return -1;
}
void fail_test()
{
struct sc_cond cond;
char* var;
mock_attrinit = true;
assert(sc_cond_init(&cond) == -1);
mock_attrinit = false;
assert(sc_cond_init(&cond) == 0);
assert(sc_cond_term(&cond) == 0);
mock_mutexinit = true;
assert(sc_cond_init(&cond) == -1);
mock_mutexinit = false;
assert(sc_cond_init(&cond) == 0);
assert(sc_cond_term(&cond) == 0);
assert(sc_cond_init(&cond) == 0);
mock_condwait = true;
assert(sc_cond_sync(&cond, (void**) &var) == -1);
mock_condwait = false;
assert(sc_cond_term(&cond) == 0);
}
#else
void fail_test()
{
}
#endif
void test1()
{
struct sc_cond cond;
struct sc_thread thread1;
struct sc_thread thread2;
assert(sc_cond_init(&cond) == 0);
sc_thread_init(&thread1);
sc_thread_init(&thread2);
assert(sc_thread_start(&thread1, thread1_fn, &cond) == 0);
assert(sc_thread_start(&thread2, thread2_fn, &cond) == 0);
assert(sc_thread_term(&thread1) == 0);
assert(sc_thread_term(&thread2) == 0);
assert(sc_cond_term(&cond) == 0);
}
int main()
{
test1();
fail_test();
return 0;
}

191
condition/sc_cond.c Normal file
View File

@ -0,0 +1,191 @@
/*
* 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_cond.h"
#include <assert.h>
#include <string.h>
#if defined(_WIN32) || defined(_WIN64)
int sc_cond_init(struct sc_cond *cond)
{
cond->data = NULL;
cond->done = false;
InitializeCriticalSection(&cond->mtx);
InitializeConditionVariable(&cond->cond);
return 0;
}
int sc_cond_term(struct sc_cond *cond)
{
DeleteCriticalSection(&cond->mtx);
return 0;
}
int sc_cond_finish(struct sc_cond *cond, void *var)
{
EnterCriticalSection(&cond->mtx);
cond->data = var;
cond->done = true;
WakeConditionVariable(&cond->cond);
LeaveCriticalSection(&cond->mtx);
return 0;
}
int sc_cond_sync(struct sc_cond *cond, void **data)
{
int rc = 0;
BOOL rv;
EnterCriticalSection(&cond->mtx);
while (cond->done == false) {
rv = SleepConditionVariableCS(&cond->cond, &cond->mtx, INFINITE);
if (rv == 0) {
sc_cond_on_error("SleepConditionVariableCS: errcode(%d) ",
(int) GetLastError());
rc = -1;
goto out;
}
}
if (data != NULL) {
*data = cond->data;
}
cond->data = NULL;
cond->done = false;
out:
LeaveCriticalSection(&cond->mtx);
return rc;
}
#else
int sc_cond_init(struct sc_cond *cond)
{
int rc;
cond->data = NULL;
cond->done = false;
pthread_mutexattr_t attr;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
cond->mtx = mut;
// May fail on OOM
rc = pthread_mutexattr_init(&attr);
if (rc != 0) {
sc_cond_on_error("pthread_mutexattr_init : errno(%d) \n", rc);
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(&cond->mtx, &attr);
if (rc != 0) {
sc_cond_on_error("pthread_mutex_init : errno(%d) \n", rc);
return -1;
}
// This won't fail as long as we pass correct param.
pthread_mutexattr_destroy(&attr);
return pthread_cond_init(&cond->cond, NULL);
}
int sc_cond_term(struct sc_cond *cond)
{
int rc;
rc = pthread_mutex_destroy(&cond->mtx);
rc |= pthread_cond_destroy(&cond->cond);
return rc;
}
int sc_cond_finish(struct sc_cond *cond, void *var)
{
int rc, rv;
rv = pthread_mutex_lock(&cond->mtx);
assert(rv == 0);
cond->data = var;
cond->done = true;
rc = pthread_cond_signal(&cond->cond);
if (rc != 0) {
sc_cond_on_error("pthread_cond_signal : errno(%d) \n", rc);
}
rv = pthread_mutex_unlock(&cond->mtx);
assert(rv == 0);
return rc;
}
int sc_cond_sync(struct sc_cond *cond, void **data)
{
int rc, rv;
rv = pthread_mutex_lock(&cond->mtx);
assert(rv == 0);
while (cond->done == false) {
rc = pthread_cond_wait(&cond->cond, &cond->mtx);
if (rc != 0) {
sc_cond_on_error("pthread_mutex_init : errno(%d) \n", rc);
goto out;
}
}
if (data != NULL) {
*data = cond->data;
}
cond->data = NULL;
cond->done = false;
out:
rv = pthread_mutex_unlock(&cond->mtx);
assert(rv == 0);
return rc;
}
#endif

63
condition/sc_cond.h Normal file
View File

@ -0,0 +1,63 @@
/*
* 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_COND_H
#define SC_COND_H
#include <stdbool.h>
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#else
#include <pthread.h>
#endif
struct sc_cond
{
bool done;
void* data;
#if defined(_WIN32) || defined(_WIN64)
CONDITION_VARIABLE cond;
CRITICAL_SECTION mtx;
#else
pthread_cond_t cond;
pthread_mutex_t mtx;
#endif
};
int sc_cond_init(struct sc_cond* cond);
int sc_cond_term(struct sc_cond* cond);
int sc_cond_finish(struct sc_cond* cond, void* data);
int sc_cond_sync(struct sc_cond* cond, void** data);
/**
* If you want to log or abort on errors like out of memory,
* put your error function here. It will be called with printf like error msg.
*
* my_on_error(const char* fmt, ...);
*/
#define sc_cond_on_error(...)
#endif

View File

@ -45,6 +45,7 @@ bool sc_heap_init(struct sc_heap *heap, size_t cap)
// Check overflow // Check overflow
if (cap > SC_CAP_MAX || (elems = sc_heap_malloc(alloc)) == NULL) { if (cap > SC_CAP_MAX || (elems = sc_heap_malloc(alloc)) == NULL) {
sc_heap_on_error("Out of memory. cap(%zu) alloc(%zu) ", cap, alloc);
return false; return false;
} }
@ -76,10 +77,11 @@ bool sc_heap_add(struct sc_heap *heap, int64_t key, void *data)
if (++heap->size >= heap->cap) { if (++heap->size >= heap->cap) {
const size_t cap = heap->cap != 0 ? heap->cap * 2 : 4; const size_t cap = heap->cap != 0 ? heap->cap * 2 : 4;
const size_t alloc = cap * 2 * sizeof(struct sc_heap_data); const size_t m = cap * 2 * sizeof(struct sc_heap_data);
// Check overflow // Check overflow
if (heap->cap >= SC_CAP_MAX / 2 || if (heap->cap >= SC_CAP_MAX / 2 ||
(exp = sc_heap_realloc(heap->elems, alloc)) == NULL) { (exp = sc_heap_realloc(heap->elems, m)) == NULL) {
sc_heap_on_error("Out of memory. cap(%zu) m(%zu) ", heap->cap, m);
return false; return false;
} }

View File

@ -44,6 +44,14 @@ struct sc_heap
struct sc_heap_data *elems; struct sc_heap_data *elems;
}; };
/**
* If you want to log or abort on errors like out of memory,
* put your error function here. It will be called with printf like error msg.
*
* my_on_error(const char* fmt, ...);
*/
#define sc_heap_on_error(...)
/** /**
* Plug your memory allocator. * Plug your memory allocator.
*/ */

View File

@ -30,7 +30,8 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP) target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP)
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin) target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin)
target_link_options(${PROJECT_NAME}_test PRIVATE target_link_options(${PROJECT_NAME}_test PRIVATE
-Wl,--wrap=fprintf,--wrap=vfprintf,--wrap=fopen,--wrap=localtime,--wrap=pthread_mutexattr_init) -Wl,--wrap=fprintf,--wrap=vfprintf,--wrap=fopen,--wrap=localtime
-Wl,--wrap=pthread_mutexattr_init,--wrap=pthread_mutex_init)
endif () endif ()
endif () endif ()

View File

@ -164,12 +164,28 @@ int __wrap_pthread_mutexattr_init(pthread_mutexattr_t *attr)
return -1; return -1;
} }
bool mock_mutexinit = false;
extern int __real_pthread_mutex_init(pthread_mutex_t *__mutex,
const pthread_mutexattr_t *__mutexattr);
int __wrap_pthread_mutex_init(pthread_mutex_t *__mutex,
const pthread_mutexattr_t *__mutexattr)
{
if (!mock_mutexinit) {
return __real_pthread_mutex_init(__mutex, __mutexattr);
}
return -1;
}
void fail_test(void) void fail_test(void)
{ {
mock_attrinit = true; mock_attrinit = true;
assert(sc_log_init() < 0); assert(sc_log_init() < 0);
mock_attrinit = false; mock_attrinit = false;
mock_mutexinit = true;
assert(sc_log_init() < 0);
mock_mutexinit = false;
assert(sc_log_init() == 0); assert(sc_log_init() == 0);
mock_fprintf = true; mock_fprintf = true;
@ -297,6 +313,8 @@ void example(void)
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
sc_log_set_thread_name("My thread");
fail_test(); fail_test();
example(); example();
test1(); test1();

View File

@ -24,12 +24,29 @@
#include "sc_log.h" #include "sc_log.h"
#include <assert.h>
#include <errno.h> #include <errno.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#ifndef thread_local
#if __STDC_VERSION__ >= 201112 && !defined __STDC_NO_THREADS__
#define thread_local _Thread_local
#elif defined _WIN32 && (defined _MSC_VER || defined __ICL || \
defined __DMC__ || defined __BORLANDC__)
#define thread_local __declspec(thread)
/* note that ICC (linux) and Clang are covered by __GNUC__ */
#elif defined __GNUC__ || defined __SUNPRO_C || defined __xlC__
#define thread_local __thread
#else
#error "Cannot define thread_local"
#endif
#endif
thread_local char sc_name[32] = "Thread";
#if defined(_WIN32) || defined(_WIN64) #if defined(_WIN32) || defined(_WIN64)
#pragma warning(disable : 4996) #pragma warning(disable : 4996)
@ -75,14 +92,24 @@ struct sc_log_mutex
int sc_log_mutex_init(struct sc_log_mutex *mtx) int sc_log_mutex_init(struct sc_log_mutex *mtx)
{ {
int rc;
pthread_mutexattr_t attr; pthread_mutexattr_t attr;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
mtx->mtx = mut; mtx->mtx = mut;
if (pthread_mutexattr_init(&attr) != 0 || rc = pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL) != 0 || if (rc != 0) {
pthread_mutex_init(&mtx->mtx, &attr) != 0) { sc_log_on_error("pthread_mutexattr_init : errcode(%d) ", rc);
return -1;
}
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
rc = pthread_mutex_init(&mtx->mtx, &attr);
if (rc != 0) {
sc_log_on_error("pthread_mutex_init : errcode(%d) ", rc);
return -1; return -1;
} }
@ -92,17 +119,30 @@ int sc_log_mutex_init(struct sc_log_mutex *mtx)
int sc_log_mutex_term(struct sc_log_mutex *mtx) int sc_log_mutex_term(struct sc_log_mutex *mtx)
{ {
return pthread_mutex_destroy(&mtx->mtx); int rc;
rc = pthread_mutex_destroy(&mtx->mtx);
if (rc != 0) {
sc_log_on_error("pthread_mutex_destroy : errcode(%d) ", rc);
}
return rc;
} }
void sc_log_mutex_lock(struct sc_log_mutex *mtx) void sc_log_mutex_lock(struct sc_log_mutex *mtx)
{ {
pthread_mutex_lock(&mtx->mtx); int rc;
rc = pthread_mutex_lock(&mtx->mtx);
assert(rc == 0);
} }
void sc_log_mutex_unlock(struct sc_log_mutex *mtx) void sc_log_mutex_unlock(struct sc_log_mutex *mtx)
{ {
pthread_mutex_unlock(&mtx->mtx); int rc;
rc = pthread_mutex_unlock(&mtx->mtx);
assert(rc == 0);
} }
#endif #endif
@ -126,17 +166,10 @@ struct sc_log sc_log;
int sc_log_init(void) 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.level = SC_LOG_INFO;
sc_log.to_stdout = true; sc_log.to_stdout = true;
return 0; return sc_log_mutex_init(&sc_log.mtx);
} }
int sc_log_term(void) int sc_log_term(void)
@ -145,6 +178,7 @@ int sc_log_term(void)
if (sc_log.fp) { if (sc_log.fp) {
rc = fclose(sc_log.fp); rc = fclose(sc_log.fp);
assert(rc == 0);
} }
sc_log_mutex_term(&sc_log.mtx); sc_log_mutex_term(&sc_log.mtx);
@ -153,6 +187,11 @@ int sc_log_term(void)
return rc; return rc;
} }
void sc_log_set_thread_name(const char *name)
{
strncpy(sc_name, name, sizeof(sc_name));
}
int sc_log_set_level(const char *str) int sc_log_set_level(const char *str)
{ {
size_t count = sizeof(sc_log_levels) / sizeof(struct sc_log_level_pair); size_t count = sizeof(sc_log_levels) / sizeof(struct sc_log_level_pair);
@ -240,9 +279,9 @@ static int sc_log_print_header(FILE *fp, enum sc_log_level level)
return -1; return -1;
} }
return fprintf(fp, "[%d-%02d-%02d %02d:%02d:%02d][%-5s] ", return fprintf(fp, "[%d-%02d-%02d %02d:%02d:%02d][%-5s][%s] ",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, 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); tm->tm_min, tm->tm_sec, sc_log_levels[level].str, sc_name);
} }
static int sc_log_stdout(enum sc_log_level level, const char *fmt, va_list va) static int sc_log_stdout(enum sc_log_level level, const char *fmt, va_list va)

View File

@ -53,6 +53,15 @@ const static struct sc_log_level_pair
}; };
// clang-format on // clang-format on
/**
* If you want to log or abort on errors like mutex init which is not supposed
* to fail ever(?), put your error function here. It will be called with printf
* like error msg.
*
* my_on_error(const char* fmt, ...);
*/
#define sc_log_on_error(...)
/** /**
* User callback * User callback
* *
@ -76,6 +85,14 @@ int sc_log_init(void);
*/ */
int sc_log_term(void); int sc_log_term(void);
/**
* Call once from each thread, it will copy the name passed into a thread
* local buffer. Max size is 31 characters.
*
* @param name Thread name
*/
void sc_log_set_thread_name(const char* name);
/** /**
* Thread-safe. * Thread-safe.
* *
@ -154,5 +171,4 @@ int sc_log_log(enum sc_log_level level, const char *fmt, ...);
#define sc_log_warn(...) (sc_log_log(SC_LOG_WARN, 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__, ""))) #define sc_log_error(...) (sc_log_log(SC_LOG_ERROR, sc_log_ap(__VA_ARGS__, "")))
#define sc_log_(args)
#endif #endif

View File

@ -336,7 +336,7 @@ void test4()
char* c; char* c;
struct sc_map_64s map64s; struct sc_map_64s map64s;
assert(sc_map_init_64s(&map64s, 0, 87)); assert(sc_map_init_64s(&map64s, 1, 87));
for (int i = 0 ; i < 100; i++) { for (int i = 0 ; i < 100; i++) {
assert(sc_map_put_64s(&map64s, i, NULL)); assert(sc_map_put_64s(&map64s, i, NULL));
assert(sc_map_get_64s(&map64s, i, &c)); assert(sc_map_get_64s(&map64s, i, &c));
@ -358,7 +358,7 @@ void test4()
void* v; void* v;
struct sc_map_64v map64v; struct sc_map_64v map64v;
assert(sc_map_init_64v(&map64v, 0, 87)); assert(sc_map_init_64v(&map64v, 1, 87));
for (int i = 0 ; i < 100; i++) { for (int i = 0 ; i < 100; i++) {
assert(sc_map_put_64v(&map64v, i, NULL)); assert(sc_map_put_64v(&map64v, i, NULL));
assert(sc_map_get_64v(&map64v, i, &v)); assert(sc_map_get_64v(&map64v, i, &v));
@ -377,19 +377,27 @@ void test4()
sc_map_term_64v(&map64v); sc_map_term_64v(&map64v);
char *keys[128];
char *values[128];
for (int i = 0; i < 128; i++) {
keys[i] = str_random((rand() % 64) + 32);
values[i] = str_random((rand() % 64) + 32);
}
struct sc_map_sv mapsv; struct sc_map_sv mapsv;
assert(sc_map_init_sv(&mapsv, 0, 87)); assert(sc_map_init_sv(&mapsv, 1, 87));
for (int i = 0 ; i < 100; i++) { for (int i = 0 ; i < 100; i++) {
assert(sc_map_put_sv(&mapsv, "", NULL)); assert(sc_map_put_sv(&mapsv, keys[i], values[i]));
assert(sc_map_get_sv(&mapsv, "", &v)); assert(sc_map_get_sv(&mapsv, keys[i], &v));
assert(v == NULL); assert(v == values[i]);
} }
assert(sc_map_size_sv(&mapsv) == 1); assert(sc_map_size_sv(&mapsv) == 100);
assert(sc_map_del_sv(&mapsv, "", &v)); assert(sc_map_del_sv(&mapsv, keys[0], &v));
assert(v == NULL); assert(v == values[0]);
assert(sc_map_size_sv(&mapsv) == 0); assert(sc_map_size_sv(&mapsv) == 99);
assert(!sc_map_del_sv(&mapsv, "", &v)); assert(!sc_map_del_sv(&mapsv, keys[0], &v));
sc_map_clear_sv(&mapsv); sc_map_clear_sv(&mapsv);
assert(sc_map_size_sv(&mapsv) == 0); assert(sc_map_size_sv(&mapsv) == 0);
sc_map_term_sv(&mapsv); sc_map_term_sv(&mapsv);
@ -398,16 +406,23 @@ void test4()
struct sc_map_s64 maps64; struct sc_map_s64 maps64;
assert(sc_map_init_s64(&maps64, 0, 26)); assert(sc_map_init_s64(&maps64, 0, 26));
assert(sc_map_put_s64(&maps64, "", 511)); for (int i =0 ; i< 64; i++) {
assert(sc_map_put_s64(&maps64, "", 511)); assert(sc_map_put_s64(&maps64, keys[i], i));
assert(sc_map_get_s64(&maps64, "", &val)); }
assert(val == 511);
assert(sc_map_size_s64(&maps64) == 1); assert(sc_map_get_s64(&maps64, keys[0], &val));
assert(sc_map_del_s64(&maps64, "", &val)); assert(val == 0);
assert(val == 511); assert(sc_map_size_s64(&maps64) == 64);
assert(sc_map_size_s64(&maps64) == 0); assert(sc_map_del_s64(&maps64, keys[12], &val));
assert(val == 12);
assert(sc_map_size_s64(&maps64) == 63);
sc_map_clear_s64(&maps64); sc_map_clear_s64(&maps64);
sc_map_term_s64(&maps64); sc_map_term_s64(&maps64);
for (int i = 0; i < 128; i++) {
free(keys[i]);
free(values[i]);
}
} }

View File

@ -1,7 +1,7 @@
#include "sc_map.h" #include "sc_map.h"
#include <memory.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#ifndef SC_SIZE_MAX #ifndef SC_SIZE_MAX
#define SC_SIZE_MAX UINT32_MAX #define SC_SIZE_MAX UINT32_MAX
@ -57,9 +57,11 @@
static void *sc_map_alloc_##name(uint32_t *cap, uint32_t factor) \ static void *sc_map_alloc_##name(uint32_t *cap, uint32_t factor) \
{ \ { \
uint32_t v = *cap; \ uint32_t v = *cap; \
void *p; \
struct sc_map_item_##name *t; \ struct sc_map_item_##name *t; \
\ \
if (*cap > SC_SIZE_MAX / factor) { \ if (*cap > SC_SIZE_MAX / factor) { \
sc_map_on_error("Out of memory. cap(%zu).", *cap); \
return NULL; \ return NULL; \
} \ } \
\ \
@ -72,7 +74,12 @@
v++; \ v++; \
\ \
*cap = v; \ *cap = v; \
return sc_map_calloc(sizeof(*t), v); \ p = sc_map_calloc(sizeof(*t), v); \
if (p == NULL) { \
sc_map_on_error("Out of memory. t(%zu) v(%zu).", sizeof(*t), v); \
} \
\
return p; \
} \ } \
\ \
bool sc_map_init_##name(struct sc_map_##name *map, uint32_t cap, \ bool sc_map_init_##name(struct sc_map_##name *map, uint32_t cap, \

View File

@ -59,17 +59,17 @@
uint32_t load_factor; \ uint32_t load_factor; \
uint32_t remap; \ uint32_t remap; \
V value; \ V value; \
bool used; \ bool used; \
}; \ }; \
\ \
bool sc_map_init_##name(struct sc_map_##name *map, uint32_t cap, \ bool sc_map_init_##name(struct sc_map_##name *map, uint32_t cap, \
uint32_t load_factor); \ uint32_t load_factor); \
void sc_map_term_##name(struct sc_map_##name *map); \ void sc_map_term_##name(struct sc_map_##name *map); \
uint32_t sc_map_size_##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); \ 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_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_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); bool sc_map_del_##name(struct sc_map_##name *map, K key, V *value);
#define sc_map_foreach(map, K, V) \ #define sc_map_foreach(map, K, V) \
for (uint32_t __i = 0, __b = 0; __i < (map)->cap; __i++) \ for (uint32_t __i = 0, __b = 0; __i < (map)->cap; __i++) \
@ -99,4 +99,13 @@ sc_map_of_strkey(sv, char *, void*)
sc_map_of_strkey(s64, char *, uint64_t) sc_map_of_strkey(s64, char *, uint64_t)
// clang-format on // clang-format on
/**
* If you want to log or abort on errors like out of memory,
* put your error function here. It will be called with printf like error msg.
*
* my_on_error(const char* fmt, ...);
*/
#define sc_map_on_error(...)
#endif #endif

View File

@ -54,7 +54,7 @@ void sc_mutex_unlock(struct sc_mutex *mtx)
int sc_mutex_init(struct sc_mutex *mtx) int sc_mutex_init(struct sc_mutex *mtx)
{ {
int rc; int rc, rv;
pthread_mutexattr_t attr; pthread_mutexattr_t attr;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
@ -63,6 +63,7 @@ int sc_mutex_init(struct sc_mutex *mtx)
// May fail on OOM // May fail on OOM
rc = pthread_mutexattr_init(&attr); rc = pthread_mutexattr_init(&attr);
if (rc != 0) { if (rc != 0) {
sc_mutex_on_error("pthread_mutexattr_init : errcode(%d) ", rc);
return rc; return rc;
} }
@ -72,27 +73,45 @@ int sc_mutex_init(struct sc_mutex *mtx)
// May fail on OOM // May fail on OOM
rc = pthread_mutex_init(&mtx->mtx, &attr); rc = pthread_mutex_init(&mtx->mtx, &attr);
if (rc != 0) {
sc_mutex_on_error("pthread_mutex_init : errcode(%d) ", rc);
}
// This won't fail as long as we pass correct param. // This won't fail as long as we pass correct param.
pthread_mutexattr_destroy(&attr); rv = pthread_mutexattr_destroy(&attr);
assert(rv == 0);
return rc; return rc;
} }
int sc_mutex_term(struct sc_mutex *mtx) int sc_mutex_term(struct sc_mutex *mtx)
{ {
return pthread_mutex_destroy(&mtx->mtx); int rc;
rc = pthread_mutex_destroy(&mtx->mtx);
if (rc != 0) {
sc_mutex_on_error("pthread_mutex_destroy : errcode(%d) ", rc);
}
return rc;
} }
void sc_mutex_lock(struct sc_mutex *mtx) void sc_mutex_lock(struct sc_mutex *mtx)
{ {
int rc;
// This won't fail as long as we pass correct param. // This won't fail as long as we pass correct param.
pthread_mutex_lock(&mtx->mtx); rc = pthread_mutex_lock(&mtx->mtx);
assert(rc == 0);
} }
void sc_mutex_unlock(struct sc_mutex *mtx) void sc_mutex_unlock(struct sc_mutex *mtx)
{ {
int rc;
// This won't fail as long as we pass correct param. // This won't fail as long as we pass correct param.
pthread_mutex_unlock(&mtx->mtx); rc = pthread_mutex_unlock(&mtx->mtx);
assert(rc == 0);
} }
#endif #endif

View File

@ -45,4 +45,12 @@ int sc_mutex_term(struct sc_mutex *mtx);
void sc_mutex_lock(struct sc_mutex *mtx); void sc_mutex_lock(struct sc_mutex *mtx);
void sc_mutex_unlock(struct sc_mutex *mtx); void sc_mutex_unlock(struct sc_mutex *mtx);
/**
* If you want to log or abort on errors like mutex init,
* put your error function here. It will be called with printf like error msg.
*
* my_on_error(const char* fmt, ...);
*/
#define sc_mutex_on_error(...)
#endif #endif

View File

@ -8,7 +8,7 @@ set(CMAKE_C_EXTENSIONS OFF)
add_executable(sc_pipe pipe_example.c sc_pipe.h sc_pipe.c) add_executable(sc_pipe pipe_example.c sc_pipe.h sc_pipe.c)
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -pedantic -Werror -D_GNU_SOURCE") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror -D_GNU_SOURCE")
endif () endif ()
@ -23,9 +23,21 @@ enable_testing()
add_executable(${PROJECT_NAME}_test pipe_test.c sc_pipe.c) add_executable(${PROJECT_NAME}_test pipe_test.c sc_pipe.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=pipe,--wrap=close)
endif ()
endif ()
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
"${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer) target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)

View File

@ -26,6 +26,53 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include <stdbool.h>
#ifdef SC_HAVE_WRAP
#include <unistd.h>
bool fail_close = false;
int __real_close(int fd);
int __wrap_close(int fd)
{
if (fail_close) {
return -1;
}
return __real_close(fd);
}
bool fail_pipe = false;
int __real_pipe(int __pipedes[2]);
int __wrap_pipe(int __pipedes[2])
{
if (fail_pipe) {
return -1;
}
return __real_pipe(__pipedes);
}
void fail_test()
{
struct sc_pipe pipe;
fail_pipe = true;
assert(sc_pipe_init(&pipe, 0) != 0);
fail_pipe = false;
assert(sc_pipe_init(&pipe, 0) == 0);
fail_close = true;
assert(sc_pipe_term(&pipe) == -1);
fail_close = false;
assert(sc_pipe_term(&pipe) == 0);
}
#else
void fail_test()
{
}
#endif
void test1(void) void test1(void)
{ {
@ -51,10 +98,12 @@ int main(int argc, char* argv[])
HIBYTE(data.wVersion) == 2); HIBYTE(data.wVersion) == 2);
#endif #endif
test1(); test1();
fail_test();
#if defined(_WIN32) || defined(_WIN64) #if defined(_WIN32) || defined(_WIN64)
rc = WSACleanup(); rc = WSACleanup();
assert(rc == 0); assert(rc == 0);
#endif #endif
return 0; return 0;
} }

View File

@ -22,23 +22,24 @@
* SOFTWARE. * SOFTWARE.
*/ */
#include <string.h> #include "sc_pipe.h"
#include <errno.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include "sc_pipe.h"
#if defined(_WIN32) || defined(_WIN64) #if defined(_WIN32) || defined(_WIN64)
int sc_pipe_init(struct sc_pipe* p) int sc_pipe_init(struct sc_pipe *p)
{ {
SOCKET listener; SOCKET listener;
int rc; int rc;
struct sockaddr_in addr; struct sockaddr_in addr;
size_t addrlen; int addrlen = sizeof(addr);
int one; int val = 1;
BOOL nodelay; BOOL nodelay = 1;
u_long nonblock; u_long nonblock;
p->w = INVALID_SOCKET; p->w = INVALID_SOCKET;
@ -50,26 +51,23 @@ int sc_pipe_init(struct sc_pipe* p)
goto wsafail; goto wsafail;
} }
one = 1; rc = setsockopt(listener, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *) &val,
rc = setsockopt(listener, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, sizeof(val));
(char*)&one, sizeof(one));
if (rc == SOCKET_ERROR) { if (rc == SOCKET_ERROR) {
goto wsafail; goto wsafail;
} }
/* Bind the listening socket to the local port. */
memset(&addr, 0, sizeof(addr)); memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = 0; addr.sin_port = 0;
rc = bind(listener, (const struct sockaddr*)&addr, sizeof(addr)); rc = bind(listener, (const struct sockaddr *) &addr, sizeof(addr));
if (rc == SOCKET_ERROR) { if (rc == SOCKET_ERROR) {
goto wsafail; goto wsafail;
} }
addrlen = sizeof(addr); rc = getsockname(listener, (struct sockaddr *) &addr, &addrlen);
rc = getsockname(listener, (struct sockaddr*)&addr, &addrlen);
if (rc == SOCKET_ERROR) { if (rc == SOCKET_ERROR) {
goto wsafail; goto wsafail;
} }
@ -84,70 +82,70 @@ int sc_pipe_init(struct sc_pipe* p)
goto wsafail; goto wsafail;
} }
/* Set TCP_NODELAY on the writer socket to make efd as fast as possible. rc = setsockopt(p->w, IPPROTO_TCP, TCP_NODELAY, (char *) &nodelay,
There's only one byte going to be written, so batching would not make sizeof(nodelay));
sense anyway. */
nodelay = 1;
rc = setsockopt(p->w, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelay,
sizeof(nodelay));
if (rc == SOCKET_ERROR) { if (rc == SOCKET_ERROR) {
goto wsafail; goto wsafail;
} }
rc = connect(p->w, (struct sockaddr*)&addr, sizeof(addr)); rc = connect(p->w, (struct sockaddr *) &addr, sizeof(addr));
if (rc == SOCKET_ERROR) { if (rc == SOCKET_ERROR) {
goto wsafail; goto wsafail;
} }
p->r = accept(listener, (struct sockaddr *) &addr, &addrlen);
addrlen = sizeof(addr);
p->r = accept(listener, (struct sockaddr*)&addr, &addrlen);
if (p->r == INVALID_SOCKET) { if (p->r == INVALID_SOCKET) {
goto wsafail; goto wsafail;
} }
(void)closesocket(listener); closesocket(listener);
return 0; return 0;
wsafail: wsafail:
sc_pipe_on_error("sc_pipe_init() : %d ", WSAGetLastError())
return -1; return -1;
} }
int sc_pipe_term(struct sc_pipe* p) int sc_pipe_term(struct sc_pipe *p)
{ {
int rc; int rc = 0, rv;
SOCKET s; SOCKET s;
rc = closesocket(p->r); rv = closesocket(p->r);
rc |= closesocket(p->w); if (rv != 0) {
rc = -1;
sc_pipe_on_error("closesocket() : errcode(%d) ", WSAGetLastError());
}
if (rc != 0) { rv = closesocket(p->w);
sc_pipe_abort(); if (rv != 0) {
rc = -1;
sc_pipe_on_error("closesocket() : errcode(%d) ", WSAGetLastError());
} }
return rc; return rc;
} }
int sc_pipe_write(struct sc_pipe* p, void* data, int len) int sc_pipe_write(struct sc_pipe *p, void *data, int len)
{ {
int rc; int rc;
rc = send(p->w, data, len, 0); rc = send(p->w, data, len, 0);
if (rc == SOCKET_ERROR || rc != len) { if (rc == SOCKET_ERROR || rc != len) {
sc_pipe_abort(); sc_pipe_on_error("send() : errcode(%d) ", WSAGetLastError());
} }
return rc; return rc;
} }
int sc_pipe_read(struct sc_pipe* p, void* data, int len) int sc_pipe_read(struct sc_pipe *p, void *data, int len)
{ {
int rc; int rc;
rc = recv(p->r, (char*)data, len, 0); rc = recv(p->r, (char *) data, len, 0);
if (rc == SOCKET_ERROR || rc != len) { if (rc == SOCKET_ERROR || rc != len) {
sc_pipe_abort(); sc_pipe_on_error("recv() : errcode(%d) ", WSAGetLastError());
} }
return rc; return rc;
@ -155,15 +153,15 @@ int sc_pipe_read(struct sc_pipe* p, void* data, int len)
#else #else
#include <unistd.h> #include <unistd.h>
int sc_pipe_init(struct sc_pipe *p, int type) int sc_pipe_init(struct sc_pipe *p, int type)
{ {
int rc; int rc;
rc = pipe(p->fds); rc = pipe(p->fds);
if (rc == -1) { if (rc == -1) {
sc_pipe_abort(); sc_pipe_on_error("pipe() : %d ", errno);
return -1; return -1;
} }
@ -174,10 +172,19 @@ int sc_pipe_init(struct sc_pipe *p, int type)
int sc_pipe_term(struct sc_pipe *nfd) int sc_pipe_term(struct sc_pipe *nfd)
{ {
int rc; int rc = 0, rv;
rc = close(nfd->fds[0]); rv = close(nfd->fds[0]);
rc |= close(nfd->fds[1]); if (rv != 0) {
rc = -1;
sc_pipe_on_error("pipe() : %d ", errno);
}
rv = close(nfd->fds[1]);
if (rv != 0) {
rc = -1;
sc_pipe_on_error("pipe() : %d ", errno);
}
return rc; return rc;
} }
@ -188,7 +195,7 @@ int sc_pipe_write(struct sc_pipe *nfd, void *data, int len)
n = write(nfd->fds[1], data, len); n = write(nfd->fds[1], data, len);
if (n != len) { if (n != len) {
sc_pipe_abort(); sc_pipe_on_error("pipe() : %d ", errno);
} }
return n; return n;
@ -199,8 +206,8 @@ int sc_pipe_read(struct sc_pipe *nfd, void *data, int len)
ssize_t n; ssize_t n;
n = read(nfd->fds[0], data, len); n = read(nfd->fds[0], data, len);
if (n == -1) { if (n != len) {
sc_pipe_abort(); sc_pipe_on_error("pipe() : %d ", errno);
} }
return n; return n;

View File

@ -27,8 +27,6 @@
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#define sc_pipe_abort()
#if defined(_WIN32) || defined(_WIN64) #if defined(_WIN32) || defined(_WIN64)
#include <winsock2.h> #include <winsock2.h>
#include <windows.h> #include <windows.h>
@ -55,4 +53,12 @@ int sc_pipe_term(struct sc_pipe *pipe);
int sc_pipe_write(struct sc_pipe *pipe, void *data, int len); int sc_pipe_write(struct sc_pipe *pipe, void *data, int len);
int sc_pipe_read(struct sc_pipe *pipe, void *data, int len); int sc_pipe_read(struct sc_pipe *pipe, void *data, int len);
/**
* If you want to log or abort on errors like mutex init,
* put your error function here. It will be called with printf like error msg.
*
* my_on_error(const char* fmt, ...);
*/
#define sc_pipe_on_error(...)
#endif #endif

View File

@ -36,10 +36,12 @@ 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) static void *queue_alloc(void *prev, size_t elem_size, size_t *cap)
{ {
size_t alloc;
size_t v = *cap; size_t v = *cap;
void *t; void *t;
if (*cap > SC_MAX_CAP) { if (*cap > SC_MAX_CAP) {
sc_queue_on_error("Max capacity has been exceed. cap(%zu). ", (*cap));
return NULL; return NULL;
} }
@ -51,7 +53,12 @@ static void *queue_alloc(void *prev, size_t elem_size, size_t *cap)
} }
v++; v++;
t = sc_queue_realloc(prev, sizeof(struct sc_queue) + (elem_size * v)); alloc = sizeof(struct sc_queue) + (elem_size * v);
t = sc_queue_realloc(prev, alloc);
if (t == NULL) {
sc_queue_on_error("Out of memory. alloc(%zu). ", alloc);
}
*cap = v; *cap = v;
return t; return t;

View File

@ -85,6 +85,14 @@ void sc_queue_term(void **q);
bool sc_queue_expand(void **q, size_t elem_size); bool sc_queue_expand(void **q, size_t elem_size);
/**
* If you want to log or abort on errors like out of memory,
* put your error function here. It will be called with printf like error msg.
*
* my_on_error(const char* fmt, ...);
*/
#define sc_queue_on_error(...)
/** /**
* Plug your allocator if you want. * Plug your allocator if you want.
*/ */

View File

@ -32,10 +32,7 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP) target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP)
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin) target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin)
target_link_options(${PROJECT_NAME}_test PRIVATE target_link_options(${PROJECT_NAME}_test PRIVATE
-Wl,--wrap=malloc,--wrap=vsnprintf,--wrap=realloc) -Wl,--wrap=malloc,--wrap=vsnprintf,--wrap=realloc,--wrap=strlen)
target_link_options(${PROJECT_NAME}_test PRIVATE
-Wl,--wrap=malloc,--wrap=vsnprintf,--wrap=realloc)
endif () endif ()
endif () endif ()
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR

View File

@ -60,6 +60,7 @@ char *sc_str_create(const char *str)
size_t size = strlen(str); size_t size = strlen(str);
if (size > SC_SIZE_MAX) { if (size > SC_SIZE_MAX) {
sc_str_on_error("Max size has been exceed. size(%zu). ", size);
return NULL; return NULL;
} }
@ -74,6 +75,7 @@ char *sc_str_create_len(const char *str, uint32_t len)
copy = sc_str_malloc(sc_str_bytes(len)); copy = sc_str_malloc(sc_str_bytes(len));
if (copy == NULL) { if (copy == NULL) {
sc_str_on_error("Out of memory. size(%zu). ", sc_str_bytes(len));
return NULL; return NULL;
} }
@ -94,12 +96,14 @@ char *sc_str_create_va(const char *fmt, va_list va)
va_copy(args, va); va_copy(args, va);
rc = vsnprintf(tmp, sizeof(tmp), fmt, args); rc = vsnprintf(tmp, sizeof(tmp), fmt, args);
if (rc < 0) { if (rc < 0) {
sc_str_on_error("vsnprintf: %d. ", rc);
return NULL; return NULL;
} }
va_end(args); va_end(args);
str = sc_str_malloc(sc_str_bytes(rc)); str = sc_str_malloc(sc_str_bytes(rc));
if (str == NULL) { if (str == NULL) {
sc_str_on_error("Out of memory. size(%zu). ", sc_str_bytes(rc));
return NULL; return NULL;
} }
@ -113,6 +117,7 @@ char *sc_str_create_va(const char *fmt, va_list va)
va_end(args); va_end(args);
if (rc < 0 || rc > str->len) { if (rc < 0 || rc > str->len) {
sc_str_on_error("vsnprintf: %d. ", rc);
sc_str_free(str); sc_str_free(str);
return NULL; return NULL;
} }
@ -197,6 +202,7 @@ bool sc_str_append(char **str, const char *param)
size_t alloc = sc_str_bytes(meta->len + len); size_t alloc = sc_str_bytes(meta->len + len);
if (alloc > SC_SIZE_MAX || (meta = sc_str_realloc(meta, alloc)) == NULL) { if (alloc > SC_SIZE_MAX || (meta = sc_str_realloc(meta, alloc)) == NULL) {
sc_str_on_error("Out of memory. alloc(%zu). ", alloc);
return false; return false;
} }
@ -304,10 +310,7 @@ bool sc_str_replace(char **str, const char *replace, const char *with)
size_t replace_len = strlen(replace); size_t replace_len = strlen(replace);
size_t with_len = strlen(with); size_t with_len = strlen(with);
if (replace_len > UINT32_MAX || with_len > UINT32_MAX) { int64_t diff = (int64_t) with_len - (int64_t) replace_len;
return false;
}
int64_t diff = (int64_t)with_len - replace_len;
size_t len_unmatch; size_t len_unmatch;
size_t count, size; size_t count, size;
struct sc_str *dest; struct sc_str *dest;
@ -316,6 +319,13 @@ bool sc_str_replace(char **str, const char *replace, const char *with)
char *orig_end = *str + meta->len; char *orig_end = *str + meta->len;
char *tmp; char *tmp;
if (replace_len >= UINT32_MAX || with_len >= UINT32_MAX) {
sc_str_on_error(
"Replace size is too big. replace_len(%zu), with_len(%zu). ",
replace_len, with_len);
return false;
}
// Fast path, same size replacement. // Fast path, same size replacement.
if (diff == 0) { if (diff == 0) {
while ((orig = strstr(orig, replace)) != NULL) { while ((orig = strstr(orig, replace)) != NULL) {
@ -333,6 +343,9 @@ bool sc_str_replace(char **str, const char *replace, const char *with)
tmp += replace_len; tmp += replace_len;
// Check overflow. // Check overflow.
if (size > SC_SIZE_MAX - diff) { if (size > SC_SIZE_MAX - diff) {
sc_str_on_error("Replace size is too big. replace_len(%zu), "
"with_len(%zu). ",
replace_len, with_len);
return false; return false;
} }
size += diff; size += diff;
@ -345,6 +358,7 @@ bool sc_str_replace(char **str, const char *replace, const char *with)
dest = sc_str_malloc(sc_str_bytes(size)); dest = sc_str_malloc(sc_str_bytes(size));
if (!dest) { if (!dest) {
sc_str_on_error("Out of memory. size(%zu). ", sc_str_bytes(size));
return false; return false;
} }
dest->len = size; dest->len = size;

View File

@ -49,6 +49,14 @@
#define sc_str_realloc realloc #define sc_str_realloc realloc
#define sc_str_free free #define sc_str_free free
/**
* If you want to log or abort on errors like out of memory,
* put your error function here. It will be called with printf like error msg.
*
* my_on_error(const char* fmt, ...);
*/
#define sc_str_on_error(...)
/** /**
* @param str '\0' terminated C string, must not be NULL. * @param str '\0' terminated C string, must not be NULL.
* @return Length prefixed string. NULL on out of memory. * @return Length prefixed string. NULL on out of memory.

View File

@ -28,6 +28,17 @@ void *__wrap_realloc(void *p, size_t n)
return __real_realloc(p, n); return __real_realloc(p, n);
} }
bool fail_strlen = false;
size_t __real_strlen(const char* str);
size_t __wrap_strlen(const char* str)
{
if (fail_strlen) {
return UINT32_MAX;
}
return __real_strlen(str);
}
bool fail_vsnprintf; bool fail_vsnprintf;
int fail_vsnprintf_at = -1; int fail_vsnprintf_at = -1;
extern int __real_vsnprintf(char *str, size_t size, const char *format, extern int __real_vsnprintf(char *str, size_t size, const char *format,
@ -175,6 +186,9 @@ void test2()
assert(strcmp(c, "test----") == 0); assert(strcmp(c, "test----") == 0);
sc_str_replace(&c, "--", "0"); sc_str_replace(&c, "--", "0");
assert(strcmp(c, "test00") == 0); assert(strcmp(c, "test00") == 0);
fail_strlen = true;
assert(sc_str_replace(&c, "*", "2") == false);
fail_strlen = false;
sc_str_destroy(c); sc_str_destroy(c);
} }

95
thread/CMakeLists.txt Normal file
View File

@ -0,0 +1,95 @@
cmake_minimum_required(VERSION 3.5.1)
project(sc_thread C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
add_executable(sc_thread thread_example.c sc_thread.h sc_thread.c)
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -pthread -Wall -pedantic -Werror -D_GNU_SOURCE")
endif ()
# --------------------------------------------------------------------------- #
# --------------------- Test Configuration Start ---------------------------- #
# --------------------------------------------------------------------------- #
include(CTest)
include(CheckCCompilerFlag)
enable_testing()
add_executable(${PROJECT_NAME}_test thread_test.c sc_thread.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=pthread_attr_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 $<CONFIG>
--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 $<CONFIG> --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 ---------------------------- #

130
thread/sc_thread.c Normal file
View File

@ -0,0 +1,130 @@
/*
* 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_thread.h"
void sc_thread_init(struct sc_thread* thread)
{
thread->id = 0;
}
#if defined(_WIN32) || defined(_WIN64)
#include <process.h>
unsigned int __stdcall sc_thread_fn(void* arg)
{
struct sc_thread* thread = arg;
thread->ret = thread->fn(thread->arg);
return 0;
}
int sc_thread_start(struct sc_thread* thread, void* (*fn)(void*), void* arg)
{
int rc;
thread->fn = fn;
thread->arg = arg;
thread->id = (HANDLE)_beginthreadex(NULL, 0, sc_thread_fn, thread, 0, NULL);
rc = thread->id == 0 ? -1 : 0;
return rc;
}
int sc_thread_stop(struct sc_thread* thread, void** ret)
{
int rc = 0;
DWORD rv;
BOOL brc;
if (thread->id == 0) {
return -1;
}
rv = WaitForSingleObject(thread->id, INFINITE);
if (rv == WAIT_FAILED) {
rc = -1;
}
brc = CloseHandle(thread->id);
if (!brc) {
rc = -1;
}
thread->id = 0;
if (ret != NULL) {
*ret = thread->ret;
}
return rc;
}
#else
int sc_thread_start(struct sc_thread* thread, void* (*fn)(void*), void* arg)
{
int rc;
pthread_attr_t hndl;
rc = pthread_attr_init(&hndl);
if (rc != 0) {
return -1;
}
// This may only fail with EINVAL.
pthread_attr_setdetachstate(&hndl, PTHREAD_CREATE_JOINABLE);
rc = pthread_create(&thread->id, &hndl, fn, arg);
// This may only fail with EINVAL.
pthread_attr_destroy(&hndl);
return rc;
}
int sc_thread_stop(struct sc_thread* thread, void** ret)
{
int rc;
void* val;
if (thread->id == 0) {
return -1;
}
rc = pthread_join(thread->id, &val);
thread->id = 0;
if (ret != NULL) {
*ret = val;
}
return rc;
}
#endif
int sc_thread_term(struct sc_thread* thread)
{
return sc_thread_stop(thread, NULL);
}

55
thread/sc_thread.h Normal file
View File

@ -0,0 +1,55 @@
/*
* 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_THREAD_H
#define SC_THREAD_H
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
struct sc_thread
{
HANDLE id;
void* (*fn)(void*);
void* arg;
void* ret;
};
#else
#include <pthread.h>
struct sc_thread
{
pthread_t id;
};
#endif
void sc_thread_init(struct sc_thread* thread);
int sc_thread_term(struct sc_thread* thread);
int sc_thread_start(struct sc_thread* thread, void* (*fn)(void*), void* arg);
int sc_thread_stop(struct sc_thread* thread, void** ret);
#endif

19
thread/thread_example.c Normal file
View File

@ -0,0 +1,19 @@
#include "sc_thread.h"
#include <stdio.h>
void* fn(void* arg)
{
printf("%s \n", (char*) arg);
return arg;
}
int main(int argc, char *argv[])
{
struct sc_thread thread;
sc_thread_init(&thread);
sc_thread_start(&thread, fn, "first");
sc_thread_term(&thread);
return 0;
}

73
thread/thread_test.c Normal file
View File

@ -0,0 +1,73 @@
#include "sc_thread.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
void* fn(void* arg)
{
printf("%s \n", (char*) arg);
return arg;
}
void test1()
{
int rc;
void* ret;
struct sc_thread thread;
sc_thread_init(&thread);
rc = sc_thread_start(&thread, fn, "first");
assert(rc == 0);
rc = sc_thread_stop(&thread, &ret);
assert(rc == 0);
assert(strcmp((char*)ret, "first") == 0);
rc = sc_thread_term(&thread);
assert(rc == -1);
sc_thread_init(&thread);
rc = sc_thread_start(&thread, fn, "first");
assert(rc == 0);
rc = sc_thread_term(&thread);
assert(rc == 0);
}
#ifdef SC_HAVE_WRAP
bool fail_pthread_attr_init = false;
int __real_pthread_attr_init (pthread_attr_t *__attr);
int __wrap_pthread_attr_init (pthread_attr_t *__attr)
{
if (fail_pthread_attr_init) {
return -1;
}
return __real_pthread_attr_init(__attr);
}
void fail_test()
{
struct sc_thread thread;
sc_thread_init(&thread);
fail_pthread_attr_init = true;
assert(sc_thread_start(&thread, fn, "first") != 0);
fail_pthread_attr_init = false;
assert(sc_thread_start(&thread, fn, "first") == 0);
assert(sc_thread_term(&thread) == 0);
}
#else
void fail_test()
{
}
#endif
int main(int argc, char *argv[])
{
test1();
fail_test();
return 0;
}

View File

@ -1,4 +1,5 @@
#include "sc_time.h" #include "sc_time.h"
#include <stdio.h> #include <stdio.h>
int main(int argc, char *argv[]) int main(int argc, char *argv[])

View File

@ -26,7 +26,6 @@
#include <assert.h> #include <assert.h>
#include <memory.h> #include <memory.h>
#include <stdio.h>
#define TICK 16u #define TICK 16u
#define WHEEL_COUNT 16u #define WHEEL_COUNT 16u
@ -39,8 +38,8 @@
bool sc_timer_init(struct sc_timer *timer, uint64_t timestamp) bool sc_timer_init(struct sc_timer *timer, uint64_t timestamp)
{ {
const size_t wheel_cap = 4; const uint32_t wheel_cap = 4;
const size_t cap = WHEEL_COUNT * wheel_cap; const uint32_t cap = WHEEL_COUNT * wheel_cap;
const size_t size = cap * sizeof(struct sc_timer_data); const size_t size = cap * sizeof(struct sc_timer_data);
timer->count = 0; timer->count = 0;
@ -50,6 +49,7 @@ bool sc_timer_init(struct sc_timer *timer, uint64_t timestamp)
timer->list = sc_timer_malloc(size); timer->list = sc_timer_malloc(size);
if (timer->list == NULL) { if (timer->list == NULL) {
sc_timer_on_error("Out of memory. size(%zu) ", size);
return false; return false;
} }
@ -68,7 +68,7 @@ void sc_timer_term(struct sc_timer *timer)
void sc_timer_clear(struct sc_timer *timer) void sc_timer_clear(struct sc_timer *timer)
{ {
const size_t cap = timer->wheel * WHEEL_COUNT; const uint32_t cap = timer->wheel * WHEEL_COUNT;
timer->count = 0; timer->count = 0;
timer->head = 0; timer->head = 0;
@ -81,17 +81,19 @@ void sc_timer_clear(struct sc_timer *timer)
static bool expand(struct sc_timer *timer) static bool expand(struct sc_timer *timer)
{ {
size_t cap = timer->wheel * WHEEL_COUNT * 2; uint32_t cap = timer->wheel * WHEEL_COUNT * 2;
size_t size = cap * sizeof(struct sc_timer_data); size_t size = cap * sizeof(struct sc_timer_data);
struct sc_timer_data *alloc; struct sc_timer_data *alloc;
// Check overflow // Check overflow
if (timer->wheel > SC_CAP_MAX / 2) { if (timer->wheel > SC_CAP_MAX / 2) {
sc_timer_on_error("Out of memory. timer->wheel(%zu) ", timer->wheel);
return false; return false;
} }
alloc = sc_timer_malloc(size); alloc = sc_timer_malloc(size);
if (alloc == NULL) { if (alloc == NULL) {
sc_timer_on_error("Out of memory. size(%zu) ", size);
return false; return false;
} }
@ -121,7 +123,7 @@ 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); const size_t pos = (timeout / TICK + timer->head) & (WHEEL_COUNT - 1);
uint64_t id; uint64_t id;
size_t seq, index, wheel_pos; uint32_t seq, index, wheel_pos;
assert(timeout < UINT64_MAX); assert(timeout < UINT64_MAX);
@ -154,7 +156,7 @@ out:
void sc_timer_cancel(struct sc_timer *timer, uint64_t *id) void sc_timer_cancel(struct sc_timer *timer, uint64_t *id)
{ {
size_t pos; uint64_t pos;
if (*id == SC_TIMER_INVALID) { if (*id == SC_TIMER_INVALID) {
return; return;

View File

@ -41,15 +41,23 @@ struct sc_timer_data
struct sc_timer struct sc_timer
{ {
uint64_t timestamp; uint64_t timestamp;
size_t head; uint32_t head;
size_t wheel; uint32_t wheel;
size_t count; uint32_t count;
struct sc_timer_data *list; struct sc_timer_data *list;
}; };
#define sc_timer_malloc malloc #define sc_timer_malloc malloc
#define sc_timer_free free #define sc_timer_free free
/**
* If you want to log or abort on errors like out of memory,
* put your error function here. It will be called with printf like error msg.
*
* my_on_error(const char* fmt, ...);
*/
#define sc_timer_on_error(...)
/** /**
* @param timer Timer * @param timer Timer
* @param timestamp Current timestamp. Use monotonic timer source. * @param timestamp Current timestamp. Use monotonic timer source.

View File

@ -23,10 +23,20 @@ enable_testing()
add_executable(${PROJECT_NAME}_test url_test.c sc_url.c) add_executable(${PROJECT_NAME}_test url_test.c sc_url.c)
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=140000ul)
"${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
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) target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)
if (SANITIZER) if (SANITIZER)

View File

@ -26,150 +26,135 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
struct sc_url *url_create(const char *str, const char *default_scheme, struct sc_url *sc_url_create(const char *str)
const char *default_port)
{ {
size_t total_len = 0; const char *s1 = "%.*s%.*s%.*s%.*s%.*s%.*s%.*s%.*s";
size_t str_len = 0; const char *s2 = "%.*s%c%.*s%c%.*s%c%.*s%c%.*s%c%.*s%c%.*s%c";
bool ipv6 = false; const char *authority = "//";
size_t scheme_len = 0; unsigned long val;
size_t user_len = 0; size_t len, full_len, parts_len;
size_t host_len = 0; size_t scheme_len = 0, authority_len = 0, userinfo_len = 0;
size_t port_len = 0; size_t host_len = 0, port_len = 0, path_len = 0;
size_t path_len = 0; size_t query_len = 0, fragment_len = 0;
size_t query_len = 0;
size_t fragment_len = 0;
char *scheme = (char *) (default_scheme != NULL ? default_scheme : "");
char *userinfo = "";
char *host = NULL;
char *port = (char *) (default_port != NULL ? default_port : "");
char *path = "";
char *query = "";
char *fragment = "";
char *pos, *ptr;
const char *end;
char *scheme = "", *userinfo = "", *host = "", *port = "";
char *path = "", *query = "", *fragment = "";
char *ptr, *dest, *parse_end;
char* pos = (char*) str;
struct sc_url *url; struct sc_url *url;
end = str + strlen(str); if (str == NULL || (ptr = strstr(pos, ":")) == NULL) {
pos = (char *) str; return NULL;
ptr = strstr(pos, "://");
if (ptr != NULL) {
scheme = pos;
scheme_len = ptr - str;
pos = ptr + strlen("://");
} }
ptr = strchr(pos, '@'); scheme = pos;
if (ptr != NULL) { scheme_len = ptr - str + 1;
userinfo = pos; pos = ptr + 1;
user_len = ptr - pos;
pos = ptr + strlen("@");
}
ptr = strchr(pos, '['); if (*pos == '/' && *(pos + 1) == '/') {
if (ptr != NULL) { authority_len = 2;
char *bracket = strrchr(pos, ']'); pos += authority_len;
if (bracket == NULL) {
return NULL; ptr = strchr(pos, '@');
if (ptr != NULL) {
userinfo = pos;
userinfo_len = ptr - pos + strlen("@");
pos = ptr + 1;
} }
host = ptr + strlen("["); ptr = pos + strcspn(pos, *pos == '[' ? "]" : ":/?#");
host_len = bracket - ptr - 1;
pos = bracket + 1;
ipv6 = true;
}
ptr = strrchr(pos, ':');
if (ptr != NULL) {
char *parse_end;
unsigned long val;
if (*(ptr + 1) == '\0') {
return NULL;
}
errno = 0;
val = strtoul(ptr + 1, &parse_end, 10);
if (errno != 0 || val > 65536) {
return NULL;
}
port = ptr + 1;
port_len = parse_end - ptr - 1;
} else {
ptr = (char *) end;
}
ptr = strchr(pos, '/');
if (ptr != NULL) {
path = pos + strcspn(pos, "?");
path_len = path - pos;
}
if (host == NULL) {
host = pos; host = pos;
host_len = ptr - pos; host_len = ptr - pos + (*host == '[');
} pos = host + host_len;
const char *s1 = "%.*s://%.*s%.*s:%.*s"; if (*host == '[' && *(host + host_len - 1) != ']') {
const char *s2 = "%.*s://%.*s[%.*s]:%.*s"; return NULL;
const char *s3 = "%.*s://%.*s@%.*s:%.*s";
const char *s4 = "%.*s://%.*s@[%.*s]:%.*s";
const char *n1 = "%.*s%c://%.*s%.*s%c:%.*s";
const char *n2 = "%.*s%c://%.*s[%.*s]%c:%.*s";
const char *n3 = "%.*s%c://%.*s%c@%.*s%c:%.*s";
const char *n4 = "%.*s%c://%.*s%c@[%.*s]%c:%.*s";
const char *s5;
const char *n5;
if (ipv6) {
if (*userinfo == '\0') {
s5 = s2;
n5 = n2;
} else {
s5 = s4;
n5 = n4;
} }
} else {
if (*userinfo == '\0') { ptr = strchr(pos, ':');
s5 = s1; if (ptr != NULL) {
n5 = n1; if (*(ptr + 1) == '\0') {
} else { return NULL;
s5 = s3; }
n5 = n3;
errno = 0;
val = strtoul(ptr + 1, &parse_end, 10);
if (errno != 0 || val > 65536) {
return NULL;
}
port = ptr;
port_len = parse_end - ptr;
pos = port + port_len;
} }
} }
str_len = scheme_len + strlen("://") + user_len + (user_len ? 1 : 0) + path = pos;
host_len + port_len + 1; path_len = strcspn(path, "?#");
total_len = str_len + 1 + 1 + 1; pos = path + path_len;
url = sc_url_malloc(sizeof(struct sc_url) + total_len + str_len); ptr = strchr(pos, '?');
if (ptr != NULL) {
query = ptr;
query_len = strcspn(query, "#");
pos = query + query_len;
}
if (*pos == '#') {
fragment = pos;
fragment_len = strlen(pos);
}
full_len = scheme_len + authority_len + userinfo_len + host_len + port_len +
path_len + query_len + fragment_len + 1;
parts_len = full_len - authority_len;
parts_len += 7; // NULL characters for each part.
parts_len -= (scheme_len != 0);
parts_len -= (userinfo_len != 0);
parts_len -= (port_len != 0);
parts_len -= (query_len != 0);
parts_len -= (fragment_len != 0);
url = sc_url_malloc(sizeof(struct sc_url) + parts_len + full_len);
if (url == NULL) { if (url == NULL) {
return NULL; return NULL;
} }
sprintf(url->buf, s5, scheme_len, scheme, user_len, userinfo, host_len, len = snprintf(url->buf, full_len, s1, scheme_len, scheme, authority_len,
host, port_len, port); authority, userinfo_len, userinfo, host_len, host, port_len,
char *dest = url->buf + strlen(url->buf) + 1; port, path_len, path, query_len, query, fragment_len,
sprintf(dest, n5, scheme_len, scheme, 0, user_len, userinfo, 0, host_len, fragment);
host, 0, port_len, port); assert(len == full_len - 1);
dest = url->buf + strlen(url->buf) + 1;
scheme_len -= (scheme_len != 0); // Skip ":"
userinfo_len -= (userinfo_len != 0); // Skip "@"
port_len -= (port_len != 0); // Skip ":"
port += (port_len != 0); // Skip ":"
query_len -= (query_len != 0); // Skip "?"
query += (query_len != 0); // Skip "?"
fragment_len -= (fragment_len != 0); // Skip "#"
fragment += (fragment_len != 0); // Skip "#"
len = sprintf(dest, s2, scheme_len, scheme, 0, userinfo_len, userinfo, 0,
host_len, host, 0, port_len, port, 0, path_len, path, 0,
query_len, query, 0, fragment_len, fragment, 0);
assert(len == parts_len - 1);
url->str = url->buf; url->str = url->buf;
url->scheme = dest; url->scheme = dest;
url->userinfo = dest + scheme_len + strlen("://") + 1; url->userinfo = dest + scheme_len + 1;
url->host = url->userinfo + user_len + 1 + (*userinfo ? 1 : 0); url->host = url->userinfo + userinfo_len + 1;
url->port = url->host + host_len + 1 + (strlen(":")); url->port = url->host + host_len + 1;
url->ipv6 = ipv6; url->path = url->port + port_len + 1;
url->query = url->path + path_len + 1;
url->fragment = url->query + query_len + 1;
return url; return url;
} }

View File

@ -46,9 +46,6 @@
* *
*/ */
#define sc_url_malloc malloc
#define sc_url_free free
struct sc_url struct sc_url
{ {
const char *str; const char *str;
@ -56,14 +53,17 @@ struct sc_url
const char *host; const char *host;
const char *userinfo; const char *userinfo;
const char *port; const char *port;
const char *path;
bool ipv6; const char *query;
const char *fragment;
char buf[]; char buf[];
}; };
struct sc_url *url_create(const char *str, const char *default_scheme, #define sc_url_malloc malloc
const char *default_port); #define sc_url_free free
struct sc_url *sc_url_create(const char *str);
void sc_url_destroy(struct sc_url *url); void sc_url_destroy(struct sc_url *url);
#endif #endif

View File

@ -1,26 +1,3 @@
/*
* 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.
*/
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {

View File

@ -1,42 +1,260 @@
/*
* 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_url.h" #include "sc_url.h"
#include <stdio.h> #include <stdio.h>
#include <assert.h>
#include <string.h>
void test1(void) void test1(void)
{ {
struct sc_url *url; struct sc_url *url;
const char* f = "foo://user:password@example.com:8042/over/there?name=ferret#nose";
url = url_create("foo://user:password@example.com:8042/over/there?name=ferret#nose", NULL, NULL); url = sc_url_create(f);
assert(url != NULL);
assert(strcmp(url->str, f) == 0);
assert(strcmp(url->scheme, "foo") == 0);
assert(strcmp(url->userinfo, "user:password") == 0);
assert(strcmp(url->host, "example.com") == 0);
assert(strcmp(url->port, "8042") == 0);
assert(strcmp(url->path, "/over/there") == 0);
assert(strcmp(url->query, "name=ferret") == 0);
assert(strcmp(url->fragment, "nose") == 0);
printf("%s \n", url->str);
sc_url_destroy(url); sc_url_destroy(url);
} }
void test2(void)
{
struct sc_url *url;
const char* f = "https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top";
url = sc_url_create(f);
assert(url != NULL);
assert(strcmp(url->str, f) == 0);
assert(strcmp(url->scheme, "https") == 0);
assert(strcmp(url->userinfo, "john.doe") == 0);
assert(strcmp(url->host, "www.example.com") == 0);
assert(strcmp(url->port, "123") == 0);
assert(strcmp(url->path, "/forum/questions/") == 0);
assert(strcmp(url->query, "tag=networking&order=newest") == 0);
assert(strcmp(url->fragment, "top") == 0);
sc_url_destroy(url);
}
void test3(void)
{
struct sc_url *url;
const char* f = "ldap://[2001:db8::7]/c=GB?objectClass?one";
url = sc_url_create(f);
assert(url != NULL);
assert(strcmp(url->str, f) == 0);
assert(strcmp(url->scheme, "ldap") == 0);
assert(strcmp(url->userinfo, "") == 0);
assert(strcmp(url->host, "[2001:db8::7]") == 0);
assert(strcmp(url->port, "") == 0);
assert(strcmp(url->path, "/c=GB") == 0);
assert(strcmp(url->query, "objectClass?one") == 0);
assert(strcmp(url->fragment, "") == 0);
sc_url_destroy(url);
}
void test4(void)
{
struct sc_url *url;
const char* f = "mailto:John.Doe@example.com";
url = sc_url_create(f);
assert(url != NULL);
assert(strcmp(url->str, f) == 0);
assert(strcmp(url->scheme, "mailto") == 0);
assert(strcmp(url->userinfo, "") == 0);
assert(strcmp(url->host, "") == 0);
assert(strcmp(url->port, "") == 0);
assert(strcmp(url->path, "John.Doe@example.com") == 0);
assert(strcmp(url->query, "") == 0);
assert(strcmp(url->fragment, "") == 0);
sc_url_destroy(url);
}
void test5(void)
{
struct sc_url *url;
const char* f = "news:comp.infosystems.www.servers.unix";
url = sc_url_create(f);
assert(url != NULL);
assert(strcmp(url->str, f) == 0);
assert(strcmp(url->scheme, "news") == 0);
assert(strcmp(url->userinfo, "") == 0);
assert(strcmp(url->host, "") == 0);
assert(strcmp(url->port, "") == 0);
assert(strcmp(url->path, "comp.infosystems.www.servers.unix") == 0);
assert(strcmp(url->query, "") == 0);
assert(strcmp(url->fragment, "") == 0);
sc_url_destroy(url);
}
void test6(void)
{
struct sc_url *url;
const char* f = "tel:+1-816-555-1212";
url = sc_url_create(f);
assert(url != NULL);
assert(strcmp(url->str, f) == 0);
assert(strcmp(url->scheme, "tel") == 0);
assert(strcmp(url->userinfo, "") == 0);
assert(strcmp(url->host, "") == 0);
assert(strcmp(url->port, "") == 0);
assert(strcmp(url->path, "+1-816-555-1212") == 0);
assert(strcmp(url->query, "") == 0);
assert(strcmp(url->fragment, "") == 0);
sc_url_destroy(url);
}
void test7(void)
{
struct sc_url *url;
const char* f = "telnet://192.0.2.16:80/";
url = sc_url_create(f);
assert(url != NULL);
assert(strcmp(url->str, f) == 0);
assert(strcmp(url->scheme, "telnet") == 0);
assert(strcmp(url->userinfo, "") == 0);
assert(strcmp(url->host, "192.0.2.16") == 0);
assert(strcmp(url->port, "80") == 0);
assert(strcmp(url->path, "/") == 0);
assert(strcmp(url->query, "") == 0);
assert(strcmp(url->fragment, "") == 0);
sc_url_destroy(url);
}
void test8(void)
{
struct sc_url *url;
const char* f = "urn:oasis:names:specification:docbook:dtd:xml:4.1.2";
url = sc_url_create(f);
assert(url != NULL);
assert(strcmp(url->str, f) == 0);
assert(strcmp(url->scheme, "urn") == 0);
assert(strcmp(url->userinfo, "") == 0);
assert(strcmp(url->host, "") == 0);
assert(strcmp(url->port, "") == 0);
assert(strcmp(url->path, "oasis:names:specification:docbook:dtd:xml:4.1.2") == 0);
assert(strcmp(url->query, "") == 0);
assert(strcmp(url->fragment, "") == 0);
sc_url_destroy(url);
}
void test9(void)
{
struct sc_url *url;
const char* f = "foo://info.example.com?fred";
url = sc_url_create(f);
assert(url != NULL);
assert(strcmp(url->str, f) == 0);
assert(strcmp(url->scheme, "foo") == 0);
assert(strcmp(url->userinfo, "") == 0);
assert(strcmp(url->host, "info.example.com") == 0);
assert(strcmp(url->port, "") == 0);
assert(strcmp(url->path, "") == 0);
assert(strcmp(url->query, "fred") == 0);
assert(strcmp(url->fragment, "") == 0);
sc_url_destroy(url);
}
void test10(void)
{
struct sc_url *url;
const char* f = "tcp://127.0.0.1:9090";
url = sc_url_create(f);
assert(url != NULL);
assert(strcmp(url->str, f) == 0);
assert(strcmp(url->scheme, "tcp") == 0);
assert(strcmp(url->userinfo, "") == 0);
assert(strcmp(url->host, "127.0.0.1") == 0);
assert(strcmp(url->port, "9090") == 0);
assert(strcmp(url->path, "") == 0);
assert(strcmp(url->query, "") == 0);
assert(strcmp(url->fragment, "") == 0);
sc_url_destroy(url);
}
void test11(void)
{
struct sc_url *url = NULL;
assert(sc_url_create("127.0.0.1") == NULL);
assert(sc_url_create("") == NULL);
assert(sc_url_create("/dsad") == NULL);
assert(sc_url_create(NULL) == NULL);
assert(sc_url_create("http://127.0.0.1:") == NULL);
assert(sc_url_create("http://127.0.0.1:88888") == NULL);
assert(sc_url_create("ldap://[2001:db8::7/c=GB?objectClass?one") == NULL);
assert(sc_url_create("ldap://[2001:db8::7") == NULL);
sc_url_destroy(url);
}
#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()
{
struct sc_url *url;
fail_malloc = true;
assert(sc_url_create("tcp://127.0.0.1") == NULL);
fail_malloc = false;
url = sc_url_create("tcp://127.0.0.1");
assert(url != NULL);
sc_url_destroy(url);
}
#else
void fail_test()
{
}
#endif
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
test1(); test1();
test2();
test3();
test4();
test5();
test6();
test7();
test8();
test9();
test10();
test11();
fail_test();
return 0; return 0;
} }