diff --git a/config.h b/config.h index 2087971..c2688cc 100644 --- a/config.h +++ b/config.h @@ -76,4 +76,4 @@ #define SHIM_13 stc_hmap #define SHIM_14 uthash #define SHIM_15 verstable -// #define SHIM_15 +#define SHIM_16 askl_map \ No newline at end of file diff --git a/main.cpp b/main.cpp index dcaeb5b..1a21ec2 100644 --- a/main.cpp +++ b/main.cpp @@ -1002,7 +1002,7 @@ std::default_random_engine random_number_generator( std::chrono::steady_clock::n // Function for providing unique keys for a given blueprint in random order. // Besides the KEY_COUNT keys to be inserted, it also provides an extra KEY_COUNT / KEY_COUNT_MEASUREMENT_INTERVAL * // 1000 keys for testing failed look-ups. -template< typename blueprint > const blueprint::key_type &shuffled_unique_key( size_t index ) +template< typename blueprint > const typename blueprint::key_type &shuffled_unique_key( size_t index ) { static auto keys = []() { diff --git a/shims/askl_map/arcane/bitops.c b/shims/askl_map/arcane/bitops.c new file mode 100644 index 0000000..f4c9401 --- /dev/null +++ b/shims/askl_map/arcane/bitops.c @@ -0,0 +1,808 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifndef _BITOPS_C_ + +#define _BITOPS_C_ + +#if (defined(_MSC_VER) && (_MSC_VER >= 1400)) +#include +#endif + +#define max32(a, b) \ +((a) - (((a) - (b)) & -((int32_t)((uint32_t)((a) - (b)) >> 31)))) + +/* fast macros to test if at least one byte in a word is < n, or > n, or = 0 */ +#define __zero(x) (((x) - 0x01010101U) & ~(x) & 0x80808080U) +#define __less(x, n) (((x) - ~0U / 255 * (n)) & ~(x) & ~0U / 255 * 128) +#define __more(x, n) ((((x) + ~0U / 255 * (127 - (n))) | (x)) & ~0U / 255 * 128) +#define __between(x, m, n) \ +(((~0U / 255 * (127 + (n)) - ((x) & ~0U / 255 * 127)) & ~(x) & \ + (((x) & ~0U / 255 * 127) + ~0U / 255 * (127 - (m)))) & ~0U / 255 * 128) + +/* prefetch */ +#if (defined(__GNUC__) && \ + ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) + #define L1_CACHE 3 + #define L2_CACHE 2 + #define L3_CACHE 1 + #define NTACCESS 0 + #define PREFETCH(addr, rw, locality) \ + __builtin_prefetch(addr, rw, locality) +#elif (defined(_MSC_VER) && (_MSC_VER >= 1600)) + #define L1_CACHE _MM_HINT_T0 + #define L2_CACHE _MM_HINT_T1 + #define L3_CACHE _MM_HINT_T2 + #define NTACCESS _MM_HINT_NTA + #if defined(_M_IX86) || defined(_M_X64) || defined(_M_AMD64) + #include + #define PREFETCH(addr, rw, locality) \ + _mm_prefetch((const char *)(addr), locality) + #elif (defined(_M_ARM64) && (_MSC_VER >= 1920)) + #define PREFETCH(addr, rw, locality) \ + __prefetch(addr) + #else + #define PREFETCH(addr, rw, locality) + #endif +#else + #define L1_CACHE + #define L2_CACHE + #define L3_CACHE + #define NTACCESS + #define PREFETCH(addr, rw, locality) +#endif + +/* -------------------------------------------------------------------------- */ +/* Bitwise operations */ +/* -------------------------------------------------------------------------- */ + +static inline unsigned int __ctz(uint32_t i) +{ + unsigned long c; + + if (likely(i)) { + /* hardware implementation */ + #if (defined(__GNUC__) && \ + ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))) && \ + (defined(__i386__) || defined(__x86_64__)) || \ + (defined(__arm__) || defined(__aarch64__)) + + c = __builtin_ctz(i); + + #elif (defined(_MSC_VER) && (_MSC_VER >= 1400)) && \ + (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_ARM)) + + #pragma intrinsic(_BitScanForward) + + _BitScanForward(& c, (unsigned long) i); + + #else + /* portable software implementation */ + i &= -i; + c = 0; + if (i & 0xaaaaaaaaU) c |= 1; + if (i & 0xccccccccU) c |= 2; + if (i & 0xf0f0f0f0U) c |= 4; + if (i & 0xff00ff00U) c |= 8; + if (i & 0xffff0000U) c |= 16; + #endif + } else c = 32; + + return c; +} + +/* -------------------------------------------------------------------------- */ + +static inline unsigned int __ctzll(uint64_t i) +{ + if (likely(i)) { + /* hardware implementation */ + #if (defined(__GNUC__) && \ + ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))) && \ + (defined(__i386__) || defined(__x86_64__)) || \ + (defined(__arm__) || defined(__aarch64__)) + + return __builtin_ctzll(i); + + #elif (defined(_MSC_VER) && (_MSC_VER >= 1400)) && \ + (defined(_M_X64) || defined(_M_AMD64) || defined(_M_ARM)) + + #pragma intrinsic(_BitScanForward64) + + unsigned long ret; + _BitScanForward64(& ret, (unsigned __int64) i); + return ret; + + #else + /* portable software implementation (de Bruijn sequence) */ + static const uint8_t seq[64] = { + 0, 1, 2, 7, 3, 13, 8, 19, 4, 25, 14, 28, 9, 34, 20, 40, + 5, 17, 26, 38, 15, 46, 29, 48, 10, 31, 35, 54, 21, 50, 41, 57, + 63, 6, 12, 18, 24, 27, 33, 39, 16, 37, 45, 47, 30, 53, 49, 56, + 62, 11, 23, 32, 36, 44, 52, 55, 61, 22, 43, 51, 60, 42, 59, 58 + }; + + return seq[((i & -i) * 0x0218a392cd3d5dbfULL) >> 58]; + + #endif + } else return 64; +} + +/* -------------------------------------------------------------------------- */ + +static inline unsigned int __clz(uint32_t i) +{ + if (likely(i)) { + /* hardware implementation */ + #if (defined(__GNUC__) && \ + ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))) && \ + (defined(__i386__) || defined(__x86_64__)) || \ + (defined(__arm__) || defined(__aarch64__)) + + return __builtin_clz(i); + + #elif (defined(_MSC_VER) && (_MSC_VER >= 1400)) && \ + (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_ARM)) + + #pragma intrinsic(_BitScanReverse) + + unsigned long ret; + + _BitScanReverse(& ret, (unsigned long) i); + + return 31 - ret; + + #else + /* portable software implementation (de Bruijn sequence) */ + static const char seq[32] = { + 0, 31, 9, 30, 3, 8, 13, 29, 2, 5, 7, 21, 12, 24, 28, 19, + 1, 10, 4, 14, 6, 22, 25, 20, 11, 15, 23, 26, 16, 27, 17, 18 + }; + + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + i ++; + + return seq[i * 0x076be629 >> 27]; + + #endif + } else return 32; +} + +/* -------------------------------------------------------------------------- */ + +static inline unsigned int __clzll(uint64_t i) +{ + if (likely(i)) { + /* hardware implementation */ + #if (defined(__GNUC__) && \ + ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))) && \ + (defined(__i386__) || defined(__x86_64__)) || \ + (defined(__arm__) || defined(__aarch64__)) + + return __builtin_clzll(i); + + #elif (defined(_MSC_VER) && (_MSC_VER >= 1400)) && \ + (defined(_M_X64) || defined(_M_AMD64) || defined(_M_ARM)) + + #pragma intrinsic(_BitScanReverse64) + + unsigned long ret; + + _BitScanReverse64(& ret, (unsigned __int64) i); + + return 63 - ret; + + #else + /* portable software implementation (de Bruijn sequence) */ + static const char seq[64] = { + 0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, + 54, 58, 35, 52, 50, 42, 21, 44, 38, 32, 29, 23, 17, 11, 4, 62, + 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, 31, 22, 10, 45, + 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63 + }; + + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + i |= i >> 32; + + return 63 - seq[(i * 0x03f79d71b4cb0a89ULL) >> 58]; + + #endif + } else return 64; +} + +/* -------------------------------------------------------------------------- */ + +static inline uint32_t __msb(uint32_t i) +{ + /* hardware implementation */ + #if (defined(__GNUC__) && \ + ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))) && \ + (defined(__i386__) || defined(__x86_64__)) || \ + (defined(__arm__) || defined(__aarch64__)) + + return 1 << (__builtin_clz(i) ^ 31); + + #elif (defined(_MSC_VER) && (_MSC_VER >= 1400)) && \ + (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_ARM)) + + #pragma intrinsic(_BitScanReverse) + + unsigned long idx; + + _BitScanReverse(& idx, (unsigned long) i); + + return 1 << (idx ^ 31); + + #else + /* portable software implementation (de Bruijn sequence) */ + static const uint8_t seq[] = { 0, 5, 1, 6, 4, 3, 2, 7 }; + + i |= i >> 1; i |= i >> 2; i |= i >> 4; + + return 1 << seq[(uint8_t) (i * 0x1D) >> 5]; + + #endif +} + +/* -------------------------------------------------------------------------- */ + +static inline uint32_t __bswap32(uint32_t i) +{ + #if (defined(__GNUC__)) + + return __builtin_bswap32(i); + + #elif (defined(_MSC_VER) && (_MSC_VER >= 1400)) + + #pragma intrinsic(_byteswap_ulong) + + return _byteswap_ulong(i); + + #else + + /* portable software implementation */ + return ( + ((i >> 24) & 0x000000ff) | + ((i >> 8) & 0x0000ff00) | + ((i << 8) & 0x00ff0000) | + ((i << 24) & 0xff000000) + ); + + #endif +} + +/* -------------------------------------------------------------------------- */ + +static inline uint64_t __bswap64(uint64_t i) +{ + #if (defined(__GNUC__)) + + return __builtin_bswap64(i); + + #elif (defined(_MSC_VER) && (_MSC_VER >= 1400)) + + #pragma intrinsic(_byteswap_uint64) + + return _byteswap_uint64(i); + + #else + + /* portable software implementation */ + return ( + (i << 56) | + ((i & 0xff00) << 40) | + ((i & 0xff0000) << 24) | + ((i & 0xff000000) << 8) | + ((i & 0xff00000000) >> 8) | + ((i & 0xff0000000000) >> 24) | + ((i & 0xff000000000000) >> 40) | + ((i & 0xff00000000000000) >> 56) + ); + + #endif +} + +/* -------------------------------------------------------------------------- */ + +static inline unsigned int __zero_idx(uint32_t i) +{ + #if (defined(BIG_ENDIAN_HOST)) + i = __bswap32(i); + #endif + return __ctz(i) >> 3; +} + +/* -------------------------------------------------------------------------- */ + +static inline unsigned int __zero_idx64(uint64_t i) +{ + #if (defined(BIG_ENDIAN_HOST)) + i = __bswap64(i); + #endif + return __ctzll(i) >> 3; +} + +/* -------------------------------------------------------------------------- */ + +static inline unsigned int __msb_idx(uint32_t i) +{ + return 31 - __clz(i); +} + +/* -------------------------------------------------------------------------- */ + +static inline unsigned int __msb_idx64(const uint64_t i) +{ + return 63 - __clzll(i); +} + +/* -------------------------------------------------------------------------- */ + +static inline size_t __next_pow2(size_t size) +{ + size --; + size |= size >> 1; + size |= size >> 2; + size |= size >> 4; + size |= size >> 8; + size |= size >> 16; + #if (SIZE_MAX > UINT32_MAX) + size |= size >> 32; + #endif + size ++; + size += (size == 0); + + return size; +} + +/* -------------------------------------------------------------------------- */ + +static inline int __is_pow2_multiple(uint64_t value, uint32_t p) +{ + return (value & ((1ULL << p) - 1)) == 0; +} + +/* -------------------------------------------------------------------------- */ + +static inline int __is_pow5_multiple(uint64_t value, const uint32_t p) +{ + /* returns true if value is divisible by 5^p */ + const uint64_t m_inv_5 = 14757395258967641293U; + const uint64_t n_div_5 = 3689348814741910323U; + uint32_t count = 0; + + while (1) { + /* simulate a division by using the modular inverse of 5 */ + value *= m_inv_5; + /* n_div_5 is the largest 64 bits multiple of 5 */ + if (value > n_div_5) break; + count ++; + } + + return (count >= p); +} + +/* -------------------------------------------------------------------------- */ +/* Other operations */ +/* -------------------------------------------------------------------------- */ + +static inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t *high) +{ + #if (defined(__SIZEOF_INT128__)) + __uint128_t result = (__uint128_t) a * (__uint128_t) b; + *high = (uint64_t) (result >> 64); + return (uint64_t) result; + #elif (defined(_MSC_VER) && (_MSC_VER >= 1400)) && \ + (defined(_M_X64) || defined(_M_AMD64) || defined(_M_ARM)) + return __umul128(a, b, high); + #else + const uint32_t a_lo = (uint32_t) a; + const uint32_t a_hi = (uint32_t) (a >> 32); + const uint32_t b_lo = (uint32_t) b; + const uint32_t b_hi = (uint32_t) (b >> 32); + + const uint64_t b00 = (uint64_t) a_lo * b_lo; + const uint64_t b01 = (uint64_t) a_lo * b_hi; + const uint64_t b10 = (uint64_t) a_hi * b_lo; + const uint64_t b11 = (uint64_t) a_hi * b_hi; + + const uint32_t b00_lo = (uint32_t) b00; + const uint32_t b00_hi = (uint32_t) (b00 >> 32); + + const uint64_t mid1 = b10 + b00_hi; + const uint32_t mid1_lo = (uint32_t) (mid1); + const uint32_t mid1_hi = (uint32_t) (mid1 >> 32); + + const uint64_t mid2 = b01 + mid1_lo; + const uint32_t mid2_lo = (uint32_t) (mid2); + const uint32_t mid2_hi = (uint32_t) (mid2 >> 32); + + const uint64_t r_hi = b11 + mid1_hi + mid2_hi; + const uint64_t r_lo = ((uint64_t) mid2_lo << 32) | b00_lo; + + *high = r_hi; + return r_lo; + #endif +} + +/* -------------------------------------------------------------------------- */ + +#define shr128(lo, hi, shift) (((hi) << (64 - (shift))) | ((lo) >> (shift))) + +static inline uint64_t mul_shift64(uint64_t n, const uint64_t *mul, int32_t i) +{ + uint64_t high1; // 128 + const uint64_t low1 = umul128(n, mul[1], & high1); // 64 + uint64_t high0; // 64 + umul128(n, mul[0], & high0); // 0 + const uint64_t sum = high0 + low1; + if (sum < high0) high1 ++; /* carry over into high1 */ + return shr128(sum, high1, i - 64); +} + +/* -------------------------------------------------------------------------- */ +/* CRC8 */ +/* -------------------------------------------------------------------------- */ + +/* crc8 lookup table (Maxim/Dallas 1 wire) */ +static const uint8_t _crc8_lut[256] = { + 0x00, 0x5e, 0xbc, 0xe2, 0x61, 0x3f, 0xdd, 0x83, + 0xc2, 0x9c, 0x7e, 0x20, 0xa3, 0xfd, 0x1f, 0x41, + 0x9d, 0xc3, 0x21, 0x7f, 0xfc, 0xa2, 0x40, 0x1e, + 0x5f, 0x01, 0xe3, 0xbd, 0x3e, 0x60, 0x82, 0xdc, + 0x23, 0x7d, 0x9f, 0xc1, 0x42, 0x1c, 0xfe, 0xa0, + 0xe1, 0xbf, 0x5d, 0x03, 0x80, 0xde, 0x3c, 0x62, + 0xbe, 0xe0, 0x02, 0x5c, 0xdf, 0x81, 0x63, 0x3d, + 0x7c, 0x22, 0xc0, 0x9e, 0x1d, 0x43, 0xa1, 0xff, + 0x46, 0x18, 0xfa, 0xa4, 0x27, 0x79, 0x9b, 0xc5, + 0x84, 0xda, 0x38, 0x66, 0xe5, 0xbb, 0x59, 0x07, + 0xdb, 0x85, 0x67, 0x39, 0xba, 0xe4, 0x06, 0x58, + 0x19, 0x47, 0xa5, 0xfb, 0x78, 0x26, 0xc4, 0x9a, + 0x65, 0x3b, 0xd9, 0x87, 0x04, 0x5a, 0xb8, 0xe6, + 0xa7, 0xf9, 0x1b, 0x45, 0xc6, 0x98, 0x7a, 0x24, + 0xf8, 0xa6, 0x44, 0x1a, 0x99, 0xc7, 0x25, 0x7b, + 0x3a, 0x64, 0x86, 0xd8, 0x5b, 0x05, 0xe7, 0xb9, + 0x8c, 0xd2, 0x30, 0x6e, 0xed, 0xb3, 0x51, 0x0f, + 0x4e, 0x10, 0xf2, 0xac, 0x2f, 0x71, 0x93, 0xcd, + 0x11, 0x4f, 0xad, 0xf3, 0x70, 0x2e, 0xcc, 0x92, + 0xd3, 0x8d, 0x6f, 0x31, 0xb2, 0xec, 0x0e, 0x50, + 0xaf, 0xf1, 0x13, 0x4d, 0xce, 0x90, 0x72, 0x2c, + 0x6d, 0x33, 0xd1, 0x8f, 0x0c, 0x52, 0xb0, 0xee, + 0x32, 0x6c, 0x8e, 0xd0, 0x53, 0x0d, 0xef, 0xb1, + 0xf0, 0xae, 0x4c, 0x12, 0x91, 0xcf, 0x2d, 0x73, + 0xca, 0x94, 0x76, 0x28, 0xab, 0xf5, 0x17, 0x49, + 0x08, 0x56, 0xb4, 0xea, 0x69, 0x37, 0xd5, 0x8b, + 0x57, 0x09, 0xeb, 0xb5, 0x36, 0x68, 0x8a, 0xd4, + 0x95, 0xcb, 0x29, 0x77, 0xf4, 0xaa, 0x48, 0x16, + 0xe9, 0xb7, 0x55, 0x0b, 0x88, 0xd6, 0x34, 0x6a, + 0x2b, 0x75, 0x97, 0xc9, 0x4a, 0x14, 0xf6, 0xa8, + 0x74, 0x2a, 0xc8, 0x96, 0x15, 0x4b, 0xa9, 0xf7, + 0xb6, 0xe8, 0x0a, 0x54, 0xd7, 0x89, 0x6b, 0x35 +}; + +/* -------------------------------------------------------------------------- */ + +static inline uint8_t _crc8(const char *string, size_t len) +{ + const uint8_t *p = (const uint8_t *) string; + uint8_t crc = 0xff; + while (len --) crc = _crc8_lut[crc ^ *p ++]; + return crc; +} + +/* -------------------------------------------------------------------------- */ +/* CRC7 */ +/* -------------------------------------------------------------------------- */ + +/* crc7 lookup table (polynomial x^7 + x^3 + 1) */ +static const uint8_t _crc7_lut[256] = { + 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, + 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, + 0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26, + 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e, + 0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d, + 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45, + 0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14, + 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c, + 0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b, + 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13, + 0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42, + 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a, + 0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, + 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21, + 0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70, + 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, + 0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e, + 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36, + 0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67, + 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f, + 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, + 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, + 0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55, + 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d, + 0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a, + 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52, + 0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03, + 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b, + 0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28, + 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60, + 0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31, + 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79 +}; + +/* -------------------------------------------------------------------------- */ + +static inline uint8_t _crc7(const char *string, size_t len) +{ + const uint8_t *p = (const uint8_t *) string; + uint8_t crc = 0; + while (len --) crc = _crc7_lut[(crc << 1) ^ *p ++]; + return crc & 0x7f; +} + +/* -------------------------------------------------------------------------- */ +/* Atomics */ +/* -------------------------------------------------------------------------- */ + +#if ((defined(_MSC_VER)) && ((_MSC_VER >= 1300) || (defined(_M_IX86)))) || \ + ((defined(__GNUC__)) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 1)) || \ + (__GNUC__ > 4) || defined(__i386__) || defined(__x86_64__))) || \ + ((defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L))) +#define HAS_ATOMICS +#endif + +#ifdef HAS_ATOMICS + +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) +#include +#define _ATOMIC _Atomic +#else +#define _ATOMIC volatile +#endif + +#if ((defined(_MSC_VER)) && (_MSC_VER == 1300)) + extern long __cdecl _InterlockedCompareExchange( + long volatile *Destination, long Exchange, long Comparand); + extern long __cdecl _InterlockedExchangeAdd( + long volatile *Addend, long Value); + void _ReadWriteBarrier(void); + #pragma intrinsic(_InterlockedCompareExchange) + #pragma intrinsic(_InterlockedExchangeAdd) + #pragma intrinsic(_ReadWriteBarrier) +#endif + +static inline int _atomic_cas(_ATOMIC int *ptr, int expected, int desired) +{ + #if (defined(_MSC_VER)) + #if (_MSC_VER >= 1300) + return _InterlockedCompareExchange( + (volatile long *) ptr, + (long) desired, + (long) expected + ) == expected; + #elif (defined(_M_IX86)) + { + long result; + __asm { + mov eax, expected + mov ecx, ptr + mov edx, desired + lock cmpxchg [ecx], edx + mov result, eax + } + return result == expected; + } + #endif + #elif (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) + return atomic_compare_exchange_weak_explicit( + ptr, + & expected, + desired, + memory_order_acq_rel, + memory_order_relaxed + ); + #elif (defined(__GNUC__)) + #if ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 7))) + return __atomic_compare_exchange_n( + ptr, + & expected, + desired, + 1, + __ATOMIC_ACQ_REL, + __ATOMIC_RELAXED + ); + #elif ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 1)) + return __sync_bool_compare_and_swap(ptr, expected, desired); + #elif defined(__i386__) || defined(__x86_64__) + int prev; + __asm__ __volatile__( + "lock; cmpxchgl %2, %1" + : "=a"(prev), "+m"(*ptr) + : "r"(desired), "0"(expected) + : "memory", "cc" + ); + return prev == expected; + #endif + #endif +} + +/* -------------------------------------------------------------------------- */ + +static inline int _atomic_ldr(_ATOMIC int *ptr) +{ + #if (defined(_MSC_VER)) + int ret = *ptr; + #if _MSC_VER >= 1300 + _ReadWriteBarrier(); + #elif (defined(_M_IX86)) + __asm { /* empty, acts as barrier */ } + #endif + return ret; + #elif (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) + return atomic_load_explicit(ptr, memory_order_relaxed); + #elif (defined(__GNUC__)) + #if ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 7))) + return __atomic_load_n(ptr, __ATOMIC_RELAXED); + #elif ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 1)) + return __sync_fetch_and_add(ptr, 0); + #elif defined(__i386__) || defined(__x86_64__) + int value; + __asm__ __volatile__( + "movl %1, %0" + : "=r"(value) + : "m"(*ptr) + : "memory" + ); + return value; + #endif + #endif +} + +/* -------------------------------------------------------------------------- */ + +static inline void _atomic_str(_ATOMIC int *ptr, int value) +{ + #if (defined(_MSC_VER)) + #if _MSC_VER >= 1300 + _ReadWriteBarrier(); + #endif + *ptr = value; + #elif (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) + atomic_store_explicit(ptr, value, memory_order_release); + #elif (defined(__GNUC__)) + #if ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 7))) + __atomic_store_n(ptr, value, __ATOMIC_RELEASE); + #elif ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 1)) + __sync_lock_test_and_set(ptr, value); + #elif defined(__i386__) || defined(__x86_64__) + __asm__ __volatile__( + "movl %1, %0" + : "=m"(*ptr) + : "r"(value) + : "memory" + ); + #endif + #endif +} + +/* -------------------------------------------------------------------------- */ + +static inline int _atomic_add(_ATOMIC int *ptr, int val) +{ + #if (defined(_MSC_VER)) + #if _MSC_VER >= 1300 + return _InterlockedExchangeAdd((long volatile *) ptr, (long) val); + #elif (defined(_M_IX86)) + long result; + __asm { + mov ecx, ptr + mov eax, val + lock xadd [ecx], eax + mov result, eax + } + return result; + #endif + #elif (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) + return atomic_fetch_add_explicit(ptr, val, memory_order_acq_rel); + #elif (defined(__GNUC__)) + #if ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 7))) + return __atomic_fetch_add(ptr, val, __ATOMIC_ACQ_REL); + #elif ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 1)) + return __sync_fetch_and_add(ptr, val); + #elif defined(__i386__) || defined(__x86_64__) + int result; + __asm__ __volatile__( + "lock; xaddl %0, %1" + : "=r"(result), "+m"(*ptr) + : "0"(val) + : "memory" + ); + return result; + #endif + #endif +} + +/* -------------------------------------------------------------------------- */ + +static inline int _atomic_sub(_ATOMIC int *ptr, int val) +{ + #if (defined(_MSC_VER)) + #if _MSC_VER >= 1300 + return _InterlockedExchangeAdd((long volatile *) ptr, - (long) val); + #elif (defined(_M_IX86)) + long result; + long neg_val = -val; + __asm { + mov ecx, ptr + mov eax, neg_val + lock xadd [ecx], eax + mov result, eax + } + return result; + #endif + #elif (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) + return atomic_fetch_sub_explicit(ptr, val, memory_order_release); + #elif (defined(__GNUC__)) + #if ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 7))) + return __atomic_fetch_sub(ptr, val, __ATOMIC_RELEASE); + #elif ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 1)) + return __sync_fetch_and_sub(ptr, val); + #elif defined(__i386__) || defined(__x86_64__) + int result; + int neg_val = -val; + __asm__ __volatile__( + "lock; xaddl %0, %1" + : "=r"(result), "+m"(*ptr) + : "0"(neg_val) + : "memory" + ); + return result; + #endif + #endif +} + +/* -------------------------------------------------------------------------- */ + +#endif + +#endif diff --git a/shims/askl_map/arcane/htable.c b/shims/askl_map/arcane/htable.c new file mode 100644 index 0000000..f76aa7f --- /dev/null +++ b/shims/askl_map/arcane/htable.c @@ -0,0 +1,318 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifdef ASKL_MAP_H + +/* -------------------------------------------------------------------------- */ +/* Hashtable internals */ +/* -------------------------------------------------------------------------- */ + +#define HASH_COUNT 8 /* number of hash functions */ +#define HASH_RETRY 4 /* number of retries if the bucket is full */ + +#ifndef MAX_LOAD_FACTOR +#define MAP_MAX_LOAD 813 /* maximum load factor (81.3%) */ +#else +#define MAP_MAX_LOAD ((MAX_LOAD_FACTOR) * 1000) +#endif +#define MAP_MIN_SIZE 4 /* smallest hash index size */ + +/* + * MAP_MAX_LOAD: maximum hash-index load before growth. + * Recommended values: + * 813 (81.3% load) - Optimal speed/memory balance (32/64 bits) [default] + * 909 (90.9% load) - 3% slower (64 bits only) + * 971 (97.1% load) - Maximum density (64 bits only) + */ + +#define REHASH_ONLY -1 +#define CREATE_ONLY 0 +#define STORE_VALUE 1 +#define MODIFY_ONLY 2 + +#if (UINTPTR_MAX == 0xffffffffffffffffULL) + #if (defined(__x86_64__) || defined(_M_X64)) + #define TAG_SHIFT 0 + #if (defined(__LA57__)) + /* 5-level paging enabled */ + #define TAG_MASK 0xfe00000000000000ULL + #else + #define TAG_MASK 0xffff000000000000ULL + #endif + #elif (defined(__aarch64__) || defined(_M_ARM64)) + #if (defined(__ARM_FEATURE_PAC_DEFAULT)) + /* PAC enabled - can't use high bits */ + #define TAG_SHIFT 61 + #define TAG_MASK 0x7ULL + #else + #define TAG_SHIFT 0 + #define TAG_MASK 0xffff000000000000ULL + #endif + #else + #define TAG_SHIFT 61 + #define TAG_MASK 0x7ULL + #endif +#elif (UINTPTR_MAX == 0xffffffffU) + #define TAG_SHIFT 30 + #define TAG_MASK 0x3UL +#endif + +/* Extract tag from hash (high bits) */ +#define HASHTAG(hash) ((((uintptr_t) (hash)) >> TAG_SHIFT) & TAG_MASK) + +/* Tag pointer */ +#define TAG_PTR(ptr, hash) ((_Item *) (((uintptr_t) (ptr)) | HASHTAG(hash))) + +/* Clean pointer */ +#define GET_PTR(ptr) ((_Item *) (((uintptr_t) (ptr)) & ~TAG_MASK)) + +/* Extract tag from pointer */ +#define GET_TAG(ptr) (((uintptr_t) (ptr)) & TAG_MASK) + +/* -------------------------------------------------------------------------- */ +#if (UINTPTR_MAX == 0xffffffffffffffffULL) /* 64 bits */ +/* -------------------------------------------------------------------------- */ +/* rapidhashNano (author: Nicolas De Carli) */ +/* -------------------------------------------------------------------------- */ + +/* rapidhash secret constants */ +static const uint64_t _rapid_secret[8] = { + 0x2d358dccaa6c78a5ULL, 0x8bb84b93962eacc9ULL, + 0x4b33a62ed433d4a3ULL, 0x4d5a2da51de1aa47ULL, + 0xa0761d6478bd642fULL, 0xe7037ed1a0b428dbULL, + 0x90ed1765281c388cULL, 0xaaaaaaaaaaaaaaaaULL +}; + +/* -------------------------------------------------------------------------- */ + +static uint64_t _rapid_read64(const uint8_t *p) +{ + uint64_t result; + memcpy(& result, p, sizeof(result)); + #if defined(BIG_ENDIAN_HOST) + return __bswap64(result); + #else + return result; + #endif +} + +/* -------------------------------------------------------------------------- */ + +static uint64_t _rapid_read32(const uint8_t *p) +{ + uint32_t result; + memcpy(& result, p, sizeof(result)); + #if defined(BIG_ENDIAN_HOST) + return __bswap32(result); + #else + return result; + #endif +} + +/* -------------------------------------------------------------------------- */ + +static void _rapid_mum(uint64_t *a, uint64_t *b) +{ + uint64_t high; + uint64_t low = umul128(*a, *b, & high); + *a = low; + *b = high; +} + +/* -------------------------------------------------------------------------- */ + +static uint64_t _rapid_mix(uint64_t a, uint64_t b) +{ + _rapid_mum(& a, & b); + return a ^ b; +} + +/* -------------------------------------------------------------------------- */ + +static void _check_seed(uintptr_t *seed) +{ + *seed ^= _rapid_mix(*seed ^ _rapid_secret[2], _rapid_secret[1]); +} + +/* -------------------------------------------------------------------------- */ + +static uint64_t _hash(const char *key, size_t len, uint64_t seed) +{ + const uint8_t *p = (const uint8_t *) key; + uint64_t a = 0, b = 0; + size_t i = len; + + if (likely(len <= 16)) { + if (len >= 4) { + seed ^= len; + if (len >= 8) { + const uint8_t *plast = p + len - 8; + a = _rapid_read64(p); + b = _rapid_read64(plast); + } else { + const uint8_t *plast = p + len - 4; + a = _rapid_read32(p); + b = _rapid_read32(plast); + } + } else if (len > 0) { + a = (((uint64_t) p[0]) << 45) | p[len - 1]; + b = p[len >> 1]; + } else { + a = b = 0; + } + } else { + if (i > 48) { + uint64_t see1 = seed, see2 = seed; + do { + seed = _rapid_mix( + _rapid_read64(p) ^ _rapid_secret[0], + _rapid_read64(p + 8) ^ seed + ); + see1 = _rapid_mix( + _rapid_read64(p + 16) ^ _rapid_secret[1], + _rapid_read64(p + 24) ^ see1 + ); + see2 = _rapid_mix( + _rapid_read64(p + 32) ^ _rapid_secret[2], + _rapid_read64(p + 40) ^ see2 + ); + p += 48; + i -= 48; + } while (i > 48); + seed ^= see1; + seed ^= see2; + } + if (i > 16) { + seed = _rapid_mix( + _rapid_read64(p) ^ _rapid_secret[2], + _rapid_read64(p + 8) ^ seed + ); + if (i > 32) { + seed = _rapid_mix( + _rapid_read64(p + 16) ^ _rapid_secret[2], + _rapid_read64(p + 24) ^ seed + ); + } + } + a = _rapid_read64(p + i - 16) ^ i; + b = _rapid_read64(p + i - 8); + } + + a ^= _rapid_secret[1]; + b ^= seed; + _rapid_mum(& a, & b); + + return _rapid_mix(a ^ _rapid_secret[7], b ^ _rapid_secret[1] ^ i); +} + +/* -------------------------------------------------------------------------- */ +#elif (UINTPTR_MAX == 0xffffffffU) /* 32 bits */ +/* -------------------------------------------------------------------------- */ +/* wyhash32 (author: 王一 Wang Yi ) */ +/* -------------------------------------------------------------------------- */ + +static uint32_t _wyr32(const uint8_t *p) +{ + uint32_t result; + memcpy(& result, p, sizeof(result)); + #if defined(BIG_ENDIAN_HOST) + return __bswap32(result); + #else + return result; + #endif +} + +/* -------------------------------------------------------------------------- */ + +static uint32_t _wyr24(const uint8_t *p, uint32_t k) +{ + return (((uint32_t) p[0]) << 16) | (((uint32_t) p[k >> 1]) << 8) | p[k - 1]; +} + +/* -------------------------------------------------------------------------- */ + +static void _wymix32(uint32_t *a, uint32_t *b) +{ + uint64_t c = *a ^ 0x53c5ca59u; + c *= *b ^ 0x74743c1bu; + *a = (uint32_t) c; + *b = (uint32_t) (c >> 32); +} + +/* -------------------------------------------------------------------------- */ + +static void _check_seed(uintptr_t *seed) +{ + /* wyhash32 known bad seeds */ + if ((*seed == 0x429dacdd) || + (*seed == 0x51a43a0f) || + (*seed == 0x522235ae) || + (*seed == 0x99ac2b20) || + (*seed == 0x9a4f1376) || + (*seed == 0xd637dbf3)) + (*seed) ++; +} + +/* -------------------------------------------------------------------------- */ + +static uint32_t _hash(const char *key, size_t len, uint32_t seed) +{ + const uint8_t *p = (const uint8_t *) key; + uint32_t i, see1 = len; + + _wymix32(& seed, & see1); + + for (i = len; i > 8; i -= 8, p += 8) { + seed ^= _wyr32(p); + see1 ^= _wyr32(p + 4); + _wymix32(& seed, & see1); + } + + if (i >= 4) { + seed ^= _wyr32(p); + see1 ^= _wyr32(p + i - 4); + } else if (i) seed ^= _wyr24(p, i); + + _wymix32(& seed, & see1); + _wymix32(& seed, & see1); + + return seed ^ see1; +} + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ + +#endif diff --git a/shims/askl_map/askl.h b/shims/askl_map/askl.h new file mode 100644 index 0000000..1acde84 --- /dev/null +++ b/shims/askl_map/askl.h @@ -0,0 +1,322 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2025 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifndef ASKL_H + +#define ASKL_H + +/** ASKL version string */ +#define ASKL_VERSION "0.3.9" + +/** ASKL API revision */ +#define __ASKL__ 1390 + +/* check the compiler settings */ +#ifdef __GNUC__ + /* gcc settings */ + #ifdef _WIN32 + #ifndef WIN32 + #define WIN32 + #endif + #else + #ifndef __APPLE__ + #ifndef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 200809L + #endif + #ifndef _XOPEN_SOURCE + #define _XOPEN_SOURCE 700 + #endif + #define _SVID_SOURCE 1 + #else + #define _DARWIN_C_SOURCE + #endif + #define _DEFAULT_SOURCE + #define _THREAD_SAFE + #define _REENTRANT + #endif +#endif + +/* standard ISO C99/POSIX includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* check system endianness */ +#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ + __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(__APPLE__) && defined(__LITTLE_ENDIAN__)) || \ + (defined(__i386) || defined(__x86_64__) || defined(__ia64) || \ + defined(_M_IX86) || defined(_M_X64) || defined(_M_IA64) || \ + defined(__ARMEL__) || defined(_M_ARM) || defined(__MIPSEL__)) + #define LITTLE_ENDIAN_HOST +#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ + __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(__APPLE__) && defined(__BIG_ENDIAN__)) || \ + (defined(__sparc) || defined(__powerpc__) || defined(__ppc__) || \ + defined(__mc68000) || defined(__ARMEB__) || defined(__MIPSEB__)) + #define BIG_ENDIAN_HOST +#endif + +#if ! defined(_MSC_VER) || _MSC_VER > 1300 +/* Microsoft Visual C++ 6.0 is missing these includes */ +#include +#include +#include +#if _MSC_VER >= 1400 +#include +#endif +#endif + +/* specific macros cleanup */ +#undef format +#undef UNUSED +#undef INTERNAL +#undef likely +#undef unlikely + +#ifndef UNUSED + #ifdef __GNUC__ + #define UNUSED __attribute__ ((unused)) + #else + #define UNUSED + #endif +#endif + +#if (! defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)) + #ifdef _MSC_VER + #if (_MSC_VER >= 1400) + #if (_MSC_VER < 1900) + #define restrict __restrict + #endif + #else + #define restrict + #endif + #elif (defined(__GNUC__)) + #define restrict __restrict__ + #else + #define restrict + #endif +#endif + +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112)) + #define ALIGNOF(type) _Alignof(type) +#elif (defined(__GNUC__) || defined(__clang__)) + #define ALIGNOF(type) __alignof__(type) +#elif defined(_MSC_VER) + #define ALIGNOF(type) __alignof(type) +#endif + +#ifdef __GNUC__ + /* specific macros */ + #define format(f, v) __attribute__ ((format(printf, (f), (v)))) + #ifndef WIN32 + #define ASKL_API + #ifdef DEBUG + #define INTERNAL + #ifndef __APPLE__ + #include + #define backtrace() \ + do { \ + void *array[10]; \ + size_t size; \ + char **strings; \ + size_t i; \ + \ + size = backtrace(array, 10); \ + strings = backtrace_symbols(array, size); \ + \ + fprintf(stderr, "======= BACKTRACE =======\n"); \ + for (i = 0; i < size; i++) \ + printf ("%s\n", strings[i]); \ + fprintf(stderr, "=========================\n"); \ + \ + free (strings); \ + } while (0); + #else + #define backtrace() + #endif + typedef uint32_t unaligned_uint32_t __attribute__((aligned(1))); + #else + #ifndef __APPLE__ + #define INTERNAL __attribute__((visibility("internal"))) + #else + #define INTERNAL __attribute__((visibility("hidden"))) + #endif + #define backtrace() + typedef uint32_t unaligned_uint32_t; + #endif + #endif + #define likely(x) __builtin_expect(!!(x), 1) + #define unlikely(x) __builtin_expect(!!(x), 0) + + /* handle the dllimport related noise in pthread.h with winegcc */ + #if (defined(__WINE__) || defined(__MINGW32__)) + #define dllimport + #endif +#endif + +/* basic fixes for WIN32 includes (Wine) */ +#ifdef WIN32 + #define WIN32_LEAN_AND_MEAN + #include + #include + #include + #if ! defined(off_t) && defined(__WINE__) + #define off_t _off_t + #endif + + #if defined(BUILDING_DLL) || defined(_USRDLL) || defined(_WINDLL) + #define ASKL_API __declspec(dllexport) + #else + #define ASKL_API __declspec(dllimport) + #endif + + #define INTERNAL + + #ifndef socklen_t + #define socklen_t size_t + #endif + + #ifndef ssize_t + #define ssize_t long + #endif +#endif + +/* project specific macros */ +#define STR(x) STRINGIFY(x) +#define ERR(c, f) #c "()::" #f "() @ " __FILE__ ":" STR(__LINE__) +#define die(s) do { fprintf(stderr, (s)); abort(); } while (0) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define STATIC_ASSERT(cond, name) typedef int assert_##name[(cond) ? 1 : -1] + +#ifdef DEBUG + #define debug(...) do { fprintf(stderr, __VA_ARGS__); } while (0) +#else + #if defined(_MSC_VER) && _MSC_VER < 1300 + #pragma warning(disable:4002) + #define debug(__VA_ARGS__) do { } while (0) + #else + #define debug(...) do { } while (0) + #endif +#endif + +#ifndef CALLBACK + #ifdef __GNUC__ + #ifdef __i386 + #define CALLBACK __attribute__ ((regparm(1))) + #else + /* x86_64 already uses registers to pass function parameters */ + #define CALLBACK + #endif + #else + /* XXX the WIN32 __fastcall calling convention uses too many + registers to be suitable for callbacks */ + #define CALLBACK + #endif +#endif + +/* various OS dependant definitions */ +#include "compat/askl_compat_layer.h" + +/* external packages */ +#ifndef ASKL_MINIMAL + +/* OpenSSL */ +#if (_ENABLE_SSL && HAS_SSL) + #include + #include + #include +#else + #undef _ENABLE_SSL +#endif + +/* MySQL */ +#if (_ENABLE_MYSQL && HAS_MYSQL) + #include +#else + #undef _ENABLE_MYSQL +#endif + +/* SQLite */ +#if (_ENABLE_SQLITE && HAS_SQLITE) + #include +#else + #undef _ENABLE_SQLITE +#endif + +#endif + +/* privileges separation */ +#ifdef _ENABLE_PRIVILEGE_SEPARATION + /* force inclusion of the server code if we use privilege separation */ + #ifndef _ENABLE_SERVER + #define _ENABLE_SERVER + #endif + #define OP_AUTH 0x0A + #define OP_BIND 0x0B + #define OP_CONF 0x0C + #define OP_EXIT 0x0E + extern int server_privileged_call(int opcode, const void *cmd, size_t len); +#endif + +/* environment */ +ASKL_API extern char *working_directory; + +#ifndef PREFIX + #define PREFIX (working_directory) +#endif + +#ifndef CONFDIR + #define CONFDIR (working_directory) +#endif + +#ifndef SHAREDIR + #define SHAREDIR (working_directory) +#endif + +#endif diff --git a/shims/askl_map/askl_htable.c b/shims/askl_map/askl_htable.c new file mode 100644 index 0000000..95affe5 --- /dev/null +++ b/shims/askl_map/askl_htable.c @@ -0,0 +1,1416 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#include "askl_htable.h" + +/* -------------------------------------------------------------------------- */ +#ifdef _ENABLE_HASHMAP +/* -------------------------------------------------------------------------- */ + +#include "arcane/bitops.c" +#include "arcane/htable.c" + +typedef struct _Item { + void *ptr; + Variant val; + struct { + uint16_t len; + char str[]; + } key; +} _Item; + +/** + * @ingroup map + * @struct _Item + * + * Internal representation of a single key/value entry stored in a @ref Map. + * An @ref _Item is never allocated on its own; it is always embedded + * inside a @ref _Bucket. + * + * @b private @ref key.len is the key length in bytes (excluding the NUL byte). + * @b private @ref key.str is the inline key storage (flexible array), always + * terminated with a NUL character. + * @b private @ref val is the value stored in the map, as a @ref variant. + * @b private @ref ptr has two roles depending on where the item is stored: + * - in the main hash table (@ref Map::_bucket), it caches the + * primary hash value (hash0) as a uintptr_t + * - in the overflow basket (@ref Map::_basket), @ref ptr is + * used as the "next" link in the basket's singly linked list. + * + * This type is internal and may change at any time. + */ + +typedef struct _Bucket { + struct _Bucket *next; + _Item item; +} _Bucket; + +#if (TAG_SHIFT > 0) +STATIC_ASSERT( + ((ALIGNOF(_Bucket) | offsetof(_Bucket, item)) & TAG_MASK) == 0, + pointer_alignment_unsuitable_for_tagging +); +#endif + +/** + * @ingroup map + * @struct _Bucket + * + * Internal node of the map's traversal list. + * + * A Map keeps two views of the same entries: + * - the hash index (@ref Map::_bucket), used for key lookup; + * - the traversal list (@ref Map::_index), used for iteration and sorting. + * + * Each _Bucket is one node in the traversal list and owns one embedded + * @ref _Item. The hash index and overflow basket do not allocate separate + * entries; they point to the @ref _Item stored inside these buckets. + * + * Sorting only relinks _Bucket nodes in the traversal list. It does not move + * or copy keys and values, and it does not change lookup semantics. + * + * @b private @ref next links the next node in traversal order. + * @b private @ref item stores the key/value payload for this entry. + * + * This type is internal and may change at any time. + */ + +struct _Map { + RW_Lock *_lock; + struct _Bucket *_index; + struct _Item **_bucket; + struct _Item *_basket; + size_t _bucket_size; + size_t _bucket_count; + void (*_freeval)(Variant); + uintptr_t _seed[HASH_COUNT]; + Map_Comparator _cmpfn; + int8_t _order; + uint8_t _state; +}; + +#define MAP_INDEX_STALE 0x1 +#define MAP_DATA_CHANGE 0x2 +#define MAP_STATE_DIRTY (MAP_DATA_CHANGE | MAP_INDEX_STALE) + +/** + * @ingroup map + * @struct _Map + * + * This structure holds the internal state of a @ref Map. + * + * A Map is a hash-indexed associative container that also maintains a stable + * traversal order. It combines cuckoo hashing for O(1) expected-time lookup + * with an overflow basket for guaranteed insertion when cuckoo displacement + * fails. + * + * Concurrency: + * - Readers hold a read lock to allow concurrent lookups/traversals. + * - Writers take a write lock to insert/remove/resize/sort. + * + * @b private @ref _lock is a reader/writer lock protecting the whole map. + * @b private @ref _index is the head of the traversal list. Each node embeds + * an @ref _Item and supports O(n) ordered traversal. + * @b private @ref _bucket is the hash index table. Its entries point to + * items embedded in the traversal list. + * @b private @ref _basket is the overflow chain head for items that failed + * cuckoo placement after HASH_RETRY displacement attempts. + * @b private @ref _bucket_size is the current capacity of the hash index. + * @b private @ref _bucket_count is the number of occupied hash slots/items + * tracked by the hash index. + * @b private @ref _freeval is an optional destructor callback used when + * removing entries from the map. + * @b private @ref _seed is the per-map hash seed material (HASH_COUNT words). + * @b private @ref _cmpfn is the persistent comparator used to maintain sorted + * traversal order, or NULL if no persistent sort is active. + * @b private @ref _order is the persistent sort order applied to @ref _cmpfn. + * @b private @ref _state stores internal state flags: + * @ref MAP_INDEX_STALE indicates that the traversal index + * should be lazily re-sorted before the next ordered + * traversal; @ref MAP_DATA_CHANGE indicates that map contents + * changed since the last observer/cache refresh. + * + * This type is internal and may change at any time; only use the public + * @ref Map API. + */ + +/* -------------------------------------------------------------------------- */ + +static int _probe(Map *h, unsigned int i, _Item *nevv, int replace) +{ + if (likely(! h->_bucket[i])) { + h->_bucket[i] = TAG_PTR(nevv, nevv->ptr); + h->_bucket_count ++; + /* insert */ + if (replace >= 0) { + if (~h->_state & MAP_STATE_DIRTY) + h->_state |= MAP_STATE_DIRTY; + } + return 0; + } + + if (replace >= 0 && unlikely(GET_TAG(h->_bucket[i]) == HASHTAG(nevv->ptr))) { + _Item *slot = GET_PTR(h->_bucket[i]); + if (nevv->ptr == slot->ptr) { + if (nevv->key.len == slot->key.len) { + if (! memcmp(nevv->key.str, slot->key.str, slot->key.len)) { + /* update if allowed */ + return (replace == CREATE_ONLY) ? -1 : 1; + } + } + } + } + + /* continue */ + return INT_MAX; +} + +/* -------------------------------------------------------------------------- */ + +static Variant _update( + Map *h, + _Item *slot, + Variant nevv, + Variant (*on_update)(const char *k, size_t l, Variant old, Variant nevv) +) +{ + Variant old = slot->val, rejected = old; + int aliased = variant_equal(nevv, old), changed = ! aliased; + + if (on_update) { + Variant val = on_update(slot->key.str, slot->key.len, old, nevv); + if (! variant_equal(nevv, val)) { + if ( (changed = ! variant_equal(old, val)) ) { + /* the function returned an entirely new value */ + if (h->_freeval) h->_freeval(old); + } + /* the item value was unused */ + rejected = nevv; + } + nevv = val; + } + + if (changed) { + if (~h->_state & MAP_STATE_DIRTY) + h->_state |= MAP_STATE_DIRTY; + slot->val = nevv; + } + + return (aliased) ? variant_null() : rejected; +} + +/* -------------------------------------------------------------------------- */ + +static int _set_item( + Map *h, + _Item *item, + int replace, + Variant *val, + Variant (*on_insert)(const char *k, size_t l, Variant nevv), + Variant (*on_update)(const char *k, size_t l, Variant old, Variant nevv) +) +{ + unsigned int i = 0, index = 0, retry = 0; + _Item *slot = NULL; + uintptr_t hash = 0, mask = h->_bucket_size - 1; + + /* avoid rehashing every key */ + if (unlikely(! (hash = (uintptr_t) item->ptr))) { + hash = _hash(item->key.str, item->key.len, h->_seed[0]); + item->ptr = (void *) hash; + } + + #if (UINTPTR_MAX == 0xffffffffffffffffULL) + PREFETCH(& h->_bucket[(hash >> 32) & mask], 1, L1_CACHE); + #endif + + goto _loop; + + /* look for a free slot */ + for (i = 0; i < HASH_COUNT; i ++) { + int probe; + + hash = _hash(item->key.str, item->key.len, h->_seed[i]); + #if (UINTPTR_MAX == 0xffffffffffffffffULL) + PREFETCH(& h->_bucket[(hash >> 32) & mask], 1, L2_CACHE); + #endif + +_loop: index = hash & mask; + if ( (probe = _probe(h, index, item, replace)) == 0) { + if (on_insert) + item->val = on_insert(item->key.str, item->key.len, item->val); + return 0; + } else if (unlikely(probe == -1)) goto _failure; + + if (probe == 1) { + slot = GET_PTR(h->_bucket[index]); + goto _replace; + } + + #if (UINTPTR_MAX == 0xffffffffffffffffULL) + /* second probe on 64 bits systems */ + index = (hash >> 32) & mask; + if ( (probe = _probe(h, index, item, replace)) == 0) { + if (on_insert) + item->val = on_insert(item->key.str, item->key.len, item->val); + return 0; + } else if (unlikely(probe == -1)) goto _failure; + + if (probe == 1) { + slot = GET_PTR(h->_bucket[index]); + goto _replace; + } + #endif + } + + /* couldn't find it, look in the basket */ + if (replace != REHASH_ONLY) { + for (slot = h->_basket; slot; slot = (_Item *) slot->ptr) { + if (likely(item->key.len == slot->key.len)) { + if (! memcmp(item->key.str, slot->key.str, item->key.len)) { + if (replace == CREATE_ONLY) + goto _failure; + goto _replace; + } + } + } + + /* no free slot found, the new item will be forcefully inserted */ + if (on_insert) + item->val = on_insert(item->key.str, item->key.len, item->val); + if (~h->_state & MAP_STATE_DIRTY) + h->_state |= MAP_STATE_DIRTY; + } + + /* try cuckoo eviction */ + for (index = (uintptr_t) item->ptr & mask; retry < HASH_RETRY; retry ++) { + _Item *tmp = GET_PTR(h->_bucket[index]); + int loop = (tmp->ptr == item->ptr); + + h->_bucket[index] = TAG_PTR(item, item->ptr); item = tmp; + + /* get rid of tombstones */ + if (unlikely(! item->key.len)) return 0; + + #if (UINTPTR_MAX == 0xffffffffffffffffULL) + index = (((uintptr_t) item->ptr) >> 32) & mask; + if (_probe(h, index, item, REHASH_ONLY) == 0) + return 0; + #endif + + for (i = 1; i < HASH_COUNT; i ++) { + hash = _hash(item->key.str, item->key.len, h->_seed[i]); + index = hash & mask; + if (_probe(h, index, item, REHASH_ONLY) == 0) + return 0; + #if (UINTPTR_MAX == 0xffffffffffffffffULL) + /* second probe on 64 bits systems */ + index = (hash >> 32) & mask; + if (_probe(h, index, item, REHASH_ONLY) == 0) + return 0; + #endif + } + + /* avoid evicting the original cuckoo */ + if (unlikely(loop)) break; + } + + /* store the key in the overflow basket */ + item->ptr = h->_basket; h->_basket = item; h->_bucket_count ++; + + return 0; + +_replace: + *val = _update(h, slot, item->val, on_update); + return 1; + +_failure: + *val = item->val; /* return the rejected value */ + return -1; +} + +/* -------------------------------------------------------------------------- */ + +static int _grow(Map *h, size_t size) +{ + _Bucket *b = NULL, *next = NULL; + _Bucket **prev = NULL; + _Item *item = NULL, *tmp = NULL; + _Item **nevv = NULL; + + /* round the size to the next highest power of 2 */ + size = __next_pow2(size); + if (unlikely(size < MAP_MIN_SIZE)) + size = MAP_MIN_SIZE; + + /* try to allocate a new bucket array */ + if (! (nevv = (_Item **) calloc(size, sizeof(*h->_bucket))) ) { + perror(ERR(_grow, calloc)); + return -1; + } + + /* clear the basket */ + for (item = h->_basket, h->_basket = NULL; item; item = tmp) { + tmp = (_Item *) item->ptr; + item->ptr = NULL; + } + + /* replace the bucket array */ + free(h->_bucket); h->_bucket = nevv; + + /* update the state */ + h->_bucket_size = size; h->_bucket_count = 0; + + /* rehash old buckets */ + for (prev = & h->_index, b = h->_index; b; b = next) { + next = b->next; + + if (unlikely(! b->item.key.len)) { + *prev = next; + free(b); + continue; + } + + prev = & b->next; + + _set_item(h, & b->item, REHASH_ONLY, NULL, NULL, NULL); + } + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +static inline int _check_size(Map *h) +{ + if (likely(h->_bucket_count <= (h->_bucket_size * MAP_MAX_LOAD) / 1000)) + return 0; + + return _grow(h, h->_bucket_size + 1); +} + +/* -------------------------------------------------------------------------- */ + +static inline Variant _insert( + Map *h, + const char *key, + size_t len, + Variant val, + int replace, + Variant (*on_insert)(const char *k, size_t l, Variant nevv), + Variant (*on_update)(const char *k, size_t l, Variant old, Variant nevv) +) +{ + _Bucket *bucket = NULL; + + #ifdef DEBUG + if (unlikely(len >= UINT16_MAX)) { + debug("_insert(): key is too long.\n"); + return val; + } + #endif + + /* replace the key by a dynamically allocated one */ + if (! (bucket = (_Bucket *) malloc(sizeof(*bucket) + len + 1)) ) { + perror(ERR(_insert, malloc)); + return val; + } + + memcpy(bucket->item.key.str, key, len); + bucket->item.key.str[len] = '\0'; + bucket->item.key.len = len; + bucket->item.val = val; + bucket->item.ptr = NULL; + + if (lock_wrlock(h->_lock) == -1) goto _err_lock; + + if (_check_size(h) == -1) goto _err_size; + + if (! _set_item(h, & bucket->item, replace, & val, on_insert, on_update)) { + bucket->next = h->_index; h->_index = bucket; + val = variant_null(); + } else free(bucket); + + lock_unlock(h->_lock); + + return val; + +_err_size: + lock_unlock(h->_lock); +_err_lock: + free(bucket); + return val; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Map *map_alloc(void (*freeval)(Variant)) +{ + Map *h = NULL; + + if (! (h = (Map *) malloc(sizeof(*h))) ) { + perror(ERR(map_alloc, malloc)); + return NULL; + } + + if (random_seed((uint32_t *) h->_seed, sizeof(h->_seed) / 4) == -1) + goto _err_rand; + + for (int i = 0; i < HASH_COUNT; i ++) + _check_seed(& h->_seed[i]); + + if (! (h->_lock = lock_alloc()) ) goto _err_lock; + if (lock_init(h->_lock) == -1) goto _err_init; + + h->_bucket = NULL; + h->_bucket_count = h->_bucket_size = 0; h->_basket = NULL; + h->_index = NULL; + h->_freeval = freeval; + + h->_cmpfn = NULL; + h->_order = 0; + h->_state = 0; + + if (_grow(h, 0) == -1) goto _err_init; + + return h; + +_err_init: + lock_free(h->_lock); +_err_lock: +_err_rand: + free(h); + + return NULL; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API void map_reserve(Map *h, size_t count) +{ + size_t size = 0; + + if (! h) { + debug("map_reserve(): bad parameters.\n"); + return; + } + + if (count > (SIZE_MAX - (MAP_MAX_LOAD - 1)) / 1000) { + debug("_reserve_size(): requested capacity is too large.\n"); + return; + } + + size = ((count * 1000) + (MAP_MAX_LOAD - 1)) / MAP_MAX_LOAD; + + if (lock_wrlock(h->_lock) == 0) { + if (size > h->_bucket_size) + _grow(h, size); + lock_unlock(h->_lock); + } +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_set_with( + Map *h, + const char *k, + size_t l, + Variant v, + Variant (*function)(const char *k, size_t l, Variant old, Variant nevv) +) +{ + if (unlikely(! h || ! k || ! l)) { + debug("map_set_with(): bad parameters.\n"); + return v; + } + + return _insert(h, k, l, v, STORE_VALUE, NULL, function); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_set(Map *h, const char *k, size_t l, Variant v) +{ + if (unlikely(! h || ! k || ! l)) { + debug("map_set(): bad parameters.\n"); + return v; + } + + return _insert(h, k, l, v, STORE_VALUE, NULL, NULL); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_insert_with( + Map *h, + const char *k, + size_t l, + Variant v, + Variant (*function)(const char *k, size_t l, Variant nevv) +) +{ + if (unlikely(! h || ! k || ! l)) { + debug("map_insert_with(): bad parameters.\n"); + return v; + } + + return _insert(h, k, l, v, CREATE_ONLY, function, NULL); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_insert(Map *h, const char *k, size_t l, Variant v) +{ + if (unlikely(! h || ! k || ! l)) { + debug("map_insert(): bad parameters.\n"); + return v; + } + + return _insert(h, k, l, v, CREATE_ONLY, NULL, NULL); +} + +/* -------------------------------------------------------------------------- */ + +static _Item *_get_item(Map *h, const char *k, size_t l, Variant *v) +{ + unsigned int i = 0; + uintptr_t h0, hash, mask = h->_bucket_size - 1; + _Item *ptr = NULL; + + #ifdef DEBUG + if (unlikely(l >= UINT16_MAX)) { + debug("_get_item(): key is too long.\n"); + return NULL; + } + #endif + + h0 = hash = _hash(k, l, h->_seed[0]); + #if (UINTPTR_MAX == 0xffffffffffffffffULL) + PREFETCH(& h->_bucket[(hash >> 32) & mask], 0, L1_CACHE); + #endif + goto _loop; + + for (i = 0; i < HASH_COUNT; i ++) { + hash = _hash(k, l, h->_seed[i]); + #if (UINTPTR_MAX == 0xffffffffffffffffULL) + PREFETCH(& h->_bucket[(hash >> 32) & mask], 0, L2_CACHE); + #endif + + #define _MAP_GET(index) \ + /* if an empty slot is found, no need to look further */ \ + if (! (ptr = h->_bucket[(index)]) ) break; \ + if (GET_TAG(ptr) == HASHTAG(h0)) { \ + ptr = GET_PTR(ptr); \ + if ((uintptr_t) ptr->ptr == h0 && likely(ptr->key.len == l)) { \ + if (likely(memcmp(ptr->key.str, k, l) == 0)) { \ + *v = ptr->val; \ + return ptr; \ + } \ + } \ + } + +_loop: _MAP_GET(hash & mask); + + #if (UINTPTR_MAX == 0xffffffffffffffffULL) + /* second probe on 64 bits systems */ + _MAP_GET((hash >> 32) & mask); + #endif + + #undef _MAP_GET + } + + /* scan the overflow basket */ + for (ptr = h->_basket; ptr; ptr = (_Item *) ptr->ptr) { + if (ptr->key.len == l && memcmp(ptr->key.str, k, l) == 0) { + *v = ptr->val; + return ptr; + } + } + + return NULL; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_update_with( + Map *h, + const char *k, + size_t l, + Variant nevv, + Variant (*function)(const char *k, size_t l, Variant old, Variant nevv) +) +{ + Variant old = { { 0 } }, res = nevv; + _Item *slot = NULL; + + if (unlikely(! h || ! k || ! l)) { + debug("map_update_with(): bad parameters.\n"); + return res; + } + + if (lock_rdlock(h->_lock) == -1) return res; + + if ( (slot = _get_item(h, k, l, & old) ) ) { + if (variant_equal(nevv, old) && ! function) { + /* no-op, avoid taking the write lock */ + res = variant_null(); + goto _noop; + } + + if (lock_upgrade(h->_lock) == 0) { + + /* check if the value was deleted during upgrade */ + if (likely(slot->key.len)) + res = _update(h, slot, nevv, function); + + lock_restore(h->_lock); + } else goto _fail; + } +_noop: + lock_unlock(h->_lock); + +_fail: + return res; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_update(Map *h, const char *k, size_t l, Variant v) +{ + if (unlikely(! h || ! k || ! l)) { + debug("map_update(): bad parameters.\n"); + return v; + } + + return map_update_with(h, k, l, v, NULL); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_get_with( + Map *h, + const char *key, + size_t len, + Variant (*function)(Variant) +) +{ + Variant res = { { 0 } }; + + if (unlikely(! h || ! key || ! len)) { + debug("map_get_with(): bad parameters.\n"); + return res; + } + + if (lock_rdlock(h->_lock) == -1) return res; + + if (_get_item(h, key, len, & res) && function) + res = function(res); + + lock_unlock(h->_lock); + + return res; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_get(Map *h, const char *key, size_t len) +{ + Variant res = { { 0 } }; + + if (unlikely(! h || ! key || ! len)) { + debug("map_get(): bad parameters.\n"); + return res; + } + + if (lock_rdlock(h->_lock) == -1) return res; + + _get_item(h, key, len, & res); + + lock_unlock(h->_lock); + + return res; +} + +/* -------------------------------------------------------------------------- */ + +static Variant _exists(UNUSED Variant v) +{ + return variant_true(); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int map_has(Map *h, const char *key, size_t len) +{ + Variant v = map_get_with(h, key, len, _exists); + return (is_boolean(v) && variant_to_boolean(v)); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int map_merge( + Map *dest, + Map *src, + Variant merge(const char *key, size_t len, Variant destval, Variant srcval) +) +{ + _Bucket *b = NULL, *next = NULL; + + if (! dest || ! src || ! merge) { + debug("map_merge(): bad parameters.\n"); + return -1; + } + + if (unlikely(dest == src)) { + debug("map_merge(): source and destination are the same map.\n"); + return -1; + } + + /* pry both maps open */ + if (lock_wrlock(dest->_lock) == -1) return -1; + if (lock_wrlock(src->_lock) == -1) { + lock_unlock(dest->_lock); + return -1; + } + + lock_break(src->_lock); + + for (b = src->_index, src->_index = NULL; b; b = next) { + Variant v = variant_null(); + next = b->next; + + if (! b->item.key.len) { free(b); continue; } + + b->item.ptr = NULL; + + /* handle conflicts with the merge helper */ + if (_set_item(dest, & b->item, 1, & v, NULL, merge)) { + if (variant_equal(v, b->item.val)) { + if (src->_freeval) src->_freeval(v); + } else if (dest->_freeval) dest->_freeval(v); + free(b); + } else { + b->next = dest->_index; dest->_index = b; + } + } + + /* destroy the source map */ + free(src->_bucket); + lock_destroy(src->_lock); + lock_free(src->_lock); free(src); + + lock_unlock(dest->_lock); + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +static inline _Bucket *_select_run( + _Bucket **src, + _Bucket **run_tail, + _Bucket ***tombstones, + unsigned int order, + Map_Comparator cmp +) +{ + _Bucket *head = NULL, *tail = NULL, *cur; + _Bucket **link = & head; + + while ( (cur = *src) ) { + if (unlikely(! cur->item.key.len)) { + **tombstones = cur; + *tombstones = & cur->next; + *src = cur->next; + cur->next = NULL; + continue; + } + + if (tail) { + int res = cmp( + tail->item.key.str, tail->item.key.len, tail->item.val, + cur->item.key.str, cur->item.key.len, cur->item.val + ); + + if (res && ((res > 0) ^ order)) + break; + } + + *link = tail = cur; + link = & cur->next; + *src = cur->next; + } + + if ( (*run_tail = tail) ) + tail->next = NULL; + + return head; +} + +/* -------------------------------------------------------------------------- */ + +static void _sort(Map *h, unsigned int order, Map_Comparator cmp) +{ + _Bucket *dead_head = NULL, *live_tail = NULL; + _Bucket **tombstones = & dead_head; + int did_merge; + + do { + _Bucket *res_head = NULL, *res_tail = NULL, *cur = h->_index; + + did_merge = 0; + + while (cur) { + _Bucket *run[2], *tail[2], *merged, *merged_tail; + int boundary; + + run[0] = _select_run(& cur, & tail[0], & tombstones, order, cmp); + if (! run[0]) break; + + run[1] = _select_run(& cur, & tail[1], & tombstones, order, cmp); + if (! run[1]) { + if (res_tail) + res_tail->next = run[0]; + else + res_head = run[0]; + res_tail = tail[0]; + break; + } + + /* fast path: check boundary between run 0 tail and run 1 head */ + boundary = cmp( + tail[0]->item.key.str, tail[0]->item.key.len, tail[0]->item.val, + run[1]->item.key.str, run[1]->item.key.len, run[1]->item.val + ); + + if (! boundary || ((boundary < 0) ^ order)) { + tail[0]->next = run[1]; + merged = run[0]; + merged_tail = tail[1]; + } else { + /* check boundary between run 1 tail and run 0 head */ + boundary = cmp( + tail[1]->item.key.str, tail[1]->item.key.len, tail[1]->item.val, + run[0]->item.key.str, run[0]->item.key.len, run[0]->item.val + ); + + if (boundary && ((boundary < 0) ^ order)) { + tail[1]->next = run[0]; + merged = run[1]; + merged_tail = tail[0]; + } else { + _Bucket *merged_head, *a = run[0], *b = run[1]; + _Bucket **mlink = & merged_head; + + /* merge both runs */ + while (a && b) { + _Bucket **pick; + int res = cmp( + a->item.key.str, a->item.key.len, a->item.val, + b->item.key.str, b->item.key.len, b->item.val + ); + + /* pick a side and preserve stability */ + pick = ((order) ? (res >= 0) : (res <= 0)) ? & a : & b; + + *mlink = *pick; + mlink = & (*pick)->next; + *pick = (*pick)->next; + } + + *mlink = (a) ? a : b; + merged = merged_head; + merged_tail = (a) ? tail[0] : tail[1]; + } + } + + did_merge = 1; + + if (res_tail) + res_tail->next = merged; + else + res_head = merged; + res_tail = merged_tail; + } + + h->_index = res_head; + live_tail = res_tail; + } while (did_merge); + + if (live_tail) + live_tail->next = dead_head; + else + h->_index = dead_head; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int map_sort(Map *h, unsigned int order, Map_Comparator cmp) +{ + int sort_once = order & MAP_SORT_ONCE; + + order ^= sort_once; + + if (! h || ! cmp || (order != MAP_ASC && order != MAP_DESC)) { + debug("map_sort(): bad parameters.\n"); + return -1; + } + + if (lock_wrlock(h->_lock) == -1) return -1; + + _sort(h, order, cmp); + h->_state &= ~MAP_INDEX_STALE; + + if (! sort_once) { + h->_cmpfn = cmp; + h->_order = order; + } + + lock_unlock(h->_lock); + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int map_sort_keys( + const char *key0, + size_t len0, + UNUSED Variant val0, + const char *key1, + size_t len1, + UNUSED Variant val1 +) +{ + int ret; + size_t len = (len0 < len1) ? len0 : len1; + + if ( (ret = memcmp(key0, key1, len)) ) + return ret; + + return (len0 > len1) - (len0 < len1); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_remove_if( + Map *h, + const char *key, + size_t len, + int (*condition)(const char *key, size_t len, Variant val) +) +{ + unsigned int i = 0; + _Item *tmp = NULL, *prev = NULL; + Variant result = { { 0 } }; + uintptr_t h0 = 0, hash = 0, mask = 0; + + if (! h || ! key || ! len) { + debug("map_remove(): bad parameters.\n"); + return result; + } + + if (lock_wrlock(h->_lock) == -1) return result; + + mask = h->_bucket_size - 1; + h0 = hash = _hash(key, len, h->_seed[i]); + goto _loop; + + for (i = 0; i < HASH_COUNT; i ++) { + hash = _hash(key, len, h->_seed[i]); + + #define _MAP_REMOVE(index) \ + if ( (tmp = h->_bucket[(index)]) && GET_TAG(tmp) == HASHTAG(h0)) { \ + tmp = GET_PTR(tmp); \ + if ((uintptr_t) tmp->ptr == h0 && likely(tmp->key.len == len)) { \ + if (likely(memcmp(tmp->key.str, key, len) == 0)) { \ + if (! condition || condition(key, len, tmp->val)) { \ + /* remove from the bucket */ \ + result = tmp->val; \ + /* a length of 0 indicates a tombstone */ \ + tmp->key.len = 0; \ + /* mark the map as dirty */ \ + h->_state |= MAP_DATA_CHANGE; \ + } \ + goto _result; \ + } \ + } \ + } + +_loop: _MAP_REMOVE(hash & mask); + + #if (UINTPTR_MAX == 0xffffffffffffffffULL) + /* second probe on 64 bits systems */ + _MAP_REMOVE((hash >> 32) & mask); + #endif + + #undef _MAP_REMOVE + } + + /* scan the overflow basket */ + for (tmp = prev = h->_basket; tmp; prev = tmp, tmp = (_Item *) tmp->ptr) { + if (tmp->key.len == len) { + if (memcmp(tmp->key.str, key, len) == 0) { + if (! condition || condition(key, len, tmp->val)) { + /* remove from the basket */ + result = tmp->val; + if (tmp == h->_basket) h->_basket = (_Item *) tmp->ptr; + else prev->ptr = tmp->ptr; + + /* tombstone */ + tmp->key.len = 0; + + /* mark the map as dirty */ + h->_state |= MAP_DATA_CHANGE; + } + goto _result; + } + } + } + +_result: + lock_unlock(h->_lock); + + return result; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_remove(Map *h, const char *key, size_t len) +{ + return map_remove_if(h, key, len, NULL); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API void map_foreach( + Map *h, + int (*f)(const char *, size_t, Variant, void *), + void *context +) +{ + _Bucket *bucket = NULL; + + if (! h || ! f) { + debug("map_foreach(): bad parameters.\n"); + return; + } + + if (lock_wrlock(h->_lock) == -1) return; + + if (h->_cmpfn && (h->_state & MAP_INDEX_STALE)) { + _sort(h, h->_order, h->_cmpfn); + h->_state &= ~MAP_INDEX_STALE; + } + + for (bucket = h->_index; bucket; bucket = bucket->next) { + if (bucket->item.key.len) { + int ret = f( + bucket->item.key.str, + bucket->item.key.len, + bucket->item.val, + context + ); + if (ret == -1) { + /* delete the record */ + if (h->_freeval) + h->_freeval(bucket->item.val); + bucket->item.key.len = 0; + h->_state |= MAP_DATA_CHANGE; + } else if (ret == 1) break; + } + } + + lock_unlock(h->_lock); + + return; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API size_t map_footprint(Map *h, size_t *overhead) +{ + _Bucket *bucket = NULL; + size_t key = 0; + size_t ret = sizeof(*h); + + if (! h) { + debug("map_footprint(): bad parameters.\n"); + return 0; + } + + if (lock_wrlock(h->_lock) == -1) return 0; + + if (h->_bucket_size) { + /* bucket size */ + ret += h->_bucket_size * sizeof(*h->_bucket); + /* keys */ + for (bucket = h->_index; bucket; bucket = bucket->next) { + if (bucket->item.key.len) { + /* key length + key recorded size + next and value pointers */ + ret += ( + sizeof(char *) + bucket->item.key.len + + sizeof(bucket->item.key.len) + + sizeof(char *) + sizeof(Variant) + ); + /* key length and value pointer are not overhead */ + key += ( + sizeof(bucket->item.key.len) + + bucket->item.key.len + sizeof(void *) + ); + } + } + } + + lock_unlock(h->_lock); + + if (overhead) *overhead = ret - key; + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Map *map_free(Map *h) +{ + _Bucket *bucket = NULL, *next = NULL; + + if (! h) return NULL; + + if (lock_wrlock(h->_lock) == -1) return NULL; + + /* free the threads waiting after the linked hashmap */ + lock_break(h->_lock); + + for (bucket = h->_index; bucket; bucket = next) { + next = bucket->next; + if (h->_freeval && bucket->item.key.len) + h->_freeval(bucket->item.val); + free(bucket); + } + + free(h->_bucket); + lock_destroy(h->_lock); + lock_free(h->_lock); free(h); + + return NULL; +} + +/* -------------------------------------------------------------------------- */ +/* Iterator */ +/* -------------------------------------------------------------------------- */ + +ASKL_API Map_Iterator *map_each(Map *h) +{ + Map_Iterator *iterator = NULL; + + if (! h) { + debug("map_each(): bad parameters.\n"); + return NULL; + } + + if (! (iterator = (Map_Iterator *) malloc(sizeof(*iterator)))) { + perror(ERR(map_each, malloc)); + return NULL; + } + + if (lock_rdlock(h->_lock) == -1) goto _err_lock; + + if (! h->_index) { + debug("map_each(): empty map.\n"); + goto _err_init; + } + + if (h->_cmpfn && (h->_state & MAP_INDEX_STALE)) { + if (lock_upgrade(h->_lock) == -1) goto _err_lock; + if (h->_state & MAP_INDEX_STALE) { + _sort(h, h->_order, h->_cmpfn); + h->_state &= ~MAP_INDEX_STALE; + } + lock_restore(h->_lock); + } + + iterator->map = h; + iterator->_current = h->_index; + if (likely(iterator->_current->item.key.len)) { + iterator->key = h->_index->item.key.str; + iterator->len = h->_index->item.key.len; + iterator->val = h->_index->item.val; + } else return map_next(iterator); + + return iterator; + +_err_init: + lock_unlock(h->_lock); +_err_lock: + free(iterator); + return NULL; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Map_Iterator *map_at(Map *h, const char *key, size_t len) +{ + Map_Iterator *iterator = NULL; + uint8_t *ptr = NULL; + + if (! h || ! key || ! len) { + debug("map_at(): bad parameters.\n"); + return NULL; + } + + if (! (iterator = (Map_Iterator *) malloc(sizeof(*iterator)))) { + perror(ERR(map_at, malloc)); + return NULL; + } + + iterator->map = h; + + if (lock_rdlock(h->_lock) == -1) goto _err_lock; + + if (h->_cmpfn && (h->_state & MAP_INDEX_STALE)) { + if (lock_upgrade(h->_lock) == -1) goto _err_lock; + if (h->_state & MAP_INDEX_STALE) { + _sort(h, h->_order, h->_cmpfn); + h->_state &= ~MAP_INDEX_STALE; + } + lock_restore(h->_lock); + } + + if (! (ptr = (uint8_t *) _get_item(h, key, len, & iterator->val))) { + debug("map_at(): key not found.\n"); + goto _err_item; + } + + /* find the bucket from the item address */ + iterator->_current = (_Bucket *) (ptr - offsetof(_Bucket, item)); + iterator->key = iterator->_current->item.key.str; + iterator->len = iterator->_current->item.key.len; + + return iterator; + +_err_item: + lock_unlock(h->_lock); +_err_lock: + free(iterator); + return NULL; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Map_Iterator *map_next(Map_Iterator *iterator) +{ + _Bucket *bucket = NULL; + + for (bucket = iterator->_current->next; bucket; bucket = bucket->next) { + if (likely(bucket->item.key.len)) { + iterator->_current = bucket; + iterator->key = bucket->item.key.str; + iterator->len = bucket->item.key.len; + iterator->val = bucket->item.val; + return iterator; + } + } + + return map_break(iterator); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_set_at(Map_Iterator *iterator, Variant nevv) +{ + Variant old = nevv; + int aliased = 0; + + if (lock_upgrade(iterator->map->_lock) == -1) return nevv; + /* XXX another thread may have deleted the entry during upgrade */ + if (likely(iterator->_current->item.key.len)) { + old = iterator->_current->item.val; + if (! (aliased = variant_equal(old, nevv)) ) { + iterator->_current->item.val = nevv; + iterator->val = nevv; + iterator->map->_state |= MAP_STATE_DIRTY; + } + } + lock_restore(iterator->map->_lock); + + return (aliased) ? variant_null() : old; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_remove_at(Map_Iterator *iterator) +{ + Variant ret = { { 0 } }; + unsigned int len = 0; + + if (lock_upgrade(iterator->map->_lock) == -1) return ret; + /* XXX another thread may have deleted the entry during upgrade */ + if (likely(len = iterator->_current->item.key.len)) { + ret = iterator->_current->item.val; + iterator->_current->item.key.len = 0; + iterator->map->_state |= MAP_DATA_CHANGE; + } + lock_restore(iterator->map->_lock); + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Map_Iterator *map_break(Map_Iterator *iterator) +{ + if (! iterator) { + debug("map_break(): bad parameters.\n"); + return NULL; + } + + lock_unlock(iterator->map->_lock); + free(iterator); + + return NULL; +} + +/* -------------------------------------------------------------------------- */ +#else +/* -------------------------------------------------------------------------- */ + +#ifdef __GNUC__ +__attribute__ ((unused)) static int __dummy__ = 0; +#endif + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ diff --git a/shims/askl_map/askl_htable.h b/shims/askl_map/askl_htable.h new file mode 100644 index 0000000..d225f24 --- /dev/null +++ b/shims/askl_map/askl_htable.h @@ -0,0 +1,900 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifndef ASKL_MAP_H + +#define ASKL_MAP_H + +#ifdef _ENABLE_HASHMAP + +#include "askl.h" +#include "askl_rwlock.h" +#include "askl_variant.h" + +#define MAP_ASC 0 +#define MAP_DESC 1 +#define MAP_SORT_ONCE 2 + +/** @defgroup map ASKL::map */ + +typedef struct _Map Map; + +/** + * @ingroup map + * @struct Map + * + * Opaque handle to a concurrent linked hash map. + * + * A Map is a hash-indexed associative container mapping arbitrary byte-string + * keys to @ref variant values. In addition to O(1) expected-time lookups, it + * maintains a stable internal index so that entries can be visited in a + * well-defined order (e.g. LIFO insertion order or user-specified sort order). + * + * The map is safe for concurrent access: readers and writers are synchronized + * internally using a read–write lock. Simple operations such as @ref map_get(), + * @ref map_set() or @ref map_remove() may be used directly from multiple + * threads without additional external locking. + * + * Instances of this type are created with @ref map_alloc() and must be + * destroyed with @ref map_free() when no longer needed. All interaction with + * the map should go through the functions declared in this header; the + * structure layout is intentionally hidden and may change between releases. + */ + +typedef struct Map_Iterator { + Map *map; + struct _Bucket *_current; + const char *key; + size_t len; + Variant val; +} Map_Iterator; + +/** + * @ingroup map + * @struct Map_Iterator + * + * This structure represents an iterator over the entries of a @ref Map. + * + * Iterators are created by @ref map_each() or @ref map_at(). + * They carry a reference to the underlying map and expose the current + * key/value pair through their public fields. + * + * The iterator maintains a read lock on @ref map for the duration of its + * lifetime. The lock is acquired when the iterator is created and is released + * when the iterator is exhausted (via @ref map_next()) or explicitly + * destroyed with @ref map_break(). + * + * @b public @ref map points to the map being traversed. + * @b public @ref key points to the current key bytes (NUL-terminated). + * @b public @ref len is the length of the current key in bytes + * (excluding the terminating NUL). + * @b public @ref val is the current value associated with @ref key. + * + * @b private @ref _current is the internal cursor used to walk the map’s + * index list. It must not be accessed directly by + * user code. + * + * Iteration is performed by repeatedly calling @ref map_next() until it + * returns NULL. The current entry may be updated or removed in-place using + * @ref map_set_at() and @ref map_remove_at(), which perform the necessary + * lock upgrades internally. + * + * @note The iterator itself is heap-allocated and is freed automatically when + * @ref map_next() reaches the end of the traversal, or manually by + * calling @ref map_break(). + */ + +typedef int (*Map_Comparator)( + const char *, size_t, Variant, + const char *, size_t, Variant +); + +/** + * @ingroup map + * @typedef Map_Comparator + * + * Comparator used by map_sort(). + * + * The function receives two key/value pairs and must return a negative value + * if the first pair should come before the second, zero if they compare equal, + * or a positive value if the first pair should come after the second. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Map *map_alloc(void (*freeval)(Variant)); + +/** + * @ingroup map + * @fn Map *map_alloc(void (*freeval)(Variant)) + * @param freeval optional callback used to destroy stored values + * @return a pointer to a newly allocated @ref Map, or NULL on error + * + * This function allocates and initializes a new concurrent linked hash map. + * + * If @p freeval is not NULL, it will be called once for each remaining value + * stored in the map when @ref map_free() is called, or when entries are + * removed via @ref map_foreach(). + * + * The returned hashmap must be destroyed with @ref map_free() when no longer + * needed. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API void map_reserve(Map *h, size_t count); + +/** + * @ingroup map + * @fn void map_reserve(Map *h, size_t count) + * @param h a pointer to a linked hashmap + * @param count the number of entries to reserve space for + * @return void + * + * This function reserves enough hash-index capacity for at least @p count + * entries. + * + * Reserving capacity before inserting many entries can reduce the number of + * internal resizes and rehashes performed while the map grows. It does not + * insert any entries, change existing key/value pairs, or alter the traversal + * order of the map. + * + * If @p count is less than or equal to the current effective capacity, this + * function succeeds without modifying the map. + * + * @note This function acquires a write lock on the hashmap while resizing. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_set_with( + Map *h, + const char *k, + size_t l, + Variant v, + Variant (*function)(const char *k, size_t l, Variant old, Variant nevv) +); + +/** + * @ingroup map + * @fn Variant map_set_with(Map *h, const char *key, size_t len, + * Variant value, + * Variant (*function)(const char *, size_t, + * Variant, Variant)) + * @param h a pointer to a linked hashmap + * @param key pointer to the key bytes + * @param len length of the key in bytes + * @param value the new value (also available to @p function as @p new) + * @param function optional callback used to resolve replacement + * @return the previous value associated with @p key, or VARIANT_NULL if the + * key was newly inserted or the value is aliased. + * + * This function sets @p key to @p value, inserting a new entry if needed. + * + * If @p function is NULL, the stored value is replaced unconditionally and + * the previous value is returned. If there was no previous value, i.e. the + * key was created, or if the new value is identical to the previous value + * (i.e. aliased), the function returns VARIANT_NULL. + * + * If @p function is non-NULL and the key already exists, it is invoked with + * the current value (@p old) and the requested value (@p new). + * + * If @p function returns: + * - @p old: the map is left unchanged and map_set_with returns @p new. + * - @p new: the @p new value is stored in the map and map_set_with returns + * the previous value (@p old). + * - any other value: the previous value (@p old) is discarded (and the + * _freeval callback invoked if defined), the returned value + * is stored in the map, and map_set_with returns @p new + * for the caller to dispose of. + * + * @note If the new value is identical to the previous value, the function will + * return VARIANT_NULL to prevent unsafe access to an object still owned + * by the map, or its accidental destruction. + * + * @note The callback @p function is executed while the map's write lock is + * held, ensuring atomicity. The callback must be fast and non-blocking. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_set(Map *h, const char *k, size_t l, Variant v); + +/** + * @ingroup map + * @fn Variant map_set(Map *h, const char *k, size_t l, Variant v) + * @param h a pointer to a linked hashmap + * @param k pointer to the key bytes (not necessarily NUL-terminated) + * @param l length of the key in bytes + * @param v the value to store + * @return the previous value associated with @p k, or VARIANT_NULL if none + * + * This function stores the value @p v under key @p k into the map. + * If an entry with the same key already exists, its value is replaced and the + * previous value is returned. Otherwise, the value is inserted and + * VARIANT_NULL is returned. + * + * @note If the new value is identical to the previous value, the function will + * return VARIANT_NULL to prevent unsafe access to an object still owned + * by the map, or its accidental destruction. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_insert_with( + Map *h, + const char *key, + size_t len, + Variant value, + Variant (*function)(const char *k, size_t l, Variant nevv) +); + +/** + * @ingroup map + * @fn Variant map_insert_with(Map *h, const char *key, size_t len, + * Variant value, + * Variant (*function)(const char *, size_t, + * Variant)) + * @param h a pointer to a linked hashmap + * @param key pointer to the key bytes + * @param len length of the key in bytes + * @param value value passed to @p function as @p new + * @param function optional callback to compute or initialize the inserted value + * @return VARIANT_NULL if the value was inserted, or @p value if + * the key already existed or the insertion failed. + * + * This function performs an insert-only operation with an optional callback. + * + * If an entry with @p key already exists, the map is left unchanged, + * @p function is not called, and @p value is returned to the caller. + * + * If the key does not exist, a new entry is inserted. If @p function is NULL, + * @p value is stored directly. If @p function is non-NULL, it is invoked with + * @p value as parameter and its return value is stored instead. + * + * @note The callback @p function is executed only when the key is newly + * inserted. It is executed while the map's write lock is held, ensuring + * atomicity. The callback must be fast and non-blocking. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_insert(Map *h, const char *k, size_t l, Variant v); + +/** + * @ingroup map + * @fn Variant map_insert(Map *h, const char *k, size_t l, Variant v) + * @param h a pointer to a linked hashmap + * @param k pointer to the key bytes (not necessarily NUL-terminated) + * @param l length of the key in bytes + * @param v the value to store + * @return VARIANT_NULL if the value was inserted, or @p v if + * the key already existed or the insertion failed. + * + * This function performs an insert-only operation. If no entry with the given + * key exists, the key/value pair is inserted and VARIANT_NULL is returned. + * If an entry already exists, the map is left unchanged and @p v is returned. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_update_with( + Map *h, + const char *k, + size_t l, + Variant v, + Variant (*function)(const char *k, size_t l, Variant old, Variant nevv) +); + +/** + * @ingroup map + * @fn Variant map_update_with(Map *h, const char *key, size_t len, + * Variant value, + * Variant (*function)(const char *key, size_t len, + * Variant old, Variant new)) + * @param h a pointer to a linked hashmap + * @param key pointer to the key bytes (not necessarily NUL-terminated) + * @param len length of the key in bytes + * @param value the proposed new value (also passed to @p function as @p new) + * @param function optional callback used to compute the replacement value + * @return the previous value associated with @p key if it existed, or + * @p value if the key was not present and no update was performed + * + * This function performs an update-only operation with an optional callback. + * It never inserts new keys into the map. + * + * If the key @p key does not exist in @p h, the map is left unchanged and + * @p value is returned to be disposed of. + * + * If the key exists and @p function is NULL, the stored value is replaced + * unconditionally with @p value and the previous value is returned. + * + * If the key exists and @p function is non-NULL, the callback is invoked with + * the current value (@p old) and the proposed value (@p new). Its return + * value determines what is stored in the map and what is returned: + * + * - If @p function returns @p old: the map is left unchanged and + * map_update_with() returns @p new. + * - If @p function returns @p new: the returned value is stored in the map + * and map_update_with() returns @p old. + * - If @p function returns any other value: the previous value (@p old) is + * discarded (and the map's @c _freeval callback is invoked if defined), + * the returned value is stored in the map, and map_update_with() returns + * @p new so that the caller may dispose of it if necessary. + * + * @note If the new value is identical to the previous value, the function will + * return VARIANT_NULL to prevent unsafe access to an object still owned + * by the map, or its accidental destruction. + * + * @note The callback @p function is executed only when the key already exists. + * It is executed while the map's write lock is held, ensuring atomicity. + * The callback must be fast and non-blocking. + * + * @see map_set_with() + * @see map_insert_with() + * @see map_update() + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_update(Map *h, const char *k, size_t l, Variant v); + +/** + * @ingroup map + * @fn Variant map_update(Map *h, const char *key, size_t len, Variant value) + * @param h a pointer to a linked hashmap + * @param key pointer to the key bytes (not necessarily NUL-terminated) + * @param len length of the key in bytes + * @param value the new value to store + * @return the previous value associated with @p key if it existed, or + * @p value if the key was not present and no update was performed + * + * This function performs a simple update-only operation. If an entry with + * @p key exists, its value is replaced with @p value and the previous value + * is returned. + * + * If the key does not exist in the map, the map is left unchanged and + * @p value is returned to be disposed of. No new entry is created. + * + * This is the update-only counterpart to @ref map_insert(), and is useful + * when the caller wants to modify an entry only if it already exists, and + * do nothing otherwise. + * + * @note If the new value is identical to the previous value, the function will + * return VARIANT_NULL to prevent unsafe access to an object still owned + * by the map, or its accidental destruction. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_get_with( + Map *h, + const char *key, + size_t len, + Variant (*function)(Variant) +); + +/** + * @ingroup map + * @fn Variant map_get_with(Map *h, const char *key, size_t len, + * Variant (*function)(Variant)) + * @param h a pointer to a linked hashmap + * @param key pointer to the key bytes + * @param len length of the key in bytes + * @param function optional callback applied to the stored value + * @return the stored value, the result of @p function, or VARIANT_NULL + * + * This function looks up the entry associated with @p key in @p h. + * + * If the key is found and @p function is NULL, the stored value is returned. + * If @p function is non-NULL, it is called with the stored value as argument + * and its return value is returned instead. + * + * If the key is not present in the map, VARIANT_NULL is returned. + * + * @note The callback @p function is executed while the map's read lock is + * held, ensuring atomicity. This is useful for acquiring locks on + * stored objects, or other operations that must be atomic with the + * lookup. The callback must be fast and non-blocking. + * + * @warning The callback @p function must not free the stored value or + * otherwise invalidate it. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_get(Map *h, const char *key, size_t len); + +/** + * @ingroup map + * @fn Variant map_get(Map *h, const char *key, size_t len) + * @param h a pointer to a linked hashmap + * @param key pointer to the key bytes + * @param len length of the key in bytes + * @return the stored value, or VARIANT_NULL if the key is not present + * + * This is a convenience wrapper around @ref map_get_with() with a NULL + * callback. It simply returns the value associated with @p key, or + * VARIANT_NULL if the key is not in the map. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int map_has(Map *h, const char *key, size_t len); + +/** + * @ingroup map + * @fn int map_has(Map *h, const char *key, size_t len) + * @param h a pointer to a linked hashmap + * @param key pointer to the key bytes + * @param len length of the key in bytes + * @return non-zero if @p key exists in the map, or 0 otherwise + * + * This function checks whether an entry with the given key exists in the map. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int map_merge( + Map *dest, + Map *src, + Variant merge(const char *key, size_t len, Variant destval, Variant srcval) +); + +/** + * @ingroup map + * @fn int map_merge(Map *dest, Map *src, + * Variant (*merge)(const char *, size_t, Variant, Variant)) + * @param dest destination hashmap + * @param src source hashmap (consumed and destroyed) + * @param merge conflict resolution callback + * @return 0 on success, or -1 on error + * + * The function transfers all entries from @p src into @p dest and resolves + * conflicts using the user-supplied @p merge callback. + * + * When a key exists in both maps, the callback is invoked with the key and + * the values from @p dest and @p src. The callback's return value replaces + * the value stored in @p dest for that key. + * + * If the callback returns the original value from @p dest, the value from + * @p src is discarded. If it returns the original value from @p src, the + * value from @p dest is discarded. If it returns a different value, both + * original values are discarded. + * + * Discarded values are released using the owning map's @p _freeval callback, + * if defined. If no @p _freeval callback is configured for the map owning a + * discarded value, the @p merge callback must release that value itself to + * avoid leaks. + * + * After a successful call, @p src is destroyed. The @p src pointer becomes + * invalid and must not be accessed again. + * + * @note The @p merge callback is executed while both maps are write-locked. + * It must be fast and must not attempt to access either map or perform + * blocking operations. + * + * @warning This function consumes and destroys @p src. + * The caller must ensure that no other threads access @p src + * concurrently with this call, or after it returns. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API void map_foreach( + Map *h, + int (*function)(const char *, size_t, Variant, void *), + void *context +); + +/** + * @ingroup map + * @fn void map_foreach(Map *h, + * int (*function)(const char *, size_t, Variant, void *), + * void *context) + * @param h a pointer to a linked hashmap + * @param function a callback invoked once per key/value pair + * @param context optional user data passed to the callback + * @return void + * + * This function iterates over all entries in the hashmap and calls @p function + * for each key/value pair. The callback receives: + * - the key pointer (NUL-terminated), + * - the key length in bytes, + * - the associated value, + * - the user provided context. + * + * If @p function returns -1 for an entry, that entry is removed from the + * map. If the hashmap was created with a @p freeval callback, it is invoked on + * the value before the entry is destroyed. + * + * If @p function returns 1 for an entry, the traversal stops. + * + * Any other return value from @p function is ignored and the iteration + * continues. + * + * @note This function acquires a write lock on the hashmap for the entire + * duration of the traversal. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int map_sort(Map *h, unsigned int order, Map_Comparator cmp); + +/** + * @ingroup map + * @fn int map_sort(Map *h, unsigned int order, Map_Comparator cmp) + * @param h a pointer to a map + * @param order sort order: @ref MAP_ASC or @ref MAP_DESC, optionally OR'ed + * with @ref MAP_SORT_ONCE + * @param cmp comparison callback + * @return 0 on success, -1 on error + * + * This function sorts the internal traversal index of @p h using the + * user-provided comparator @p cmp and a stable natural merge sort. + * + * The comparator receives: + * - @p key0, @p key1: pointers to key bytes; + * - @p len0, @p len1: length of the keys in bytes; + * - @p value0, @p value1: associated values. + * + * @warning Keys are followed by a trailing NUL byte for convenience, but may + * contain embedded NUL bytes. Comparators should use @p len0 and @p len1 + * rather than treating keys as C strings. + * + * The comparator must return: + * - a negative value if (key0, value0) should come before (key1, value1), + * - zero if they are considered equal for ordering purposes, + * - a positive value if (key0, value0) should come after (key1, value1). + * + * The @p order argument controls whether the resulting order is ascending + * (@ref MAP_ASC) or descending (@ref MAP_DESC) with respect to @p cmp. + * + * By default, sorting is persistent. After a successful call without + * @ref MAP_SORT_ONCE, @p cmp and @p order become the map's active traversal + * ordering policy. Later insertions or updates mark the index stale; + * @ref map_foreach(), @ref map_each(), and @ref map_at() will lazily re-sort + * the index before traversal when needed. + * + * If @ref MAP_SORT_ONCE is OR'ed into @p order, the index is sorted + * immediately using @p cmp, but @p cmp and @p order are not installed as the + * persistent ordering policy. If a persistent ordering policy is already + * active, it is left unchanged and will be used again after a later mutation + * marks the index stale. + * + * Example: + * @code + * map_sort(map, MAP_DESC | MAP_SORT_ONCE, map_sort_keys); + * @endcode + * + * @note Sorting only affects traversal order, including @ref map_foreach() + * and iterators. It does not change lookup, insertion, update, or + * removal semantics. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int map_sort_keys( + const char *key0, + size_t len0, + UNUSED Variant val0, + const char *key1, + size_t len1, + UNUSED Variant val1 +); + +/** + * @ingroup map + * @fn int map_sort_keys(const char *key0, size_t len0, Variant val0, + * const char *key1, size_t len1, Variant val1) + * @param key0 first key + * @param len0 first key length + * @param val0 unused + * @param key1 second key + * @param len1 second key length + * @param val1 unused + * @return an integer less than, equal to, or greater than zero + * + * This is a convenience comparator suitable for use with @ref map_sort(), + * it compares @p key0 and @p key1 lexicographically and ignores the values. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_remove_if( + Map *h, + const char *key, + size_t len, + int (*condition)(const char *key, size_t len, Variant val) +); + +/** + * @ingroup map + * @fn Variant map_remove_if(Map *h, const char *key, size_t len, + * int (*condition)(const char *, size_t, Variant)) + * @param h a pointer to a linked hashmap + * @param key pointer to the key bytes + * @param len length of the key in bytes + * @param condition optional predicate controlling removal + * @return the removed value if the entry was removed, or VARIANT_NULL if the + * key was not present or was not removed + * + * This function removes the entry associated with @p key from @p h. + * + * If @p condition is NULL, the entry is removed unconditionally. + * + * If @p condition is non-NULL, it is invoked with the stored value. The entry + * is removed only if the callback returns non-zero. + * + * @note The callback @p condition is executed while the map's write lock is + * held, ensuring atomicity. The callback must be fast and non-blocking. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_remove(Map *h, const char *key, size_t len); + +/** + * @ingroup map + * @fn Variant map_remove(Map *h, const char *key, size_t len) + * @param h a pointer to a linked hashmap + * @param key pointer to the key bytes + * @param len length of the key in bytes + * @return the removed value, or VARIANT_NULL if the key was not present + * + * This function removes the entry associated with @p key from @p h and + * returns its value. If the key does not exist, VARIANT_NULL is returned. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API size_t map_footprint(Map *h, size_t *overhead); + +/** + * @ingroup map + * @fn size_t map_footprint(Map *h, size_t *overhead) + * @param h a pointer to a linked hashmap + * @param overhead optional pointer to receive the internal overhead, in bytes + * @return the total memory footprint of the hashmap, in bytes + * + * This function computes an approximate memory footprint of the hashmap, + * including: + * - the structure itself, + * - its dynamically allocated lock, + * - the bucket array, + * - all allocated items and their keys. + * + * If @p overhead is non-NULL, @c *overhead is set to the portion of @p h + * memory that is considered overhead (metadata, buckets, etc.) rather than + * user payload (keys and the pointer to the value). + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Map *map_free(Map *h); + +/** + * @ingroup map + * @fn Map *map_free(Map *h) + * @param h a pointer to a linked hashmap + * @return always NULL + * + * This function destroys the hashmap @p h and frees all associated resources. + * If a @p freeval callback was specified at creation time, it is called once + * for each remaining stored value before the corresponding entry is freed. + * + * This function always returns NULL so it can be used to clear the pointer: + * @code + * map = map_free(map); + * @endcode + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Map_Iterator *map_each(Map *h); + +/** + * @ingroup map + * @fn Map_Iterator *map_each(Map *h) + * @param h a pointer to a linked hashmap + * @return a newly allocated iterator positioned on the first entry, or @c NULL + * if the map is empty or an error occurred + * + * This function creates an iterator that allows the caller to traverse all + * entries of the hashmap in the current traversal order. + * + * The returned iterator holds a read lock on @p h for the duration of the + * iteration. The iterator must be advanced using @ref map_next and eventually + * destroyed using @ref map_break (or implicitly when @ref map_next reaches + * the end). + * + * @note The iterator acquires a read lock on @p h when created. This lock + * is automatically released when the iterator is exhausted or explicitly + * destroyed with @ref map_break. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Map_Iterator *map_at(Map *h, const char *key, size_t len); + +/** + * @ingroup map + * @fn Map_Iterator *map_at(Map *h, const char *key, size_t len) + * @param h a pointer to a linked hashmap + * @param key a pointer to the key to look up + * @param len the length in bytes of @p key + * @return a newly allocated iterator positioned on the entry matching @p key, + * or @c NULL if the key is not found or an error occurred + * + * This function creates an iterator positioned on the entry associated with + * the specified @p key in the hashmap. It performs a lookup in @p h and, if + * the key exists, returns an iterator whose @c val, @c key and @c len fields + * are initialized to the corresponding entry. + * + * The returned iterator holds a read lock on @p h for the duration of its + * lifetime. As with iterators created by @ref map_each, the iterator must be + * advanced using @ref map_next and eventually destroyed using @ref map_break + * (or implicitly when @ref map_next reaches the end). + * + * @note If @p key is not present in the map or an internal error occurs, + * this function returns @c NULL and does not leave a lock held on + * @p h. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Map_Iterator *map_next(Map_Iterator *iterator); + +/** + * @ingroup map + * @fn Map_Iterator *map_next(Map_Iterator *iterator) + * @param iterator an iterator created with @ref map_each or @ref map_at + * @return the same iterator positioned on the next entry, or @c NULL if the + * end of the traversal is reached or an error occurred + * + * This function advances the iterator to the next entry in the map. If another + * entry is found, the iterator's @c key, @c len and @c val fields are updated + * accordingly and @p iterator is returned. + * + * When there are no more entries, the iterator is automatically destroyed, + * its read lock on the map is released, and @c NULL is returned. + * + * @note The caller must not free the iterator returned by @ref map_next; it is + * freed automatically when the iteration ends. To stop early, call + * @ref map_break instead. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_set_at(Map_Iterator *iterator, Variant nevv); + +/** + * @ingroup map + * @fn Variant map_set_at(Map_Iterator *iterator, Variant value) + * @param iterator a valid iterator positioned on an existing entry + * @param value the new value to store at the current position + * @return the previous value stored at the iterator's current key, or + VARIANT_NULL if the previous value compares equal to @p value + * + * This function replaces the value associated with the entry currently pointed + * to by @p iterator. The key is left unchanged; only the value is updated. + * + * Internally, the implementation upgrades the iterator's read lock to a write + * lock for the duration of the update, then restores it back to a read lock. + * This ensures that the update is atomic with respect to other concurrent + * map operations and that the iterator remains valid after the call. + * + * @note If the map has a persistent ordering policy, changing the value marks + * the traversal order stale. The active iterator is not re-sorted; the + * order will be restored before the next fresh traversal. + * + * @warning The iterator must currently point to a valid entry (i.e. it must + * be the result of a successful call to @ref map_each, @ref map_at + * or @ref map_next). Calling this function on an exhausted or broken + * iterator results in undefined behaviour. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant map_remove_at(Map_Iterator *iterator); + +/** + * @ingroup map + * @fn Variant map_remove_at(Map_Iterator *iterator) + * @param iterator a valid iterator positioned on an existing entry + * @return the value that was stored at the iterator’s current key + * + * This function removes the entry currently pointed to by @p iterator from + * the map and returns its value. The key is removed from the map; subsequent + * lookups for that key will fail as if it had never been inserted. + * + * Internally, the implementation upgrades the iterator's read lock to a write + * lock for the duration of the update, then restores it back to a read lock. + * This ensures that the update is atomic with respect to other concurrent + * map operations and that the iterator remains valid after the call. + * + * After @ref map_remove_at() returns, @p iterator remains valid but its + * current position should be considered implementation-defined. The only + * valid operations on the iterator are to continue the traversal with + * @ref map_next() or to stop it with @ref map_break(). The caller must not + * attempt to reuse the previous @c key/@c len/@c val fields after the entry + * has been removed. + * + * The caller is responsible for disposing of the returned value if needed. + * In particular, if the map was configured with a @c _freeval callback, that + * callback is **not** invoked automatically by @ref map_remove_at(); it is + * up to the caller to free or recycle the removed value as appropriate. + * + * @warning The iterator must currently point to a valid entry (i.e. it must + * be the result of a successful call to @ref map_each, @ref map_at + * or @ref map_next). Calling this function on an exhausted or broken + * iterator results in undefined behaviour. + * + * @see map_each() + * @see map_next() + * @see map_break() + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Map_Iterator *map_break(Map_Iterator *iterator); + +/** + * @ingroup map + * @fn Map_Iterator *map_break(Map_Iterator *iterator) + * @param iterator an iterator obtained from @ref map_each or @ref map_at + * @return always @c NULL + * + * This function explicitly destroys @p iterator and releases its read lock on + * the underlying map. After this call, @p iterator must not be used again. + * + * This is the manual counterpart to the implicit destruction performed by + * @ref map_next when the end of the traversal is reached. + * + * @note This function always returns @c NULL so that callers can conveniently + * clear their iterator variables: + * @code + * it = map_break(it); + * @endcode + */ + +/* _ENABLE_HASHMAP */ +#endif + +#endif diff --git a/shims/askl_map/askl_rwlock.c b/shims/askl_map/askl_rwlock.c new file mode 100644 index 0000000..a951eed --- /dev/null +++ b/shims/askl_map/askl_rwlock.c @@ -0,0 +1,459 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#include "askl_rwlock.h" +#include "arcane/bitops.c" + +struct _RW_Lock { + _ATOMIC int state; + _ATOMIC int wflag; + pthread_mutex_t mutex; + pthread_cond_t cond; +}; + +/** + * @ingroup rwlock + * @struct RW_Lock + * + * An internal read/write lock which provides: + * - A fast uncontended path via atomic operations on @ref state. + * - Blocking/wakeup for contended paths via @ref mutex and @ref cond. + * - Support for upgrading a read lock to a write lock via @ref lock_upgrade() + * with a single "claimant" at a time and cooperative help from other readers. + * - The ability to "break" the lock when destroying a map so that threads + * blocked in map operations wake up and fail cleanly instead of deadlocking + * on freed memory. + * + * The lock uses a single integer @ref state plus a mutex/condition pair. + * The low bit of @ref state (0x1) is used as an "upgrade in progress" flag, + * while the higher bits encode the base lock state and the number of readers. + * + * The following symbolic values are used: + * + * - @c -1 : broken; further calls to lock_rdlock(), lock_wrlock() + * or lock_upgrade() will fail and return -1. + * - @c WRLOCKED (0) : write-locked; exactly one writer holds the lock. + * - @c UPGRADED (1) : upgraded; a single thread holds what was previously + * a read lock but has transitioned to exclusive mode. + * - @c UNLOCKED (2) : no active readers or writers. + * - @c RDLOCKED (4) : base value for the "one reader, no claimant" state. + * + * For @ref state >= RDLOCKED the value is interpreted as: + * + * - Even (@c state & 0x1 == 0): + * the lock is held in read mode with no upgrade claim. The number of + * readers is (@c state - @c RDLOCKED) / @c LOCKSTEP + 1. + * + * - Odd (@c state & 0x1 == 1): + * an upgrade claim is in progress. One of the readers has set the + * claim flag and is attempting to become a writer. + * + * The special value @c CLAIMANT (5) denotes "exactly one reader remains and + * it is the thread that has claimed the upgrade". At that point the upgrader + * can atomically transition the lock from @c CLAIMANT to @c UPGRADED. + * + * On the fast path, readers and writers adjust @ref state atomically: + * + * - Readers increment @ref state by @c LOCKSTEP (2) as long as it is + * positive and even (no writer and no upgrade claim). + * - Writers transition @ref state from @c UNLOCKED to @c WRLOCKED when no + * readers or claimers are present. + * - Upgraders transition from @c RDLOCKED to @c UPGRADED when they are the + * only reader, or set the claim bit (by adding 1) when other readers are + * present and rely on them to cooperate. + * + * Cooperative readers that observe a claimed state with multiple readers + * temporarily drop their read share (decrement @ref state by @c LOCKSTEP) + * so that the upgrader can eventually become the sole remaining reader. + * + * On the slow path, @ref mutex and @ref cond are used to: + * + * - put readers and writers to sleep when they cannot adjust @ref state + * immediately, and + * - wake them when the lock is released or broken. + * + * In builds without atomics, the same invariants are preserved, but all + * updates to @ref state are performed under @ref mutex instead of using + * atomic operations. + */ + +#define WRLOCKED 0 +#define UPGRADED 1 +#define UNLOCKED 2 +#define RDLOCKED 4 +#define CLAIMANT 5 + +#define LOCKSTEP 2 +#define RDWAITER 0x1000 +#define WRWAITER 0x0001 + +#ifdef HAS_ATOMICS +#define _LOCKSTATE_GET(lk) _atomic_ldr(& (lk)->state) +#define _LOCKSTATE_SET(lk, v) _atomic_str(& (lk)->state, (v)) +#define _LOCKSTATE_CAS(lk, a, b) _atomic_cas(& (lk)->state, (a), (b)) +#define _LOCKSTATE_INC(lk) _atomic_add(& (lk)->state, LOCKSTEP) +#define _LOCKSTATE_DEC(lk) _atomic_sub(& (lk)->state, LOCKSTEP) +#define _LOCKWFLAG_GET(lk) _atomic_ldr(& (lk)->wflag) +#define _LOCKWFLAG_SET(lk, v) _atomic_str(& (lk)->wflag, (v)) +#define _LOCKWFLAG_INC(lk, v) _atomic_add(& (lk)->wflag, (v)) +#define _LOCKWFLAG_DEC(lk, v) _atomic_sub(& (lk)->wflag, (v)) +#else +#define _LOCKSTATE_GET(lk) ((lk)->state) +#define _LOCKSTATE_SET(lk, v) do { (lk)->state = (v); } while (0) +#define _LOCKSTATE_INC(lk) do { (lk)->state += LOCKSTEP; } while (0) +#define _LOCKSTATE_DEC(lk) do { (lk)->state -= LOCKSTEP; } while (0) +#define _LOCKWFLAG_GET(lk) ((lk)->wflag) +#define _LOCKWFLAG_SET(lk, v) do { (lk)->wflag = (v); } while (0) +#define _LOCKWFLAG_INC(lk, v) do { (lk)->wflag += (v); } while (0) +#define _LOCKWFLAG_DEC(lk, v) do { (lk)->wflag -= (v); } while (0) +#endif + +/* -------------------------------------------------------------------------- */ + +INTERNAL RW_Lock *lock_alloc(void) +{ + RW_Lock *ret = (RW_Lock *) malloc(sizeof(*ret)); + if (! ret) { + perror(ERR(lock_alloc, malloc)); + return NULL; + } + return ret; +} + +/* -------------------------------------------------------------------------- */ + +INTERNAL int lock_init(RW_Lock *lock) +{ + if (pthread_mutex_init(& lock->mutex, NULL)) { + perror(ERR(lock_init, pthread_mutex_init)); + return -1; + } + + if (pthread_cond_init(& lock->cond, NULL)) { + perror(ERR(lock_init, pthread_cond_init)); + goto _err_cond; + } + + _LOCKSTATE_SET(lock, UNLOCKED); + _LOCKWFLAG_SET(lock, 0); + + return 0; + +_err_cond: + pthread_mutex_destroy(& lock->mutex); + return -1; +} + +/* -------------------------------------------------------------------------- */ + +INTERNAL int lock_rdlock(RW_Lock *lock) +{ + int ret = 0, x = 0; + + if (unlikely(_LOCKWFLAG_GET(lock) & 0xfff)) sched_yield(); + + #ifdef HAS_ATOMICS + for (x = _LOCKSTATE_GET(lock); x && ! (x & 0x1); x = _LOCKSTATE_GET(lock)) { + if (_LOCKSTATE_CAS(lock, x, x + LOCKSTEP)) + return 0; + } + + if (unlikely(x == -1)) return -1; + #endif + + pthread_mutex_lock(& lock->mutex); + + _LOCKWFLAG_INC(lock, RDWAITER); + + #ifdef HAS_ATOMICS + while (1) { + #endif + /* wait while write-locked (lockstate == 0) */ + while (! (x = _LOCKSTATE_GET(lock)) || x & 0x1) { + if (unlikely(x == -1)) { + ret = -1; goto _err_lock; + } + pthread_cond_wait(& lock->cond, & lock->mutex); + } + + #ifdef HAS_ATOMICS + /* XXX handle slippery claimants */ + if (_LOCKSTATE_CAS(lock, x, x + LOCKSTEP)) break; + #else + _LOCKSTATE_INC(lock); + #endif + #ifdef HAS_ATOMICS + } + #endif + +_err_lock: + _LOCKWFLAG_DEC(lock, RDWAITER); + + pthread_mutex_unlock(& lock->mutex); + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +INTERNAL int lock_wrlock(RW_Lock *lock) +{ + int ret = 0, x = 0; + + #ifdef HAS_ATOMICS + if (likely(_LOCKSTATE_CAS(lock, UNLOCKED, WRLOCKED))) return 0; + #endif + + pthread_mutex_lock(& lock->mutex); + + _LOCKWFLAG_INC(lock, WRWAITER); + + #ifdef HAS_ATOMICS + while (1) { + #endif + /* wait for unlock */ + while ( (x = _LOCKSTATE_GET(lock)) != UNLOCKED) { + if (unlikely(x == -1)) { + ret = -1; goto _err_lock; + } + pthread_cond_wait(& lock->cond, & lock->mutex); + } + + #ifdef HAS_ATOMICS + /* XXX handle slippery readers */ + if (_LOCKSTATE_CAS(lock, UNLOCKED, WRLOCKED)) break; + #else + _LOCKSTATE_DEC(lock); + #endif + #ifdef HAS_ATOMICS + } + #endif + +_err_lock: + _LOCKWFLAG_DEC(lock, WRWAITER); + + pthread_mutex_unlock(& lock->mutex); + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +static int _cooperate(RW_Lock *lock) +{ + int cooperative = 0; + int state = _LOCKSTATE_GET(lock); + + if ( (state & 0x1) && state > CLAIMANT) { + /* there is multiple readers, help the claimant by releasing our lock */ + #ifdef HAS_ATOMICS + if (! _LOCKSTATE_CAS(lock, state, state - LOCKSTEP)) return 0; + #else + _LOCKSTATE_DEC(lock); + #endif + pthread_cond_broadcast(& lock->cond); + cooperative = 1; + } else if (state == -1) return -1; + + #ifndef HAS_ATOMICS + /* wait for the claim to be relinquished */ + while (! (state = _LOCKSTATE_GET(lock)) || (state & 0x1) ) { + if (unlikely(state == -1)) return -1; + pthread_cond_wait(& lock->cond, & lock->mutex); + } + #endif + + if (cooperative) { + /* re-take our lock */ + #ifdef HAS_ATOMICS + while (1) { + state = _LOCKSTATE_GET(lock); + /* XXX a writer may have slipped in when we were cooperating */ + if (state > WRLOCKED && ! (state & 0x1)) { + if (_LOCKSTATE_CAS(lock, state, state + LOCKSTEP)) + return 0; + } else if (unlikely(state == -1)) return -1; + sched_yield(); + } + #else + _LOCKSTATE_INC(lock); + #endif + } + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +INTERNAL int lock_upgrade(RW_Lock *lock) +{ + #ifdef HAS_ATOMICS + /* fast path: only reader */ + if (_LOCKSTATE_CAS(lock, RDLOCKED, UPGRADED)) return 0; + + /* claim the lock */ + while (1) { + int state = _LOCKSTATE_GET(lock); + + if (! (state & 0x1)) { + if (_LOCKSTATE_CAS(lock, state, state + 1)) { + /* only I will remain */ + while (! _LOCKSTATE_CAS(lock, CLAIMANT, UPGRADED)) { + if (unlikely(_LOCKSTATE_GET(lock) == -1)) + return -1; + sched_yield(); + } + return 0; + } + } else if (state == -1) return -1; + + if (_cooperate(lock) == -1) return -1; + } + #else + while (1) { + int state; + + pthread_mutex_lock(& lock->mutex); + + if (! ((state = _LOCKSTATE_GET(lock)) & 0x1) ) { + _LOCKSTATE_SET(lock, state + 1); + + /* wait for the other readers to go away */ + while ( (state = _LOCKSTATE_GET(lock)) != CLAIMANT) { + if (unlikely(state == -1)) goto _failure; + pthread_cond_wait(& lock->cond, & lock->mutex); + } + + /* take the lock */ + _LOCKSTATE_SET(lock, 1); + goto _success; + } else if (state == -1) goto _failure; + + /* call cooperate while holding the state mutex */ + if (_cooperate(lock) == -1) goto _failure; + + pthread_mutex_unlock(& lock->mutex); + } +_failure: + pthread_mutex_unlock(& lock->mutex); + return -1; +_success: + pthread_mutex_unlock(& lock->mutex); + return 0; + #endif +} + +/* -------------------------------------------------------------------------- */ + +INTERNAL void lock_restore(RW_Lock *lock) +{ + pthread_mutex_lock(& lock->mutex); + + _LOCKSTATE_SET(lock, RDLOCKED); + + pthread_mutex_unlock(& lock->mutex); + + pthread_cond_broadcast(& lock->cond); +} + +/* -------------------------------------------------------------------------- */ + +INTERNAL void lock_break(RW_Lock *lock) +{ + pthread_mutex_lock(& lock->mutex); + + _LOCKSTATE_SET(lock, -1); + + pthread_mutex_unlock(& lock->mutex); + + pthread_cond_broadcast(& lock->cond); +} + +/* -------------------------------------------------------------------------- */ + +INTERNAL void lock_unlock(RW_Lock *lock) +{ + int x; + + #ifdef HAS_ATOMICS + while (1) { + x = _LOCKSTATE_GET(lock); + + if (x == WRLOCKED) { + if (likely(_LOCKSTATE_CAS(lock, x, UNLOCKED))) + break; + } else if (x > UNLOCKED) { + if (_LOCKSTATE_CAS(lock, x, x - LOCKSTEP)) + break; + } else return; + } + + if (likely(! _LOCKWFLAG_GET(lock))) return; + + pthread_mutex_lock(& lock->mutex); + pthread_mutex_unlock(& lock->mutex); + + pthread_cond_broadcast(& lock->cond); + #else + pthread_mutex_lock(& lock->mutex); + + if ( (x = _LOCKSTATE_GET(lock)) == 0) + _LOCKSTATE_SET(lock, UNLOCKED); + else if (x > UNLOCKED) + _LOCKSTATE_DEC(lock); + + pthread_mutex_unlock(& lock->mutex); + + pthread_cond_broadcast(& lock->cond); + #endif +} + +/* -------------------------------------------------------------------------- */ + +INTERNAL void lock_destroy(RW_Lock *lock) +{ + pthread_cond_destroy(& lock->cond); + pthread_mutex_destroy(& lock->mutex); +} + +/* -------------------------------------------------------------------------- */ + +INTERNAL RW_Lock *lock_free(RW_Lock *lock) +{ + free(lock); + return NULL; +} + +/* -------------------------------------------------------------------------- */ diff --git a/shims/askl_map/askl_rwlock.h b/shims/askl_map/askl_rwlock.h new file mode 100644 index 0000000..a6d3c0d --- /dev/null +++ b/shims/askl_map/askl_rwlock.h @@ -0,0 +1,314 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifndef ASKL_RWLOCK_H + +#define ASKL_RWLOCK_H + +#include "askl.h" + +/** @defgroup rwlock ASKL::rwlock */ + +typedef struct _RW_Lock RW_Lock; + +/* -------------------------------------------------------------------------- */ + +INTERNAL RW_Lock *lock_alloc(void); + +/** + * @ingroup rwlock + * @fn RW_Lock *lock_alloc(void) + * @return a newly allocated lock on success, or NULL on allocation failure + * + * This private helper allocates an uninitialized read/write lock structure. + * + * The returned lock must be initialized with @ref lock_init() before it can + * be used. + * + * @note This function only allocates memory; it does not initialize any + * synchronization primitives inside the lock. + * + * @see lock_init() + * @see lock_free() + */ + +/* -------------------------------------------------------------------------- */ + +INTERNAL int lock_init(RW_Lock *lock); + +/** + * @ingroup rwlock + * @fn int lock_init(RW_Lock *lock) + * @param lock pointer to a lock structure + * @return 0 on success, -1 on error + * + * This function initializes a read/write lock structure. + * + * @see lock_alloc() + * @see lock_destroy() + */ + +/* -------------------------------------------------------------------------- */ + +INTERNAL int lock_rdlock(RW_Lock *lock); + +/** + * @ingroup rwlock + * @fn int lock_rdlock(RW_Lock *lock) + * @param lock the lock to acquire in read (shared) mode + * @return 0 on success, -1 if the lock is broken or an error occurred + * + * This function acquires the lock in shared (read) mode. Multiple readers + * may hold the lock concurrently as long as no writer owns it. + * + * If the lock is currently write-locked, the caller blocks until the write + * lock is released or the lock is broken via @ref lock_break(). + * + * On success, the caller holds a read lock and must eventually release it + * with @ref lock_unlock(). + * + * On failure (return value -1), the caller does not hold the lock. This + * typically indicates that the lock has been broken. + * + * @warning The return value must always be checked. Treat a return value + * of -1 as "you do not own the lock". + * + * @see lock_wrlock() + * @see lock_unlock() + * @see lock_break() + */ + +/* -------------------------------------------------------------------------- */ + +INTERNAL int lock_wrlock(RW_Lock *lock); + +/** + * @ingroup rwlock + * @fn int lock_wrlock(RW_Lock *lock) + * @param lock the lock to acquire in write (exclusive) mode + * @return 0 on success, -1 if the lock is broken or an error occurred + * + * This function acquires the lock in exclusive (write) mode. When a writer + * holds the lock, no other reader or writer may hold it at the same time. + * + * If the lock is currently held by readers or another writer, the caller + * blocks until the lock becomes available or is broken via + * @ref lock_break(). + * + * On success, the caller holds the write lock and must eventually release + * it with @ref lock_unlock(). + * + * On failure (return value -1), the caller does not hold the lock and + * should treat the lock as broken. + * + * @see lock_rdlock() + * @see lock_unlock() + * @see lock_break() + */ + +/* -------------------------------------------------------------------------- */ + +INTERNAL int lock_upgrade(RW_Lock *lock); + +/** + * @ingroup rwlock + * @fn int lock_upgrade(RW_Lock *lock) + * @param lock the lock to upgrade from read to write mode + * @return 0 on success, -1 if the lock is broken or the upgrade fails + * + * This function upgrades a lock that the caller already holds in read mode + * to an exclusive write lock, without allowing an intervening writer to + * slip in. + * + * Only one upgrader at a time may claim the lock. If multiple readers attempt + * to upgrade concurrently, unsuccessful claimants temporarily cooperate by + * dropping and later re-acquiring their read share so the current upgrader + * can become the sole owner and transition the lock to write mode. + * + * On success, the caller no longer holds a read lock; it now owns the lock in + * a special write mode and must eventually release it with @ref lock_restore(). + * + * On failure (return value -1), the caller must assume that it no longer + * owns a valid lock, neither for reading nor for writing. This usually + * indicates that the lock has been broken via @ref lock_break(). + * + * @warning This function must only be called by a thread that already + * holds the lock in read mode. Calling it without a read lock + * is undefined behavior. + * + * @warning An upgraded lock must only be released with @ref lock_restore(), + * using @ref lock_unlock() instead will result in a corrupted lock + * state and undefined behavior. + * + * @see lock_rdlock() + * @see lock_wrlock() + * @see lock_restore() + * @see lock_break() + */ + +/* -------------------------------------------------------------------------- */ + +INTERNAL void lock_restore(RW_Lock *lock); + +/** + * @ingroup rwlock + * @fn void lock_restore(RW_Lock *lock) + * @param lock the lock to restore to read mode + * @return void + * + * This function restores a lock that was previously upgraded to write mode + * via @ref lock_upgrade() back to a regular read-lock owned by the + * upgrading thread, and releases the upgrade claim so that other upgraders + * may proceed. + * + * After @ref lock_restore() returns, the caller holds the lock in read + * mode and must still eventually release it with @ref lock_unlock(). + * + * @warning This function must only be called after a successful + * @ref lock_upgrade(). Calling it on a lock that was not + * upgraded by the calling thread results in undefined + * behavior. + * + * @see lock_upgrade() + */ + +/* -------------------------------------------------------------------------- */ + +INTERNAL void lock_break(RW_Lock *lock); + +/** + * @ingroup rwlock + * @fn void lock_break(ASKL_RWLock *lock) + * @param lock the lock to break + * @return void + * + * This function marks the lock as "broken" and wakes up all threads that + * are currently waiting on it. + * + * Once a lock is broken, all subsequent calls to @ref lock_rdlock(), + * @ref lock_wrlock() or @ref lock_upgrade() return -1, and waiting + * operations will abort rather than blocking indefinitely. + * + * This is typically used just before destroying the lock, so that all + * threads currently blocked on the lock can detect the shutdown and exit + * their critical sections cleanly. + * + * @note Calling @ref lock_unlock() on a broken lock is safe and becomes + * a no-op. + * + * @warning After a lock has been broken, it must not be used again except + * to allow pending operations to detect the broken state and + * exit gracefully. + */ + +/* -------------------------------------------------------------------------- */ + +INTERNAL void lock_unlock(RW_Lock *lock); + +/** + * @ingroup rwlock + * @fn void lock_unlock(RW_Lock *lock) + * @param lock the lock to release + * @return void + * + * This function releases a lock held either in read or write mode by the + * calling thread. + * + * For a read lock, it decrements the internal reader count; for a write + * lock, it transitions the state back to "unlocked". Waiting readers and + * writers are notified via the internal condition variable. + * + * Calling this function on a broken lock is safe and effectively a no-op. + * + * @warning The caller must only call this after successfully acquiring + * the lock (via @ref lock_rdlock() or @ref lock_wrlock()). + * + * @see lock_rdlock() + * @see lock_wrlock() + */ + +/* -------------------------------------------------------------------------- */ + +INTERNAL void lock_destroy(RW_Lock *lock); + +/** + * @ingroup rwlock + * @fn void lock_destroy(ASKL_RWLock *lock) + * @param lock the lock whose resources should be released + * @return void + * + * This function destroys the internal mutexes and condition variables + * associated with a lock. It does not free the memory of the lock itself. + * + * The caller is responsible for ensuring that no thread is currently + * blocked on or holding the lock when this function is called. + * + * @warning Destroying a lock that is still in use by other threads leads + * to undefined behavior. + * + * @see lock_break() + * @see lock_free() + */ + +/* -------------------------------------------------------------------------- */ + +INTERNAL RW_Lock *lock_free(RW_Lock *lock); + +/** + * @ingroup rwlock + * @fn RW_Lock *lock_free(RW_Lock *lock) + * @param lock the lock structure to free + * @return always NULL + * + * This helper frees the memory associated with a lock structure previously + * allocated with @ref lock_alloc(). It always returns NULL, which allows + * idioms such as: + * + * @code + * mylock = lock_free(mylock); + * @endcode + * + * @warning The lock must have been destroyed with @ref lock_destroy() + * (or otherwise guaranteed to be unused) before calling this + * function. Freeing a lock that is still in use results in + * undefined behavior. + * + * @see lock_alloc() + * @see lock_destroy() + */ + +/* -------------------------------------------------------------------------- */ + +#endif diff --git a/shims/askl_map/askl_string.h b/shims/askl_map/askl_string.h new file mode 100644 index 0000000..7aaf558 --- /dev/null +++ b/shims/askl_map/askl_string.h @@ -0,0 +1,1053 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifndef ASKL_STRING_H + +#define ASKL_STRING_H + +#include "askl.h" + +#ifdef HAS_PCRE +#include +#endif + +#ifdef HAS_ICONV +#include +#endif + +/** @defgroup string ASKL::string */ +typedef struct String { + char *data; + struct String *tokens; + struct String *parent; + uint32_t len; + uint32_t count; + struct { + uint32_t capacity; + uint16_t tokens_capacity; + uint16_t flags; + } internal; +} String; + +typedef struct _String_Pattern String_Pattern; + +#define _STRING_FIXED_LENGTH 0x0001 /* disable string resizing */ +#define _STRING_READ_ONLY 0x0002 /* disable string writing */ +#define _STRING_IMMUTABLE 0x0003 /* disable writing and resizing */ +#define _STRING_DISABLE_FREE 0x0004 /* disable free() on string content */ +#define _STRING_ENCAPSULATED 0x0005 /* disable all dynamic allocation */ +#define _STRING_STATIC_ALLOC 0x0008 /* static, stack allocated string */ +#define _STRING_EXTENSION 0x000f /* mask extension flags */ +#define _STRING_VALIDATED 0x0010 /* this string has been validated */ +#define _STRING_HAS_ERROR 0x0020 /* this string contains errors */ +#define HAS_ERROR(x) ((x)->internal.flags & _STRING_HAS_ERROR) +#define _STRING_BUFFERING 0x0040 /* this string is used for buffering */ +#define IS_BUFFER(x) ((x)->internal.flags & _STRING_BUFFERING) +/* 0x0080 reserved */ +#ifdef _ENABLE_HTTP +#define _STRING_HTTP_REQUEST 0x0100 /* HTTP request */ +#define IS_HTTP(x) ((x)->_flags & _STRING_HTTP_REQUEST) +#endif +#define _STRING_PARTIAL_DATA 0x0200 /* used by the server for streaming */ +#define IS_FRAGMENT(x) ((x)->internal.flags & _STRING_PARTIAL_DATA) +#define _STRING_LARGE_BUFFER 0x0400 /* large request */ +#define IS_LARGE(x) ((x)->_flags & _STRING_LARGE_BUFFER) +#ifdef _ENABLE_HTTP +#define _STRING_HTTP_CHUNKED 0x0800 /* HTTP 1.1 Chunked encoding */ +#define IS_CHUNK(x) ((x)->_flags & _STRING_HTTP_CHUNKED) +#endif + +#define STRING_STATIC_INITIALIZER(s, l) { \ + (char *)(s), NULL, NULL, (l), 0, \ + { 0, 0, _STRING_IMMUTABLE | _STRING_ENCAPSULATED | _STRING_STATIC_ALLOC } \ +} + +/** + * @ingroup string + * @struct String + * + * This structure is designed to hold arbitrary sequences of bytes. + * + * @ref data is a pointer to the content of the String. + * + * @ref len is the size, in bytes, of that content. + * + * @ref count holds the number of tokens in which the string has been sliced, + * with @ref string_split or any other function. + * + * @ref tokens is an array of String structures, which are set to point + * to particular subsections of the current string. Tokens can be resized or + * altered with the various functions defined in this API, without restrictions. + * However, some functions performing destructive transformations on their + * input may destroy all tokens associated to a string; in this case, this + * behaviour will be specified in the function documentation. + * + * @ref parent is a pointer to the parent string of a token (which may be + * a token itself). You can test if a string is a token or not by checking + * the parent field value; if it is NULL, then the string is not a token. + * + * You can clean up the tokens of a string at any time by calling + * @ref string_free_token(). + * + * All other fields are private, using or altering their values is likely + * to result in unexpected behaviours. + * + * @b private @ref flags is a bitfield holding several internal flags, like + * write protection, or resizing protection. + * + * @b private @ref capacity is the actual size of the inner data buffer of the + * String structure. It should only be used in the resizing functions. + * + * @b private @ref tokens_capacity is the number of available slots for new + * tokens. This structure cannot hold more than 65535 tokens. + * + */ + +#define EMPTY(s) (! (s)->len || ((s)->len == 1 && ! *(s)->data)) + +#define tokens(...) EXPAND_TOKENS(__VA_ARGS__) + +#define EXPAND_TOKENS_1(a) tokens[(a)] +#define EXPAND_TOKENS_2(a,b) tokens[(a)].tokens[(b)] +#define EXPAND_TOKENS_3(a,b,c) tokens[(a)].tokens[(b)].tokens[(c)] + +#define COUNT_ARGS_IMPL(_1,_2,_3,N,...) N +#define COUNT_ARGS(...) COUNT_ARGS_IMPL(__VA_ARGS__, 3, 2, 1) + +#define SELECT_EXPAND(N) EXPAND_TOKENS_##N +#define EXPAND_TOKENS_EVAL(N) SELECT_EXPAND(N) +#define EXPAND_TOKENS(...) \ +EXPAND_TOKENS_EVAL(COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__) + +/** + * @name Token navigation helpers + * @ingroup string + * @def tokens(...) + * + * Convenience macro for navigating nested tokens from a parent String. + * + * Example: + * @code + * String *s = ...; + * // First-level token + * String *t = & s->tokens(x); + * // Second-level token + * String *u = & s->tokens(x, y); + * // Third-level token + * String *v = & s->tokens(x, y, z); + * @endcode + * + * This expands to: + * + * - s->tokens[x] + * - s->tokens[x].tokens[y] + * - s->tokens[x].tokens[y].tokens[z] + * + * The macro does not perform bounds checking. + */ + + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_api_setup(void); + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_reserve(const char *string, size_t len, size_t extra); + +/** + * @ingroup string + * @fn String *string_reserve(const char *string, size_t len, size_t extra) + * @param string Optional pointer to initial data to copy into the new string. + * @param len Number of bytes to copy from @p string. + * @param extra Additional bytes to reserve in the internal buffer. + * @return A pointer to a new String, or NULL on error. + * + * Allocates a new String and its internal buffer, with enough space for + * len + extra bytes. + * + * - If @p string is non-NULL, up to @p len bytes are copied into the new + * buffer; the buffer is then padded with a wchar_t-sized NUL terminator. + * - If @p string is NULL and @p len is zero, the String is created with + * no buffer and len == 0. + * + * This is useful when you know a string will grow and want to avoid multiple + * reallocations. + * + * The returned String must be destroyed with @ref string_free(). + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_alloc(const char *string, size_t len); + +/** + * @ingroup string + * @fn String *string_alloc(const char *string, size_t len) + * @param string Optional pointer to initial data to copy. + * @param len Number of bytes to allocate and (optionally) copy. + * @return A pointer to a new String, or NULL on error. + * + * string_alloc() is a convenience wrapper over @ref string_reserve() with + * extra == 0. + * + * Typical patterns: + * + * @code + * // Allocate an empty string with no buffer. + * String *s = string_alloc(NULL, 0); + * + * // Allocate a 256-byte buffer, uninitialized. + * String *buf = string_alloc(NULL, 256); + * + * // Copy an existing C string (NUL byte is treated as data like any other). + * String *copy = string_alloc(src, len); + * @endcode + * + * The returned String must be freed with @ref string_free(). + */ + + +/* -------------------------------------------------------------------------- */ + +static inline const char *string_end(const String *s) +{ + assert(s && s->data); + return s->data + s->len; +} + +/* -------------------------------------------------------------------------- */ + +static inline String *last_token(const String *s) { + assert(s && s->count); + return & s->tokens[s->count - 1]; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_encaps(const char *string, size_t len); + +/** + * @ingroup string + * @fn String *string_encaps(const char *string, size_t len) + * @param string the buffer to encapsulate in a String structure + * @param len the length of the buffer + * @return NULL if an error occurred, a pointer to a new String otherwise + * + * This functions wraps an existing static or dynamically allocated buffer + * in a new read-only, fixed length String structure. + * + * This "static" String structure can be used with all string functions + * accepting read-only Strings, and should be deleted after use with + * the @ref string_free() function. This will not free the initial buffer, + * though. Handling of the initial buffer is left to the programmer. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API uint8_t string_fetch_uint8(String *string); + +/* -------------------------------------------------------------------------- */ + +ASKL_API uint16_t string_fetch_uint16(String *string); + +/* -------------------------------------------------------------------------- */ + +ASKL_API uint32_t string_fetch_uint32(String *string); + +/* -------------------------------------------------------------------------- */ + +ASKL_API uint64_t string_fetch_uint64(String *string); + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_fetch_buffer(String *string, char *out, size_t len); + +/* -------------------------------------------------------------------------- */ + +ASKL_API void string_flush(String *string); + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_wchar(String *string); + +/** + * @ingroup string + * @fn int string_wchar(String *string) + * @param string the string to be converted. + * @return -1 if an error occurred, 0 otherwise. + * + * This function converts the data stored in the internal buffer of the given + * @b string to wide characters from multibyte characters. + * + * If the conversion is successful, the inner buffer is replaced by its + * multibyte equivalent. + * + * If the function fails to convert the data, it will returns -1 and leave the + * buffer unchanged. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_mbyte(String *string); + +/** + * @ingroup string + * @fn int string_mbyte(String *string) + * @param string the string to be converted. + * @return -1 if an error occurred, 0 otherwise. + * + * This function converts the data stored in the internal buffer of the given + * @b string to multibyte characters from wide characters. + * + * If the conversion is successful, the inner buffer is replaced by its + * wide character equivalent. + * + * If the function fails to convert the data, it will returns -1 and leave the + * buffer unchanged. + * + */ + +/* -------------------------------------------------------------------------- */ +#ifdef HAS_ICONV +/* -------------------------------------------------------------------------- */ + +ASKL_API size_t string_convs(const char *src, size_t srclen, const char *src_enc, + char *dst, size_t dstlen, const char *dst_enc); + +/** + * @ingroup string + * @fn size_t string_convs(const char *src, size_t srclen, const char *src_enc, + * char *dst, size_t dstlen, const char *dst_enc) + * @param src the string to be converted. + * @param srclen the length of the string to be converted. + * @param src_enc the encoding of the string to be converted. + * @param dst the output buffer. + * @param dstlen length of the output buffer. + * @param dst_enc encoding to use for the conversion. + * @return -1 if an error occurred, 0 otherwise. + * + * This function uses the Iconv library to convert the encoding of the given + * string. If the output is NULL, the function will return the length the + * output buffer should have to fit the converted string. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_conv(String *s, const char *src_enc, const char *dst_enc); + +/** + * @ingroup string + * @fn int string_conv(String *s, const char *src_enc, const char *dst_enc) + * @param s the string to be converted. + * @param src_enc the encoding of the original string + * @param dst_enc the encoding to use for the conversion + * @return -1 if an error occurred, 0 otherwise. + * + * This function uses the Iconv library to convert the encoding of the given + * string. + * + */ + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_clone(const String *string); + +/** + * @ingroup string + * @fn String *string_clone(const String *string) + * @param string the string to be duplicated. + * @return a pointer to a new string, or NULL. + * + * This function simply makes an exact copy of the String given in parameter, + * and returns it. + * + * If the source string has subtokens, they are copied along with the data. + * + * If for some reason copying the string was not possible, the function + * will return NULL. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_clone_reserve(const String *string, size_t extra); + +/** + * @ingroup string + * @fn String *string_clone_reserve(const String *string) + * @param string the string to be duplicated. + * @param extra additional space to allocate. + * @return a pointer to a new string, or NULL. + * + * This function simply makes an exact copy of the String given in parameter, + * and returns it. The new string will be extended to be @b extra bytes longer + * than the original. + * + * If the source string has subtokens, they are copied along with the data. + * + * If for some reason copying the string was not possible, the function + * will return NULL. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_free(String *string); + +/** + * @ingroup string + * @fn String *string_free(String *string) + * @param string the string to be destroyed. + * @return always NULL. + * + * This function will properly clean up and destroy a String structure, + * including its tokens if there are any. + * + * This function always returns NULL, so it can be used to clean a pointer: + * str = string_free(str); + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API size_t string_len(const String *string); + +/** + * @ingroup string + * @fn size_t string_len(const String *string) + * @param string the string the size has to be read. + * @return the size of the string, or (size_t) -1. + * + * This function simply returns the size of the given string. It may be + * preferable to the SIZE, CLEN or WLEN macros since it performs a NULL check. + * + * If the size can not be read, the function returns (size_t) -1. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API size_t string_capacity(const String *string); + +/** + * @ingroup string + * @fn size_t string_capacity(const String *string) + * @param string the string the buffer space has to be read. + * @return the allocation size of the string, or (size_t) -1. + * + * This function simply returns the allocation space consumed by the buffer + * of the given string. + * + * If the buffer space can not be read, the function returns (size_t) -1. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API size_t string_available(const String *string); + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_resize(String *string, size_t size); + +/** + * @ingroup string + * @fn int string_resize(String *string, size_t size) + * @param string the string to resize. + * @param size the new size to give to the string. + * @return -1 if the string could not be resized, 0 otherwise. + * + * This function forces the resizing of a string. The buffer will be truncated + * or extended to the given @b size, without care for the inner data. + * + * If the buffer can not be resized (wrong size, not enough memory or if the + * string is marked as not resizable), the string is left untouched and the + * function returns -1. + * + * If the buffer size matches the given @b size, no changes are done to the + * string and the function returns 0. + * + * If this function is called on a token, it will extend or shrink the + * main string and alter the token size accordingly. + * + * Subtokens are kept and updated after resizing. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_extend(String *string, size_t size); + +/** + * @ingroup string + * @fn int string_extend(String *string, size_t size) + * @param string the string to be extended. + * @param size the new size of the string. + * @return -1 if an error occurs (see @ref string_resize), 0 otherwise. + * + * This function is simply a wrapper around @ref string_resize(), which ensures + * the buffer will only be resized if @b size is greater than its current size. + * + */ + + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_shrink(String *string, size_t size); + +/** + * @ingroup string + * @fn int string_shrink(String *string, size_t size) + * @param string the string to be shrunk. + * @param size the new size of the string. + * @return -1 if an error occurs (see @ref string_resize), 0 otherwise. + * + * This function is simply a wrapper around @ref string_resize(), which ensures + * the buffer will only be resized if @b size is less than its current size. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_splice(String *to, off_t o, const char *from, size_t l); + +/** + * @ingroup string + * @fn String *string_splice(String *to, off_t o, const char *from, size_t l) + * @param to Destination string (may be NULL). + * @param o Offset in @p to at which to write, within current bounds. + * @param from Source buffer to copy from. + * @param l Number of bytes to copy. + * @return NULL on error, or a pointer to the destination string. + * + * Low-level primitive for moving bytes into a String. + * + * - If @p to is NULL, a new String is allocated large enough to hold + * @p l bytes. + * - The destination is resized as needed to make room for o + l bytes. + * - Overlapping source/destination is handled correctly. + * + * On success, @p to->len is updated as needed and the internal buffer is + * NUL-terminated if @p to is a top-level string. + * + * This function may drop or partially adjust existing tokens when they would + * become invalid. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_cut(String *string, off_t o, size_t l, char *out); + +/** + * @ingroup string + * @fn int string_cut(String *string, off_t o, size_t l, char *out) + * @param string Target string. + * @param o Offset of the substring to cut (relative to @p string). + * @param l Length of the substring to cut. + * @param out Optional destination buffer; if non-NULL, the removed bytes + * are copied there. + * @return 0 on success, -1 on error. + * + * Removes a substring [o, o + l) from @p string and closes the gap by + * shifting subsequent data to the left. + * + * - If @p out is non-NULL, the removed bytes are copied into @p out. + * - The underlying buffer is reused and the string is resized in place. + * - Token metadata are updated to keep existing tokens valid: + * tokens fully inside the removed region are dropped; tokens after the + * removed region are shifted; straddling tokens are truncated. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_append_buffer(String *to, const char *from, size_t len); + +/** + * @ingroup string + * @fn String *string_append_buffer(String *to, const char *from, size_t len) + * @param to Destination string (may be NULL). + * @param from Source buffer. + * @param len Length of @p from in bytes. + * @return NULL on error, otherwise a pointer to the resulting string. + * + * Appends @p from to the end of @p to, resizing it as needed. If @p to is + * NULL, a new String is allocated and initialized with @p from. + * + * If appending to a token whose end matches the current end of the string, + * the append is performed relative to that token to preserve logical token + * structure. Existing tokens are preserved where possible. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_prepend_buffer(String *to, const char *from, size_t len); + +/** + * @ingroup string + * @fn String *string_prepend_buffer(String *to, const char *from, size_t len) + * @param to the destination string. + * @param from the source C string. + * @param len the source C string length. + * @return NULL if an error occurred, 0 otherwise. + * + * This function prepends the source string to the destination string, + * using @ref string_movs() as backend. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_compare_buffer(const String *a, const char *b, size_t len); + +/** + * @ingroup string + * @fn int string_compare_buffer(const String *a, const char *b, size_t len) + * @param a String to compare. + * @param b Raw buffer to compare to. + * @param len Number of bytes in @p b. + * @return memcmp()-style result, or INT_MAX on parameter error. + * + * Compares up to min(a->len, len) bytes of @p a->data against @p b using + * memcmp(). Returns: + * + * - < 0 if @p a is lexicographically less than @p b, + * - > 0 if @p a is greater than @p b, + * - 0 if the prefixes compared are equal, + * - INT_MAX if parameters are invalid. + */ + + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_append(String *to, const String *from); + +/** + * @ingroup string + * @fn String *string_append(String *to, const String *from) + * @param to the destination string. + * @param from the source string. + * @return NULL if an error occurred, 0 otherwise. + * + * This function is simply a wrapper around @ref string_append_buffer(). + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_prepend(String *to, const String *from); + +/** + * @ingroup string + * @fn String *string_prepend(String *to, const String *from) + * @param to the destination string. + * @param from the source string. + * @return NULL if an error occurred, 0 otherwise. + * + * This function is simply a wrapper around @ref string_prepend_buffer(). + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_compare(const String *a, const String *b); + +/** + * @ingroup string + * @fn string_compare(const String *a, const String *b) + * @param a the destination string. + * @param b the source string. + * @return NULL if an error occurred, 0 otherwise. + * + * This function is simply a wrapper around @ref string_cmps(), please see + * the documentation of @ref string_cmps(). + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_upper(String *string); + +/** + * @ingroup string + * @fn int string_upper(String *string) + * @param string + * @return -1 if an error occurred, 0 otherwise + * + * This function simply converts the internal buffer of the given string to + * upper case. + * + * Since the conversion is done in place, no resizing is done and thus + * existing tokens are preserved. + * + * If an error occurs, the function returns -1. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_lower(String *string); + +/** + * @ingroup string + * @fn int string_lower(String *string) + * @param string + * @return -1 if an error occurred, 0 otherwise + * + * This function simply converts the internal buffer of the given string to + * lower case. + * + * Since the conversion is done in place, no resizing is done and thus + * existing tokens are preserved. + * + * If an error occurs, the function returns -1. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_pattern_compile(String_Pattern *p, const char *s, size_t len); + +/** + * @ingroup string + * @fn int string_pattern_compile(String_Pattern *p, const char *s, size_t len) + * @param p Pattern object to initialize. + * @param s Pointer to the pattern bytes ("needle"). + * @param len Length of the pattern in bytes. + * @return 0 on success, -1 on error. + * + * Pre-computes lookup data for a search pattern to be used with + * @ref string_find_pattern(). The pattern data itself is not copied; only + * metadata (length and Boyer–Moore style skip table) are stored in @p p. + * + * For patterns longer than 4 bytes, a tuned Boyer–Moore algorithm is used. + * For patterns of length 1–4, @ref string_find_pattern() falls back to a + * simple, faster naive search and only the effective length is stored. + * + * @warning The pattern length must be in the range [1, 255]. Longer patterns + * are rejected and cause the function to return -1. + * + * @note The caller is responsible for ensuring that the pointer @p s remains + * valid for as long as it is used with @ref string_find_pattern(), or + * that an equivalent pattern buffer is passed as the @p sub parameter + * there. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API off_t string_find_pattern( + const String *s, + size_t o, + const char *sub, + String_Pattern *p +); + +/** + * @ingroup string + * @fn off_t string_find_pattern(const String *s, size_t o, const char *sub, + * String_Pattern *p) + * @param s The "haystack" string to search in. + * @param o Starting offset within @p s (in bytes). + * @param sub Pointer to the pattern bytes ("needle"). + * @param p A pattern object previously initialized with + * @ref string_pattern_compile() for the same pattern. + * @return -1 on error or if the pattern is not found, otherwise the byte + * offset of the first match within @p s. + * + * Searches for a pre-compiled pattern inside @p s, starting at offset @p o. + * The effective pattern length and lookup table are taken from @p p; the + * @p sub pointer is only used to compare candidate matches. + * + * For patterns of length 1–4 (as encoded into @p p by + * @ref string_pattern_compile()), a small naive search is used. For longer + * patterns, an optimized Boyer–Moore style algorithm with a skip table + * is used. + * + * @warning @p p must have been initialized by calling + * @ref string_pattern_compile() with the same pattern bytes and + * length that are referenced by @p sub here. Passing a different + * pattern buffer or length is undefined behaviour. + * + * @note If @p o plus the pattern length stored in @p p exceeds @p s->len, + * the function fails and returns -1. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API off_t string_find( + const String *str, + size_t o, + const char *sub, + size_t len +); + +/** + * @ingroup string + * @fn off_t string_find(const String *str, off_t o, const char *sub, + size_t len ) + * @param str The "haystack" string to search in. + * @param o Starting offset within @p str (in bytes). + * @param sub The "needle" bytes to search for. + * @param len Length of the needle in bytes. + * @return -1 on error or if the substring is not found, otherwise the byte + * offset of the first match within @p str. + * + * Searches for the substring @p sub in @p str, starting at offset @p o. + * Internally this function compiles a temporary search pattern using + * @ref string_pattern_compile() and then calls @ref string_find_pattern(). + * + * @warning The needle length @p len must not exceed 255 bytes. Longer + * needles are rejected and cause the function to return -1. + * + * @note This is a convenience function for one-off searches. If you need to + * search for the same needle multiple times (possibly in different + * strings), it is more efficient to call @ref string_pattern_compile() + * once and then use @ref string_find_pattern() repeatedly. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_split(String *string, const char *pattern, size_t len); + +/** + * @ingroup string + * @fn string_split(String *string, const char *pattern, size_t len) + * @param string the string to be split. + * @param pattern the token delimiter. + * @param len the size of the delimiter. + * @return -1 if an error occurred, 0 otherwise. + * + * This function use @ref string_finds() to split the given @b string, using + * a delimiter passed in parameter. + * + * The parts of a split string are stored in the @ref tokens field of the + * String structure, and their number in the @ref count field. + * + * @note Tokens are views; they do not copy data. However, they can be + * transparently used as valid, resizeable and writeable String with the + * great majority of the functions operating on String structures. + * + * @warning several string manipulation functions destroy tokens to avoid + * corruption, mainly when the underlying data are removed. + * + * You can clean up tokens at any time by calling @ref string_free_token(). + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_merge(String *string, const char *pattern, size_t len); + +/** + * @ingroup string + * @fn int string_merge(String *string, const char *pattern, size_t len) + * @param string the string to be merged + * @param pattern the new token delimiter + * @param len the size of the delimiter + * @return -1 if an error occurred, 0 otherwise. + * + * This function replaces all the delimiters between tokens of the + * target string with the new delimiter given in parameter. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_replace_all( + String *string, + const char *search, + size_t slen, + const char *rep, + size_t rlen +); + +/** + * @ingroup string + * @fn String *string_replace_all(String *string, const char *search, + * size_t slen, const char *rep, size_t rlen) + * @param string the string where an expression should be replaced. + * @param search the expression to be replaced. + * @param slen the length of this expression. + * @param rep the replacement string. + * @param rlen the length of the replacement string. + * @return NULL if an error occurred, a pointer to the main string otherwise. + * + * This function searches for the given @b search string inside the main + * @b string, and replaces each occurrence by the provided @b rep string. + * + * If a NULL @b rep parameter is given, the target substring is deleted + * instead and the string resized accordingly. + * + * The original string is automatically resized to fit the modifications, but + * since it is a destructive transformation, all its token will be deleted + * in order to avoid corruption. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_remove_all(String *str, const char *rem, size_t len); + +/** + * @ingroup string + * @fn String *string_remove_all(String *str, const char *rem, size_t len) + * @param str the string to be processed + * @param rem the substring which must be removed + * @param len the length of the substring + * @return NULL if an error occurred, a pointer to the main string otherwise. + * + * This function is simply a wrapper around @ref string_replace_all() with + * a NULL replacement string. This way, all occurrences of the target substring + * are dropped. + * + * @note Since it calls @ref string_replace_all(), this function cause the + * destruction of all the tokens of the main string. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_select(String *string, unsigned int off, size_t len); + +/** + * @ingroup string + * @fn String *string_select(String *string, unsigned int off, size_t len) + * @param string Parent string. + * @param off Offset from which to create the token. + * @param len Length of the selected region. + * @return Pointer to the new token, or NULL on error. + * + * Convenience function that clears any existing tokens on @p string and then + * creates a single token covering [off, off + len). + * + * Equivalent to: + * @code + * string_free_token(string); + * return string_add_token(string, off, off + len); + * @endcode + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_add_token(String *s, off_t start, off_t end); + +/** + * @ingroup string + * @fn String *string_add_token(String *s, off_t start, off_t end) + * @param s Parent string. + * @param start Start offset (inclusive) relative to @p s->data. + * @param end End offset (exclusive) relative to @p s->data. + * @return Pointer to the newly created token, or NULL on error. + * + * Creates a new token that views the range [start, end) of @p s. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_push_token(String *string, const char *token, size_t len); + +/** + * @ingroup string + * @fn int string_push_tokens(String *string, const char *token, size_t len) + * @param string the string to which the token will be appended + * @param token the data to append + * @param len the length of the data + * @return -1 if an error happens, 0 otherwise + * + * Appends @p token to the end of @p string and creates a token that points to + * the newly appended region. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_suppr_token(String *s, unsigned int index); + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *string_pop_token(String *string); + +/* -------------------------------------------------------------------------- */ +#ifdef HAS_PCRE +/* -------------------------------------------------------------------------- */ + +ASKL_API int string_parse(String *string, const char *pattern, size_t len); + +/** + * @ingroup string + * @fn int string_parse(String *string, const char *pattern, size_t len) + * @param string the string to be processed + * @param pattern the C string holding the regular expression to match + * @param len the length of the pattern + * @return -1 if an error happens, 0 otherwise + * + * This function will match the regular expression @ref pattern with the + * given @ref string. If a match is found, it will be stored as a token. + * + * If you use the parenthesis to extract substrings, you can get the + * substrings as subtokens of the matching token. + * + */ + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ + +ASKL_API void string_free_token(String *string); + +/** + * @ingroup string + * @fn void string_free_token(String *string) + * @param string the string to be cleaned. + * @return void + * + * This function deletes all the tokens of the given string. + * See @ref String or @ref string_split() for more informations about + * the tokens. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API void string_api_cleanup(void); + +/* -------------------------------------------------------------------------- */ + +#endif diff --git a/shims/askl_map/askl_variant.c b/shims/askl_map/askl_variant.c new file mode 100644 index 0000000..99baa40 --- /dev/null +++ b/shims/askl_map/askl_variant.c @@ -0,0 +1,186 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#include "askl_variant.h" + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_from_pointer(void *ptr) +{ + Variant v = { { 0 } }; + v.metadata.fields.type = VALUE_POINTER; + v.value.pointer = ptr; + return v; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_from_integer(uint64_t i) +{ + Variant v = { { 0 } }; + v.metadata.fields.type = VALUE_INTEGER; + v.value.integer = i; + return v; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_from_decimal(double d) +{ + Variant v = { { 0 } }; + v.metadata.fields.type = VALUE_DECIMAL; + v.value.decimal = d; + return v; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_from_boolean(int b) +{ + Variant v = { { 0 } }; + v.metadata.fields.type = VALUE_BOOLEAN; + v.value.integer = !! b; + return v; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_from_string(String *s) +{ + Variant v = { { 0 } }; + v.metadata.fields.type = VALUE_STRING; + v.value.pointer = s; + return v; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_null(void) +{ + Variant v = { { 0 } }; + v.metadata.fields.type = VALUE_NULL; + v.value.pointer = NULL; + return v; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_true(void) +{ + Variant v = { { 0 } }; + v.metadata.fields.type = VALUE_BOOLEAN; + v.value.integer = 1; + return v; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_false(void) +{ + Variant v = { { 0 } }; + v.metadata.fields.type = VALUE_BOOLEAN; + return v; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API void *variant_to_pointer(Variant v) +{ + if (! is_pointer(v)) { + /* tolerate VALUE_NULL and _VALUE_OBJECT */ + if (! _is_object(v) && v.metadata.fields.type != VALUE_NULL) + die("type error"); + } + return v.value.pointer; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API uint64_t variant_to_integer(Variant v) +{ + if (! is_integer(v)) die("type error"); + + return v.value.integer; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API double variant_to_decimal(Variant v) +{ + if (! is_decimal(v)) die("type error"); + return v.value.decimal; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int variant_to_boolean(Variant v) +{ + if (! is_boolean(v)) die("type error"); + return (v.value.integer); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *variant_to_string(Variant v) +{ + if (! is_string(v)) die("type error"); + return (String *) v.value.pointer; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int variant_equal(Variant a, Variant b) +{ + if (a.metadata.fields.type != b.metadata.fields.type) return 0; + + switch (a.metadata.fields.type) { + case VALUE_NULL: return 1; + case VALUE_STRING: return (a.value.pointer == b.value.pointer); + case VALUE_INTEGER: + case VALUE_BOOLEAN: return (a.value.integer == b.value.integer); + case VALUE_DECIMAL: { + return memcmp( + & a.value.decimal, + & b.value.decimal, + sizeof(a.value.decimal) + ) == 0; + } + case VALUE_POINTER: + case _VALUE_OBJECT: return (a.value.pointer == b.value.pointer); + default: return 0; + } +} + +/* -------------------------------------------------------------------------- */ diff --git a/shims/askl_map/askl_variant.h b/shims/askl_map/askl_variant.h new file mode 100644 index 0000000..92aa58f --- /dev/null +++ b/shims/askl_map/askl_variant.h @@ -0,0 +1,333 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifndef ASKL_VARIANT_H + +#define ASKL_VARIANT_H + +#include "askl.h" +#include "askl_string.h" + +/** @defgroup variant ASKL::variant */ + +typedef struct Variant { + union { + void *pointer; + uint64_t integer; + double decimal; /* assume IEEE754 64-bits double precision format */ + } value; + union { + struct { + uint8_t type; /* reserved unless type & _VALUE_OBJECT */ + uint8_t byte; + uint16_t word; + uint32_t dword; + } fields; + struct { + uint8_t type; + uint8_t bytes[7]; + } raw; + } metadata; /* 56 bits of reclaimed padding to store metadata */ +} Variant; + +/** + * @ingroup variant + * @struct Variant + * + * The Variant struct provides a small tagged value type used by ASKL containers + * (e.g. Map and Trie) to store and retrieve values with lightweight runtime + * type identification. + * + * A @ref Variant is passed by value. The value is stored either as: + * - a pointer + * - a 64-bit integer + * - a 64-bit IEEE754 double + * + * A 1-byte type tag is stored in the metadata area. + * + * @note pointer Variants are opaque: the API does not manage the pointed memory + * + */ + + +#define VALUE_NULL 0 +#define VALUE_STRING 1 +#define VALUE_INTEGER 2 +#define VALUE_BOOLEAN 3 +#define VALUE_DECIMAL 4 +#define VALUE_POINTER 5 +#define _VALUE_OBJECT 0x80 + +#define is_null(v) ((v).metadata.fields.type == VALUE_NULL) +#define is_string(v) ((v).metadata.fields.type == VALUE_STRING) +#define is_integer(v) ((v).metadata.fields.type == VALUE_INTEGER) +#define is_boolean(v) ((v).metadata.fields.type == VALUE_BOOLEAN) +#define is_decimal(v) ((v).metadata.fields.type == VALUE_DECIMAL) +#define is_pointer(v) ((v).metadata.fields.type == VALUE_POINTER) +#define _is_object(v) ((v).metadata.fields.type & _VALUE_OBJECT) + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_from_pointer(void *ptr); + +/** + * @ingroup variant + * @fn Variant variant_from_pointer(void *ptr) + * @param ptr pointer value to store + * @return a pointer-typed variant + * + * Build a @ref VALUE_POINTER variant storing @p ptr. + * + * This function does not take ownership of @p ptr. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_from_integer(uint64_t i); + +/** + * @ingroup variant + * @fn Variant variant_from_integer(uint64_t i) + * @param i integer value to store + * @return an integer-typed variant + * + * Build a @ref VALUE_INTEGER variant storing @p i. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_from_decimal(double d); + +/** + * @ingroup variant + * @fn Variant variant_from_decimal(double d) + * @param d double value to store + * @return a decimal-typed variant + * + * Build a @ref VALUE_DECIMAL variant storing @p d. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_from_boolean(int b); + +/** + * @ingroup variant + * @fn Variant variant_from_boolean(int b) + * @param b boolean value (0 is false, non-zero is true) + * @return a boolean-typed variant + * + * Build a @ref VALUE_BOOLEAN variant. + * + * The stored value is normalized to 0 or 1. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_from_string(String *s); + +/** + * @ingroup variant + * @fn Variant variant_from_string(m_string *s) + * @param s pointer to an @ref m_string + * @return a string-typed variant + * + * Build a @ref VALUE_STRING variant storing @p s. + * + * This function stores the pointer as-is. It does not duplicate the string. + * Ownership is not modified; if the string must outlive its original owner, + * the caller must duplicate it prior to storing it. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_null(void); + +/** + * @ingroup variant + * @fn Variant variant_null(void) + * @return a null variant + * + * Return the canonical null value variant (@ref VALUE_NULL). + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_true(void); + +/** + * @ingroup variant + * @fn Variant variant_true(void) + * @return a TRUE boolean variant + * + * Return the canonical TRUE value variant. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API Variant variant_false(void); + +/** + * @ingroup variant + * @fn Variant variant_false(void) + * @return a FALSE boolean variant + * + * Return the canonical FALSE value variant. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API void *variant_to_pointer(Variant v); + +/** + * @ingroup variant + * @fn void *variant_to_pointer(Variant v) + * @param v a variant + * @return the contained pointer + * + * Extract the pointer from @p v. + * + * If @p v is not a pointer variant, the program will terminate with an error. + * Callers should test with @ref is_pointer() first. + * + * @note VALUE_NULL or _VALUE_OBJECT are tolerated. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API uint64_t variant_to_integer(Variant v); + +/** + * @ingroup variant + * @fn uint64_t variant_to_integer(Variant v) + * @param v a variant + * @return the contained integer value + * + * Extract the integer from @p v. + * + * If @p v is not an integer variant, the program will terminate with an error. + * Callers should test with @ref is_integer() first. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API double variant_to_decimal(Variant v); + +/** + * @ingroup variant + * @fn double variant_to_decimal(variant v) + * @param v a variant + * @return the contained double value + * + * Extract the double from @p v. + * + * If @p v is not a decimal variant, the program will terminate with an error. + * Callers should test with @ref is_decimal() first. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int variant_to_boolean(Variant v); + +/** + * @ingroup variant + * @fn int variant_to_boolean(variant v) + * @param v a variant + * @return 0 for false, non-zero for true + * + * Extract the boolean value from @p v. + * + * If @p v is not a boolean variant, the program will terminate with an error. + * Callers should test with @ref is_boolean() first. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API String *variant_to_string(Variant v); + +/** + * @ingroup variant + * @fn String *variant_to_string(Variant v) + * @param v a variant + * @return the contained @ref m_string pointer + * + * Extract the string pointer from @p v. + * + * If @p v is not a string variant, the program will terminate with an error. + * Callers should test with @ref is_string() first. + * + * The returned pointer is not duplicated; ownership is unchanged. + * + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int variant_equal(Variant a, Variant b); + +/** + * @ingroup variant + * @fn int variant_equal(variant a, variant b) + * @param a first variant + * @param b second variant + * @return non-zero if the variants are equal, zero otherwise + * + * This function compares two variants for equality. + * + * Two variants are considered equal if they have the same runtime type and + * carry the same value for that type. + * + * @note For pointer-like types (VALUE_STRING, VALUE_POINTER, _VALUE_OBJECT), + * equality means the pointers are equal; it does not compare the + * pointed-to contents. + * + * @note For VALUE_DECIMAL, this function uses exact representation equality. + * This means that values that compare equal numerically may still be + * considered different if their bit patterns differ (e.g. +0.0 vs -0.0), + * and NaN payloads will only compare equal if their representations match + */ + +#endif diff --git a/shims/askl_map/compat/askl_compat_layer.c b/shims/askl_map/compat/askl_compat_layer.c new file mode 100644 index 0000000..4328894 --- /dev/null +++ b/shims/askl_map/compat/askl_compat_layer.c @@ -0,0 +1,57 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2025 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#include "askl_compat_layer.h" + +/* emulate miscellaneous standard functions if required */ +#include "askl_stdc_compat.c" + +/* low-level file I/O compatibility */ +#include "askl_file_compat.c" + +/* emulate gettimeofday() if necessary and ensure the availability of sleep() */ +#include "askl_time_compat.c" + +/* emulate the mmap() and associated syscalls if they are not available */ +#include "askl_mmap_compat.c" + +/* non-portable networking code */ +#include "askl_socket_compat.c" + +/* non-portable dynamic linking code */ +#include "askl_module_compat.c" + +/* non-portable random seed */ +#include "askl_random_compat.c" diff --git a/shims/askl_map/compat/askl_compat_layer.h b/shims/askl_map/compat/askl_compat_layer.h new file mode 100644 index 0000000..5679204 --- /dev/null +++ b/shims/askl_map/compat/askl_compat_layer.h @@ -0,0 +1,75 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2025 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifndef ASKL_COMPAT_LAYER_H + +#define ASKL_COMPAT_LAYER_H + +#define ASKL_MINIMAL +#include "../askl.h" +#undef ASKL_MINIMAL + +#if defined(_USE_BIG_FDS) + #if ( (_USE_BIG_FDS > FD_SETSIZE) && defined(HAS_POLL) ) + /* can not allow more than SOCKET_MAX descriptors */ + #if (_USE_BIG_FDS > 0x0FFF) + #warning "Can not allow more than SOCKET_MAX descriptors." + #undef _USE_BIG_FDS + #define _USE_BIG_FDS 0x0FFF + #endif + + /* redefine FD_SETSIZE to its maximal value */ + #ifndef FD_SETSIZE + #define FD_SETSIZE _USE_BIG_FDS + #endif + + /* this ugly hack is necessary with glibc */ + #ifdef __linux__ + #include + #undef __FD_SETSIZE + #define __FD_SETSIZE _USE_BIG_FDS + #endif + #endif +#endif + +#include "askl_stdc_compat.h" +#include "askl_file_compat.h" +#include "askl_time_compat.h" +#include "askl_mmap_compat.h" +#include "askl_socket_compat.h" +#include "askl_module_compat.h" +#include "askl_random_compat.h" + +#endif diff --git a/shims/askl_map/compat/askl_file_compat.c b/shims/askl_map/compat/askl_file_compat.c new file mode 100644 index 0000000..7a640d5 --- /dev/null +++ b/shims/askl_map/compat/askl_file_compat.c @@ -0,0 +1,310 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#include "askl_file_compat.h" + +/* -------------------------------------------------------------------------- */ +#ifdef WIN32 /* make WIN32 open() more like its UNIX counterpart */ +/* -------------------------------------------------------------------------- */ + +ASKL_API int posix_open(const char *pathname, int flags, ...) +{ + unsigned int mode = 0; + va_list args; + + /* XXX win32 opens files in _O_TEXT mode by default */ + flags |= _O_BINARY; + + if (flags & O_CREAT) { + va_start(args, flags); + mode = va_arg(args, int); + va_end(args); + return _sopen(pathname, flags, _SH_DENYNO, mode); + } + + return _sopen(pathname, flags, _SH_DENYNO); +} + +/* -------------------------------------------------------------------------- */ +/* Extended Attributes */ +/* -------------------------------------------------------------------------- */ + +/* ntea.c: code for manipulating NTEA information + + Copyright 1997, 1998, 2000, 2001 Red Hat, Inc. + + Written by Sergey S. Okhapkin (sos@prospect.com.ru) + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +typedef struct _FILE_FULL_EA_INFORMATION { + ULONG NextEntryOffset; + UCHAR Flags; + UCHAR EaNameLength; + USHORT EaValueLength; + CHAR EaName[1]; +} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION; + +/* -------------------------------------------------------------------------- */ + +static PFILE_FULL_EA_INFORMATION NTReadEARaw(HANDLE f, int *len) +{ + WIN32_STREAM_ID sid; + DWORD w; + LPVOID ctx = NULL; + DWORD size; + PFILE_FULL_EA_INFORMATION eafound = NULL; + + size = sizeof(WIN32_STREAM_ID) - sizeof(WCHAR **); + + /* read the WIN32_STREAM_ID in */ + while (BackupRead(f, (LPBYTE) & sid, size, & w, FALSE, FALSE, & ctx)) { + DWORD sl, sh; + + /* no more stream ids */ + if (! w) break; + + /* skip StreamName */ + if (sid.dwStreamNameSize) { + unsigned char *buf = NULL; + + if (! (buf = malloc(sid.dwStreamNameSize)) ) break; + + if (! BackupRead(f, buf, sid.dwStreamNameSize, + & w, FALSE, FALSE, & ctx)) { + /* read error */ + free(buf); break; + } + + free(buf); + } + + /* EA stream */ + if (sid.dwStreamId == BACKUP_EA_DATA) { + unsigned char *buf = NULL; + + if (! (buf = malloc(sid.Size.LowPart)) ) break; + + if (! BackupRead(f, buf, sid.Size.LowPart, + & w, FALSE, FALSE, & ctx)) { + /* read error */ + free(buf); break; + } + + eafound = (PFILE_FULL_EA_INFORMATION) buf; + *len = sid.Size.LowPart; + break; + } + + /* skip current stream */ + if (! BackupSeek(f, sid.Size.LowPart, sid.Size.HighPart, + & sl, & sh, & ctx)) + break; + } + + /* free context */ + BackupRead(f, NULL, 0, & w, TRUE, FALSE, & ctx); + + return eafound; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API ssize_t file_getxattr( + const char *file, + const char *attrname, + void *attrbuf, + size_t len +) +{ + HANDLE f; + int eafound = 0; + PFILE_FULL_EA_INFORMATION ea, sea; + int easize; + SECURITY_ATTRIBUTES sec_attr; + + sec_attr.nLength = sizeof(sec_attr); + sec_attr.bInheritHandle = FALSE; + sec_attr.lpSecurityDescriptor = NULL; + + f = CreateFile(file, FILE_READ_EA, FILE_SHARE_READ | FILE_SHARE_WRITE, + & sec_attr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (f == INVALID_HANDLE_VALUE) return -1; + + /* read in raw array of EAs */ + ea = sea = NTReadEARaw(f, & easize); + + /* search for requested attribute */ + while (sea) { + + if (! stricmp(ea->EaName, attrname)) { + + if (ea->EaValueLength > len) { + /* buffer too small */ + eafound = -1; errno = ERANGE; break; + } + + /* EA found, copy the data */ + memcpy(attrbuf, ea->EaName + (ea->EaNameLength + 1), + ea->EaValueLength); + eafound = ea->EaValueLength; + break; + } + + if (! ea->NextEntryOffset || ((int) ea->NextEntryOffset > easize)) + break; + + ea = (PFILE_FULL_EA_INFORMATION) ((char *) ea + ea->NextEntryOffset); + } + + free(sea); + + CloseHandle(f); + + return eafound; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int file_setxattr( + const char *file, + const char *attrname, + const void *buf, + size_t len, + int flags +) +{ + HANDLE f; + WIN32_STREAM_ID sid; + DWORD w; + LPVOID ctx = NULL; + DWORD size, easize; + int ret = -1; + PFILE_FULL_EA_INFORMATION ea; + SECURITY_ATTRIBUTES sec_attr; + + sec_attr.nLength = sizeof(sec_attr); + sec_attr.bInheritHandle = FALSE; + sec_attr.lpSecurityDescriptor = NULL; + + f = CreateFile(file, FILE_WRITE_EA, FILE_SHARE_READ | FILE_SHARE_WRITE, + & sec_attr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (f == INVALID_HANDLE_VALUE) return -1; + + size = sizeof(WIN32_STREAM_ID) - sizeof(WCHAR **); + + /* FILE_FULL_EA_INFORMATION structure is longword-aligned */ + easize = sizeof(*ea) - sizeof(WCHAR **) + strlen(attrname) + 1 + len + + (sizeof(DWORD) - 1); + easize &= ~(sizeof(DWORD) - 1); + + if (! (ea = malloc(easize)) ) goto _cleanup; + + memset(ea, 0, easize); + ea->EaNameLength = strlen(attrname); + ea->EaValueLength = len; + strcpy(ea->EaName, attrname); + memcpy(ea->EaName + (ea->EaNameLength + 1), buf, len); + + /* initialize the stream id */ + sid.dwStreamId = BACKUP_EA_DATA; + sid.dwStreamAttributes = 0; + sid.Size.HighPart = 0; + sid.Size.LowPart = easize; + sid.dwStreamNameSize = 0; + + if (! BackupWrite(f, (LPBYTE) & sid, size, & w, FALSE, FALSE, & ctx)) + goto _cleanup; + + if (! BackupWrite(f, (LPBYTE) ea, easize, & w, FALSE, FALSE, & ctx)) + goto _cleanup; + + ret = 0; + + /* free context */ +_cleanup: + BackupRead(f, NULL, 0, & w, TRUE, FALSE, & ctx); + CloseHandle(f); + free(ea); + + return ret; +} + +/* -------------------------------------------------------------------------- */ +#elif defined(__APPLE__) +/* -------------------------------------------------------------------------- */ + +ASKL_API ssize_t file_getxattr( + const char *file, + const char *attrname, + void *attrbuf, + size_t len +) +{ + #if defined(MAC_OS_X_VERSION_10_4) || defined(__MAC_10_4) + /* XXX getxattr() first appeared in Mac OS X 10.4 */ + return getxattr(file, attrname, attrbuf, len, 0, 0); + #else + errno = ENOSYS; + return -1; + #endif +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int file_setxattr( + const char *file, + const char *attrname, + const void *buf, + size_t len, + int flags +) +{ + #if defined(MAC_OS_X_VERSION_10_4) || defined(__MAC_10_4) + /* XXX setxattr() first appeared in Mac OS X 10.4 */ + return setxattr(file, attrname, buf, len, 0, flags); + #else + errno = ENOSYS; + return -1; + #endif +} + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ diff --git a/shims/askl_map/compat/askl_file_compat.h b/shims/askl_map/compat/askl_file_compat.h new file mode 100644 index 0000000..15c237b --- /dev/null +++ b/shims/askl_map/compat/askl_file_compat.h @@ -0,0 +1,303 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifndef ASKL_FILE_COMPAT_H + +#define ASKL_FILE_COMPAT_H + +/* -------------------------------------------------------------------------- */ +#ifdef WIN32 /* WIN32 file I/O compatibility */ +/* -------------------------------------------------------------------------- */ + +#ifndef __MINGW32__ + #ifndef stat + #define stat _stat + #endif + + #ifndef fstat + #define fstat _fstat + #endif +#endif + +/* open */ +#include +#include +#include +#include +#include + +#ifndef O_RDONLY + #define O_RDONLY _O_RDONLY +#endif + +#ifndef O_WRONLY + #define O_WRONLY _O_WRONLY +#endif + +#ifndef O_RDWR + #define O_RDWR _O_RDWR +#endif + +#ifndef O_CREAT + #define O_CREAT _O_CREAT +#endif + +#ifndef O_APPEND + #define O_APPEND _O_APPEND +#endif + +#ifndef O_EXCL + #define O_EXCL _O_EXCL +#endif + +#ifndef O_TRUNC + #define O_TRUNC _O_TRUNC +#endif + +/* user permissions */ +#ifndef S_IRWXU + #define S_IRWXU (_S_IREAD | _S_IWRITE) +#endif +#ifndef S_IRUSR + #define S_IRUSR _S_IREAD +#endif +#ifndef S_IWUSR + #define S_IWUSR _S_IWRITE +#endif +#ifndef S_IXUSR + #define S_IXUSR 0 +#endif +/* group */ +#ifndef S_IRWXG + #define S_IRWXG (_S_IREAD | _S_IWRITE) +#endif +#ifndef S_IRGRP + #define S_IRGRP _S_IREAD +#endif +#ifndef S_IWGRP + #define S_IWGRP _S_IWRITE +#endif +#ifndef S_IXGRP + #define S_IXGRP 0 +#endif +/* others */ +#ifndef S_IRWXO + #define S_IRWXO (_S_IREAD | _S_IWRITE) +#endif +#ifndef S_IROTH + #define S_IROTH _S_IREAD +#endif +#ifndef S_IWOTH + #define S_IWOTH _S_IWRITE +#endif +#ifndef S_IXOTH + #define S_IXOTH 0 +#endif + +/* access(2) */ +#ifndef access + #define access _access +#endif + +#ifndef F_OK + #define F_OK 0x0 +#endif + +#ifndef R_OK + #define R_OK 0x4 +#endif + +#ifndef W_OK + #define W_OK 0x2 +#endif + +/* X_OK is not portable, fallback to existence */ +#ifndef X_OK + #define X_OK F_OK +#endif + +/* unlink(2) */ +#ifndef unlink + #define unlink _unlink +#endif + +/* utime(2) */ +#include + +#ifndef utime + #define utime _utime + #define utimbuf _utimbuf +#endif + +/* mkdir(2) */ +#ifndef mkdir + #define mkdir(p, m) _mkdir(p) +#endif + +/* open(2) */ +#undef open +#define open posix_open + +ASKL_API int posix_open(const char *pathname, int flags, ...); + +/** + * @fn int posix_open(const char *pathname, int flags, ...) + * @param pathname path to the file to open + * @param flags same semantics as POSIX @c open(2) + * @param ... optional @c mode_t when @c O_CREAT is present in @p flags + * @return a file descriptor on success, or @c -1 if an error occurs + * + * This function provides a POSIX-like @c open(2) interface on Windows. + * + * It calls @_sopen() and: + * - forces binary mode by OR-ing @c _O_BINARY into @p flags; + * - uses @c _SH_DENYNO sharing mode so other processes may access the file. + * + * When @c O_CREAT is present in @p flags, an additional @c mode argument + * must be provided, exactly as for POSIX @c open(2). + */ + +ASKL_API ssize_t file_getxattr( + const char *file, + const char *attrname, + void *attrbuf, + size_t len +); + +ASKL_API int file_setxattr( + const char *file, + const char *attrname, + const void *buf, + size_t len, + int flags +); + +/* -------------------------------------------------------------------------- */ +#else +/* -------------------------------------------------------------------------- */ + +#include +#include +#include +#include + +#ifdef __APPLE__ +ASKL_API ssize_t file_getxattr( + const char *file, + const char *attrname, + void *attrbuf, + size_t len +); + +ASKL_API int file_setxattr( + const char *file, + const char *attrname, + const void *buf, + size_t len, + int flags +); +#else +#define file_getxattr getxattr +#define file_setxattr setxattr +#endif + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ + +/** + * @fn ssize_t file_getxattr(const char *file, const char *attrname, + * void *attrbuf, size_t len) + * + * @param file path to the file whose extended attribute should be read + * @param attrname name of the extended attribute + * @param attrbuf buffer where the attribute value will be copied + * @param len size of @p attrbuf in bytes + * + * @return the number of bytes copied into @p attrbuf on success, or @c -1 + * if an error occurs + * + * This function provides a portable wrapper for retrieving extended file + * attributes. + * + * On Linux and other systems providing @c getxattr(2), it is a direct + * alias of that system call. On Mac OS X, it wraps @c getxattr(2) with + * the appropriate arguments for named attributes. + * + * On Windows, this function emulates extended attributes using NTFS EA + * data streams and the BackupRead API. It searches the EA stream for an + * entry whose name matches @p attrname and, if found, copies its value + * into @p attrbuf. + * + * If the attribute’s stored size exceeds @p len, the function fails with + * @c errno set to @c ERANGE. Callers may use this to size their buffer. + * + * On platforms where extended attributes are not supported, the function + * fails with @c errno set to @c ENOSYS. + */ + +/** + * @fn int file_setxattr(const char *file, const char *attrname, + * const void *buf, size_t len, int flags) + * + * @param file path to the file whose extended attribute should be set + * @param attrname name of the extended attribute + * @param buf pointer to the attribute value to store + * @param len size of the attribute value in bytes + * @param flags platform-specific flags (e.g., @c XATTR_CREATE, + * @c XATTR_REPLACE on systems that support them) + * + * @return 0 on success, or @c -1 if an error occurs + * + * This function provides a portable wrapper for setting extended file + * attributes. + * + * On Linux and other systems providing @c setxattr(2), it is a direct + * alias of that system call. On Mac OS X, it wraps @c setxattr(2) with + * the appropriate arguments for named attributes. + * + * On Windows, this function emulates extended attributes using NTFS EA + * data streams and the BackupWrite API. It writes or replaces the EA + * entry whose name matches @p attrname with the value found in @p buf. + * + * Depending on the platform, @p flags may be ignored. On systems that + * support @c XATTR_CREATE and @c XATTR_REPLACE, callers can use these + * to control creation versus replacement semantics. + * + * On platforms where extended attributes are not supported, the function + * fails with @c errno set to @c ENOSYS. + */ + +#endif diff --git a/shims/askl_map/compat/askl_mmap_compat.c b/shims/askl_map/compat/askl_mmap_compat.c new file mode 100644 index 0000000..a1e4091 --- /dev/null +++ b/shims/askl_map/compat/askl_mmap_compat.c @@ -0,0 +1,408 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#include "askl_mmap_compat.h" + +/* -------------------------------------------------------------------------- */ +#ifdef WIN32 /* WIN32 mmap() compatibility module */ +/* -------------------------------------------------------------------------- */ + +ASKL_API int get_page_size(void) +{ + #ifndef __WINE__ + SYSTEM_INFO info; + GetSystemInfo(& info); + return info.dwPageSize; + #else + /* FIXME dwPageSize is set to 0 under winelib ? */ + return 4096; + #endif +} + +/* -------------------------------------------------------------------------- */ + +/* + _align_malloc and friends, implemented using Microsoft's public + interfaces and with the help of the algorithm description provided + by Wu Yongwei: http://sourceforge.net/mailarchive/message.php?msg_id=3847075 + + I hereby place this implementation in the public domain. + -- Steven G. Johnson (stevenj@alum.mit.edu) +*/ + +#define NOT_POWER_OF_TWO(n) (((n) & ((n) - 1))) +#define UI(p) ((uintptr_t) (p)) +#define CP(p) ((char *) p) + +#define PTR_ALIGN(p0, alignment, offset) \ + ((void *) (((UI(p0) + (alignment + sizeof(void*)) + offset) \ + & (~UI(alignment - 1))) - offset)) + +/* pointer must sometimes be aligned; assume sizeof(void*) is a power of two */ +#define ORIG_PTR(p) (*(((void **) (UI(p) & (~UI(sizeof(void*) - 1)))) - 1)) + +/* -------------------------------------------------------------------------- */ + +static void *_aligned_offset_alloc(size_t size, size_t alignment, size_t offset) +{ + void *p0 = NULL, *p = NULL; + + if (NOT_POWER_OF_TWO(alignment)) { + errno = EINVAL; + return NULL; + } + + if (! size) return NULL; + + if (alignment < sizeof(void *)) alignment = sizeof(void *); + + /* including the extra sizeof(void*) is overkill on a 32-bit + machine, since malloc is already 8-byte aligned, as long + as we enforce alignment >= 8 ...but oh well */ + if (! (p0 = malloc(size + (alignment + sizeof(void *)))) ) + return NULL; + + p = PTR_ALIGN(p0, alignment, offset); ORIG_PTR(p) = p0; + + return p; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int posix_memalign(void **p, size_t alignment, size_t size) +{ + if ( (*p = _aligned_offset_alloc(size, alignment, 0)) ) + return 0; + return -1; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API void posix_memfree(void *memblock) +{ + if (memblock) free(ORIG_PTR(memblock)); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API void *mmap( + void *start, + size_t len, + int prot, + int flags, + int fd, + off_t offset +) +{ + /* + This is a minimal implementation of the *NIX mmap syscall for WIN32. + It supports the following protections flags : + PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC + It handles the following mapping flags: + MAP_FIXED, MAP_SHARED, MAP_PRIVATE (POSIX) and MAP_ANONYMOUS (SVID) + */ + + void *ret = MAP_FAILED; + HANDLE hmap = INVALID_HANDLE_VALUE; + long wprot = 0, wflags = 0; + + if (~flags & MAP_ANONYMOUS) { + if ( (hmap = (HANDLE) _get_osfhandle(fd)) == INVALID_HANDLE_VALUE) { + /* non-file-backed mapping is only allowed with MAP_ANONYMOUS */ + errno = EBADF; + return MAP_FAILED; + } + } + + /* map *NIX protections and flags to their WIN32 equivalents */ + if ( (prot & PROT_READ) && (~prot & PROT_WRITE) ) { + /* read only, maybe exec */ + wprot = (prot & PROT_EXEC) ? (PAGE_EXECUTE_READ) : (PAGE_READONLY); + wflags = (prot & PROT_EXEC) ? (FILE_MAP_EXECUTE) : (FILE_MAP_READ); + } else if (prot & PROT_WRITE) { + /* read/write, maybe exec */ + if ( (flags & MAP_SHARED) && (~flags & MAP_PRIVATE) ) { + /* changes are committed to the file */ + wprot = (prot & PROT_EXEC) ? + (PAGE_EXECUTE_READWRITE) : (PAGE_READWRITE); + wflags = (prot & PROT_EXEC) ? + (FILE_MAP_EXECUTE) : (FILE_MAP_WRITE); + } else if ( (flags & MAP_PRIVATE) && (~flags & MAP_SHARED) ) { + /* does not affect the original file */ + wprot = PAGE_WRITECOPY; wflags = FILE_MAP_COPY; + } else { + /* MAP_PRIVATE + MAP_SHARED is not allowed, abort */ + errno = EINVAL; + return MAP_FAILED; + } + } + + /* create the windows map object */ + hmap = CreateFileMapping(hmap, NULL, wprot, 0, len, NULL); + + if (! hmap) { + /* the fd was checked before, so it must have bad access rights */ + errno = EPERM; + return MAP_FAILED; + } + + /* create a view */ + ret = MapViewOfFileEx( + hmap, + wflags, + 0, + offset, + len, + (flags & MAP_FIXED) ? (start) : (NULL) + ); + + /* drop the map, it will not be deleted until last 'view' is closed */ + CloseHandle(hmap); + + if (! ret) { + /* if MAP_FIXED was set, the address was probably wrong */ + errno = (flags & MAP_FIXED) ? (EINVAL) : (ENOMEM); + return MAP_FAILED; + } + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int munmap(void *start, UNUSED size_t _dummy) +{ + /* + This is a minimal implementation of the *NIX munmap syscall for WIN32. + The size parameter is ignored under Win32. + */ + + if (start == NULL) { errno = EINVAL; return -1; } + + return UnmapViewOfFile(start) ? 0 : -1; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API void *shm_alloc(const char *name, size_t size) +{ + HANDLE hmap = NULL; + void *ret = NULL; + + if (! name || ! size) return NULL; + + /* create a named mapping object */ + hmap = CreateFileMapping( + INVALID_HANDLE_VALUE, + NULL, + PAGE_READWRITE, + 0, + size, + name + ); + + /* we don't want to inadvertently open an existing shared memory object */ + if (GetLastError() == ERROR_ALREADY_EXISTS) { + CloseHandle(hmap); + return NULL; + } + + if (! hmap) { perror(ERR(shm_alloc, CreateFileMapping)); return NULL; } + + /* map the shared memory */ + ret = MapViewOfFileEx(hmap, FILE_MAP_WRITE, 0, 0, size, NULL); + + CloseHandle(hmap); + + if (! ret) { perror(ERR(shm_alloc, MapViewOfFileEx)); return NULL; } + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API void *shm_attach(const char *name, size_t size) +{ + HANDLE hmap = NULL; + void *ret = NULL; + + if (! name || ! size) return NULL; + + hmap = OpenFileMapping(PAGE_READWRITE, 0, name); + if (! hmap) { perror(ERR(shm_alloc, OpenFileMapping)); return NULL; } + + /* map the shared memory */ + ret = MapViewOfFileEx(hmap, FILE_MAP_WRITE, 0, 0, size, NULL); + + CloseHandle(hmap); + + if (! ret) { perror(ERR(shm_alloc, MapViewOfFileEx)); return NULL; } + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API void shm_detach(void *start, UNUSED size_t _dummy) +{ + /* we already dropped the reference of the map object, just unmap it */ + UnmapViewOfFile(start); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API void shm_free(UNUSED const char *_name, void *start, UNUSED size_t _dummy) +{ + /* we already dropped the reference of the map object, just unmap it */ + UnmapViewOfFile(start); +} + +/* -------------------------------------------------------------------------- */ +#else /* POSIX compliant systems */ +/* -------------------------------------------------------------------------- */ + +ASKL_API int get_page_size(void) +{ + return sysconf(_SC_PAGESIZE); +} + +/* -------------------------------------------------------------------------- */ +#ifdef __APPLE__ +/* -------------------------------------------------------------------------- */ + +#if ! defined(MAC_OS_X_VERSION_10_6) && ! defined(__MAC_10_6) + +ASKL_API int posix_memalign(void **p, UNUSED size_t alignment, size_t size) +{ + /* malloc returns 16-byte aligned memory addresses on OS X */ + return ( (*p = malloc(size)) ) ? 0 : -1; +} + +#endif + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ + +ASKL_API void posix_memfree(void *memblock) +{ + free(memblock); +} + +/* -------------------------------------------------------------------------- */ + +/* mmap(2) and munmap(2) are already there, wrap around shm_open(2) */ + +ASKL_API void *shm_alloc(const char *name, size_t size) +{ + int shm = 0; + void *ret = NULL; + + if (! name || ! size) return NULL; + + shm = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); + if (shm == -1) { + perror(ERR(shm_alloc, shm_open)); + return NULL; + } + + if (ftruncate(shm, size) == -1) { + perror(ERR(shm_alloc, ftruncate)); + goto _err_shm; + } + + /* map the shared memory to the process address space */ + ret = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm, 0x0); + if (ret == MAP_FAILED) { + perror(ERR(shm_alloc, mmap)); + goto _err_shm; + } + + close(shm); + + return ret; + +_err_shm: + close(shm); shm_unlink(name); + return NULL; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API void *shm_attach(const char *name, size_t size) +{ + int shm = 0; + void *ret = NULL; + + if (! name || ! size) return NULL; + + /* try to get the shared memory descriptor */ + shm = shm_open(name, O_RDWR, 0); + if (shm == -1) { + perror(ERR(shm_attach, shm_open)); + return NULL; + } + + /* map the shared memory to the process address space */ + ret = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm, 0x0); + if (ret == MAP_FAILED) { + perror(ERR(shm_attach, mmap)); + ret = NULL; + } + + close(shm); + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API void shm_detach(void *start, size_t size) +{ + munmap(start, size); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API void shm_free(const char *name, void *start, size_t size) +{ + /* unmap and unlink */ + munmap(start, size); + shm_unlink(name); +} + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ diff --git a/shims/askl_map/compat/askl_mmap_compat.h b/shims/askl_map/compat/askl_mmap_compat.h new file mode 100644 index 0000000..64a406c --- /dev/null +++ b/shims/askl_map/compat/askl_mmap_compat.h @@ -0,0 +1,237 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifndef ASKL_MMAP_COMPAT_H + +#define ASKL_MMAP_COMPAT_H + +/* -------------------------------------------------------------------------- */ +#ifdef WIN32 /* WIN32 mmap() compatibility */ +/* -------------------------------------------------------------------------- */ + +#define WIN32_LEAN_AND_MEAN +#include +#include + +#ifndef off_t + #ifdef _off_t + #define off_t _off_t + #else + #define off_t long + #endif +#endif + +/* standard mmap() definitions - shamelessly ripped off bits/mman.h */ + +#define MAP_FAILED ((void *) -1) + +/* Protections are chosen from these bits, OR'd together. The + implementation does not necessarily support PROT_EXEC or PROT_WRITE + without PROT_READ. The only guarantees are that no writing will be + allowed without PROT_WRITE and no access will be allowed for PROT_NONE. */ +#define PROT_READ 0x1 /* Page can be read. */ +#define PROT_WRITE 0x2 /* Page can be written. */ +#define PROT_EXEC 0x4 /* Page can be executed. */ +#define PROT_NONE 0x0 /* Page can not be accessed. */ +/* Sharing types (must choose one and only one of these). */ +#define MAP_SHARED 0x01 /* Share changes. */ +#define MAP_PRIVATE 0x02 /* Changes are private. */ +/* Other flags. */ +#define MAP_FIXED 0x10 /* Interpret addr exactly. */ +#define MAP_ANONYMOUS 0x20 /* Don't use a file. */ +#define MAP_ANON MAP_ANONYMOUS + +#ifndef FILE_MAP_EXECUTE + #define FILE_MAP_EXECUTE 0x0 +#endif + +ASKL_API int posix_memalign(void **p, size_t alignment, size_t size); + +ASKL_API void *mmap(void *start, size_t len, int prot, int flags, int fd, + off_t offset ); +ASKL_API int munmap(void *start, UNUSED size_t _dummy); + +/* -------------------------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +#else /* Standard mmap() on SVID compliant systems */ +/* -------------------------------------------------------------------------- */ + +#define _SVID_SOURCE 1 +#include +#include +#include /* S_IRUSR etc... */ +#include /* O_* consts in shm_open(2) */ + +#ifndef MAP_ANON +#define MAP_ANON MAP_ANONYMOUS +#endif + +#ifndef MAP_POPULATE +#define MAP_POPULATE 0x0 /* Linux-specific optimization */ +#endif + +#if ! defined(_SC_PAGESIZE) && defined(_SC_PAGE_SIZE) +#define _SC_PAGESIZE _SC_PAGE_SIZE +#endif + +/* -------------------------------------------------------------------------- */ +#ifdef __APPLE__ +/* -------------------------------------------------------------------------- */ + +#if (! defined(MAC_OS_X_VERSION_10_6) && ! defined(__MAC_10_6)) +/* OS X lacked posix_memalign() before Snow Leopard */ +ASKL_API int posix_memalign(void **p, UNUSED size_t alignment, size_t size); +#endif + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ + +/* common definitions */ +ASKL_API int get_page_size(void); + +/** + * @fn int get_page_size(void) + * @param void + * @return the operating system memory page size in bytes + * + * This function returns the memory page size used by the underlying + * operating system. + * + * On POSIX systems, it wraps @c sysconf(_SC_PAGESIZE). On Windows, it + * wraps @c GetSystemInfo() and returns @c dwPageSize. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API void posix_memfree(void *memblock); + +/** + * @fn void posix_memfree(void *memblock) + * @param memblock a pointer returned by @ref posix_memalign() + * @return void + * + * This function frees a memory block previously allocated with + * @ref posix_memalign(). + * + * On POSIX systems it simply calls @c free(). On Windows it uses the + * internal bookkeeping required by the emulated @ref posix_memalign() + * implementation. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API void *shm_alloc(const char *name, size_t size); + +/** + * @fn void *shm_alloc(const char *name, size_t size) + * + * @param name name of the shared memory object + * @param size size of the shared memory segment in bytes + * + * @return a pointer to the mapped shared memory, or @c NULL on error + * + * This function allocates a new named shared memory segment and maps it + * into the current process address space. + * + * On POSIX systems, it uses @c shm_open(3), @c ftruncate(2), and + * @c mmap(2). On Windows, it uses @c CreateFileMapping() with + * @c INVALID_HANDLE_VALUE and @c MapViewOfFileEx(). + * + * The returned mapping must be detached with @ref shm_detach() and the + * shared memory object must be destroyed with @ref shm_free() when no + * longer needed. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API void *shm_attach(const char *name, size_t size); + +/** + * @fn void *shm_attach(const char *name, size_t size) + * + * @param name name of an existing shared memory object + * @param size expected size of the shared memory segment + * + * @return a pointer to the mapped shared memory, or @c NULL on error + * + * This function opens an existing shared memory segment previously created + * with @ref shm_alloc() and maps it into the current process address space. + * + * The returned mapping must be detached with @ref shm_detach(). + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API void shm_detach(void *start, size_t size); + +/** + * @fn void shm_detach(void *start, size_t size) + * + * @param start address of a mapped shared memory segment + * @param size size of the mapping in bytes (ignored on some platforms) + * + * @return void + * + * This function unmaps a shared memory segment from the current process, + * without destroying the underlying shared memory object. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API void shm_free(const char *name, void *start, size_t size); + +/** + * @fn void shm_free(const char *name, void *start, size_t size) + * + * @param name name of the shared memory object + * @param start address of a mapped shared memory segment + * @param size size of the mapping in bytes + * + * @return void + * + * This function destroys a shared memory object and unmaps the associated + * memory from the current process. After this call, other processes will + * no longer be able to attach to the shared segment using @ref shm_attach(). + */ + +/* -------------------------------------------------------------------------- */ + +#endif diff --git a/shims/askl_map/compat/askl_module_compat.c b/shims/askl_map/compat/askl_module_compat.c new file mode 100644 index 0000000..e9b7d63 --- /dev/null +++ b/shims/askl_map/compat/askl_module_compat.c @@ -0,0 +1,309 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#include "askl_module_compat.h" + +/* -------------------------------------------------------------------------- */ +#ifdef WIN32 +/* -------------------------------------------------------------------------- */ + +#ifdef __GNUC__ +static __thread char _dlerror_buffer[1024]; +#else +static __declspec(thread) char _dlerror_buffer[1024]; +#endif + +INTERNAL const char *dlerror(void) +{ + DWORD err = GetLastError(); + + if (! err) return NULL; + + FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + _dlerror_buffer, + (DWORD) sizeof(_dlerror_buffer), + NULL + ); + + return _dlerror_buffer; +} + +/* -------------------------------------------------------------------------- */ +#elif (defined(__APPLE__)) +/* -------------------------------------------------------------------------- */ + +#if (! defined(MAC_OS_X_VERSION_10_3) && ! defined(__MAC_10_3)) +/* use dlcompat up to Mac OS 10.2.x */ + +/* +Copyright (c) 2002 Peter O'Gorman + +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. +*/ + + +/* Just to prove that it isn't that hard to add Mac calls to your code :) + This works with pretty much everything, including kde3 xemacs and the gimp, + I'd guess that it'd work in at least 95% of cases, use this as your starting + point, rather than the mess that is dlfcn.c, assuming that your code does not + require ref counting or symbol lookups in dependent libraries +*/ + +#define ERR_STR_LEN 256 +static void *dlsymIntern(void *handle, const char *symbol); +static const char *error(int setget, const char *str, ...); + +/* -------------------------------------------------------------------------- */ + +/* Set and get the error string for use by dlerror */ +static const char *error(int setget, const char *str, ...) +{ + static char errstr[ERR_STR_LEN]; + static int err_filled = 0; + const char *retval; + NSLinkEditErrors ler; + int lerno; + const char *dylderrstr; + const char *file; + va_list arg; + + if (setget <= 0) { + + va_start(arg, str); + strncpy(errstr, "dlsimple: ", ERR_STR_LEN); + vsnprintf(errstr + 10, ERR_STR_LEN - 10, str, arg); + va_end(arg); + + /* We prefer to use the dyld error string if getset is 1*/ + if (setget == 0) { + NSLinkEditError(&ler, &lerno, &file, &dylderrstr); + fprintf(stderr, "dyld: %s\n", dylderrstr); + if (dylderrstr && strlen(dylderrstr)) + strncpy(errstr, dylderrstr, ERR_STR_LEN); + } + err_filled = 1; + retval = NULL; + + } else { + retval = (! err_filled) ? NULL : errstr; + err_filled = 0; + } + + return retval; +} + +/* -------------------------------------------------------------------------- */ + +/* dlopen */ +INTERNAL void *dlopen(const char *path, int mode) +{ + void *module = 0; + NSObjectFileImage ofi = 0; + NSObjectFileImageReturnCode ofirc; + static int (*make_private_module_public)(NSModule module) = 0; + unsigned int flags = + NSLINKMODULE_OPTION_RETURN_ON_ERROR | NSLINKMODULE_OPTION_PRIVATE; + + /* If we got no path, the app wants the global namespace, use -1 as the marker + in this case */ + if (! path) return (void *) -1; + + /* Create the object file image, works for things linked + with the -bundle arg to ld */ + ofirc = NSCreateObjectFileImageFromFile(path, &ofi); + + switch (ofirc) { + + case NSObjectFileImageSuccess: + /* It was okay, so use NSLinkModule to link in the image */ + if (!(mode & RTLD_LAZY)) flags += NSLINKMODULE_OPTION_BINDNOW; + module = NSLinkModule(ofi, path,flags); + /* Don't forget to destroy the object file image, unless you like leaks */ + NSDestroyObjectFileImage(ofi); + /* If the mode was global, then change the module, this avoids + multiply defined symbol errors to first load private then make + global. Silly, isn't it. */ + if ((mode & RTLD_GLOBAL)) { + if (!make_private_module_public) { + _dyld_func_lookup("__dyld_NSMakePrivateModulePublic", + (unsigned long *) & make_private_module_public); + } + make_private_module_public(module); + } + break; + + case NSObjectFileImageInappropriateFile: + /* It may have been a dynamic library rather than a bundle, + try to load it */ + module = (void *) NSAddImage(path, NSADDIMAGE_OPTION_RETURN_ON_ERROR); + break; + + case NSObjectFileImageFailure: + error(0,"Object file setup failure : \"%s\"", path); + return 0; + + case NSObjectFileImageArch: + error(0,"No object for this architecture : \"%s\"", path); + return 0; + + case NSObjectFileImageFormat: + error(0,"Bad object file format : \"%s\"", path); + return 0; + + case NSObjectFileImageAccess: + error(0,"Can't read object file : \"%s\"", path); + return 0; + } + + if (! module) error(0, "Can not open \"%s\"", path); + + return module; +} + +/* -------------------------------------------------------------------------- */ + +/* dlsymIntern is used by dlsym to find the symbol */ +static void *dlsymIntern(void *handle, const char *symbol) +{ + NSSymbol *nssym = 0; + + /* If the handle is -1, it is the app global context */ + if (handle == (void *) -1) { + /* Global context, use NSLookupAndBindSymbol */ + if (NSIsSymbolNameDefined(symbol)) nssym = NSLookupAndBindSymbol(symbol); + + } else { + /* Now see if the handle is a struch mach_header * or not, + use NSLookupSymbol in image for libraries, and + NSLookupSymbolInModule for bundles */ + + /* Check for both possible magic numbers depending on x86/ppc byte order */ + if ( (((struct mach_header *) handle)->magic == MH_MAGIC) || + (((struct mach_header *) handle)->magic == MH_CIGAM)) { + if (NSIsSymbolNameDefinedInImage((struct mach_header *) handle, symbol)) { + nssym = NSLookupSymbolInImage((struct mach_header *) handle, + symbol, + NSLOOKUPSYMBOLINIMAGE_OPTION_BIND | + NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR); + } + + } else nssym = NSLookupSymbolInModule(handle, symbol); + } + + if (! nssym) { + error(0, "Symbol \"%s\" Not found", symbol); + return NULL; + } + + return NSAddressOfSymbol(nssym); +} + +/* -------------------------------------------------------------------------- */ + +INTERNAL const char *dlerror(void) +{ + return error(1, (char *) NULL); +} + +/* -------------------------------------------------------------------------- */ + +INTERNAL int dlclose(void *handle) +{ + if ( (((struct mach_header *) handle)->magic == MH_MAGIC) || + (((struct mach_header *) handle)->magic == MH_CIGAM) ) { + error(-1, "Can't remove dynamic libraries on darwin"); + return 0; + } + + if (! NSUnLinkModule(handle, 0)) { + error(0, "unable to unlink module %s", NSNameOfModule(handle)); + return 1; + } + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +/* dlsym, prepend the underscore and call dlsymIntern */ +INTERNAL void *dlsym(void *handle, const char *symbol) +{ + static char undersym[257]; /* Saves calls to malloc(3) */ + int sym_len = strlen(symbol); + void *value = NULL; + char *malloc_sym = NULL; + + if (sym_len < 256) { + snprintf(undersym, 256, "_%s", symbol); + value = dlsymIntern(handle, undersym); + } else { + if ( (malloc_sym = malloc(sym_len + 2)) ) { + sprintf(malloc_sym, "_%s", symbol); + value = dlsymIntern(handle, malloc_sym); + free(malloc_sym); + } else { + error(-1, "Unable to allocate memory"); + } + } + + return value; +} + +#endif /* MAC OS X 10.2 */ + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ diff --git a/shims/askl_map/compat/askl_module_compat.h b/shims/askl_map/compat/askl_module_compat.h new file mode 100644 index 0000000..3c85fe1 --- /dev/null +++ b/shims/askl_map/compat/askl_module_compat.h @@ -0,0 +1,148 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifndef ASKL_MODULE_COMPAT_H + +#define ASKL_MODULE_COMPAT_H + +/* WIN32 LoadLibrary() API is quite similar to the dlfcn functions */ +#ifdef WIN32 + + #define WIN32_LEAN_AND_MEAN + #include + #include + + #define handle_t HANDLE + + /* dlfcn.h compatibility can be achieved with simple macros */ + #define dlopen(f, o) LoadLibrary((f)) + #define dlsym(h, f) GetProcAddress((h), (f)) + #define dlclose(h) (! FreeLibrary((h))) + + /* dlerror is implemented using GetLastError() */ + INTERNAL const char *dlerror(void); + +/* Mac OS 10.2 does not come with the dlopen api */ +#elif (defined(__APPLE__) && ! defined(MAC_OS_X_VERSION_10_3)) + + #define handle_t void * + + /* on Mac OS 10.2, use dlcompat */ + + /* + Copyright (c) 2002 Jorge Acereda & + Peter O'Gorman + + Portions may be copyright others, see the AUTHORS file included with this + distribution. + + Maintained by Peter O'Gorman + + Bug Reports and other queries should go to + + 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 + #include + #include + #include + #include + #include + #include + #include + + #if defined (__GNUC__) && __GNUC__ > 3 + #define dl_restrict __restrict + #else + #define dl_restrict + #endif + + /* + * Structure filled in by dladdr(). + */ + + typedef struct dl_info { + const char *dli_fname; /* Pathname of shared object */ + void *dli_fbase; /* Base address of shared object */ + const char *dli_sname; /* Name of nearest symbol */ + void *dli_saddr; /* Address of nearest symbol */ + } Dl_info; + + extern void *dlopen(const char *path, int mode); + extern void *dlsym(void * dl_restrict handle, const char *dl_restrict symbol); + extern const char *dlerror(void); + extern int dlclose(void *handle); + extern int dladdr(const void *dl_restrict, Dl_info *dl_restrict); + + #define RTLD_LAZY 0x1 + #define RTLD_NOW 0x2 + #define RTLD_LOCAL 0x4 + #define RTLD_GLOBAL 0x8 + #define RTLD_NOLOAD 0x10 + #define RTLD_NODELETE 0x80 + + /* + * Special handle arguments for dlsym(). + */ + #define RTLD_NEXT ((void *) -1) /* Search subsequent objects. */ + #define RTLD_DEFAULT ((void *) -2) /* Use default search algorithm. */ + +#else + + /* it is assumed that other operating systems provide the dlopen api */ + #define handle_t void * + #include + +#endif + +#endif diff --git a/shims/askl_map/compat/askl_random_compat.c b/shims/askl_map/compat/askl_random_compat.c new file mode 100644 index 0000000..a8779f7 --- /dev/null +++ b/shims/askl_map/compat/askl_random_compat.c @@ -0,0 +1,118 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#include "askl_random_compat.h" + +/* -------------------------------------------------------------------------- */ +#ifdef WIN32 /* WIN32 compatibility */ +/* -------------------------------------------------------------------------- */ + +ASKL_API int random_seed(uint32_t *out, size_t words) +{ + HCRYPTPROV prov; + BOOL ret; + size_t bytes; + + if (! out || ! words) return -1; + + bytes = words * sizeof(*out); + + #ifdef PROV_RSA_AES + ret = CryptAcquireContextA( + & prov, + NULL, + NULL, + PROV_RSA_AES, + CRYPT_VERIFYCONTEXT | CRYPT_SILENT + ); + if (! ret) + #endif + ret = CryptAcquireContextA( + & prov, + NULL, + NULL, + PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT | CRYPT_SILENT + ); + + if (! ret) return -1; + + if (! CryptGenRandom(prov, (DWORD) bytes, (BYTE *) out)) { + CryptReleaseContext(prov, 0); + return -1; + } + + CryptReleaseContext(prov, 0); + + return 0; +} + +/* -------------------------------------------------------------------------- */ +#else /* POSIX compatibility */ +/* -------------------------------------------------------------------------- */ + +ASKL_API int random_seed(uint32_t *out, size_t words) +{ + int fd; + size_t i, bytes; + ssize_t ret = 0; + unsigned char *p = (unsigned char *) out; + + if (! out || ! words) return -1; + + bytes = words * sizeof(*out); + + if ( (fd = open("/dev/urandom", O_RDONLY)) == -1) { + perror(ERR(random_seed, open)); + return -1; + } + + for (i = 0; i < bytes; i += ret) { + if ( (ret = read(fd, p + i, bytes - i)) <= 0) { + if (errno == EINTR) continue; + perror(ERR(random_seed, read)); + close(fd); + return -1; + } + } + + close(fd); + + return 0; +} + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ diff --git a/shims/askl_map/compat/askl_random_compat.h b/shims/askl_map/compat/askl_random_compat.h new file mode 100644 index 0000000..fd178a4 --- /dev/null +++ b/shims/askl_map/compat/askl_random_compat.h @@ -0,0 +1,61 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifndef ASKL_RANDOM_COMPAT_H + +#define ASKL_RANDOM_COMPAT_H + +/* -------------------------------------------------------------------------- */ +#ifdef WIN32 /* WIN32 compatibility */ +/* -------------------------------------------------------------------------- */ + +#include +#include + +/* -------------------------------------------------------------------------- */ +#else /* POSIX compatibility */ +/* -------------------------------------------------------------------------- */ + +#include +#include +#include + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ + +ASKL_API int random_seed(uint32_t *out, size_t words); + +#endif diff --git a/shims/askl_map/compat/askl_socket_compat.c b/shims/askl_map/compat/askl_socket_compat.c new file mode 100644 index 0000000..1680526 --- /dev/null +++ b/shims/askl_map/compat/askl_socket_compat.c @@ -0,0 +1,689 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#include "askl_socket_compat.h" + +#ifdef _POSIX_EMULATION +/* this mutex is used to avoid corruption when using inet_ntoa */ +static pthread_mutex_t _not_reentrant = PTHREAD_MUTEX_INITIALIZER; +#endif + +/* -------------------------------------------------------------------------- */ +#ifdef WIN32 /* WIN32 Winsock2 */ +/* -------------------------------------------------------------------------- */ + +/* winsock2 error codes */ +static int err[] = { + 0, WSAEINTR, WSAEBADF, WSAEACCES, WSAEFAULT, WSAEINVAL, + WSAEMFILE, WSAEWOULDBLOCK, WSAEINPROGRESS, WSAEALREADY, + WSAENOTSOCK, WSAEDESTADDRREQ, WSAEMSGSIZE, WSAEPROTOTYPE, + WSAENOPROTOOPT, WSAEPROTONOSUPPORT, WSAESOCKTNOSUPPORT, + WSAEOPNOTSUPP, WSAEPFNOSUPPORT, WSAEAFNOSUPPORT, + WSAEADDRINUSE, WSAEADDRNOTAVAIL, WSAENETDOWN, + WSAENETUNREACH, WSAENETRESET, WSAECONNABORTED, + WSAECONNRESET, WSAENOBUFS, WSAEISCONN, WSAENOTCONN, + WSAESHUTDOWN, WSAETOOMANYREFS, WSAETIMEDOUT, + WSAECONNREFUSED, WSAELOOP, WSAENAMETOOLONG, WSAEHOSTDOWN, + WSAEHOSTUNREACH, WSAENOTEMPTY, WSAEPROCLIM, WSAEUSERS, + WSAEDQUOT, WSAESTALE, WSAEREMOTE, WSASYSNOTREADY, + WSAVERNOTSUPPORTED, WSANOTINITIALISED, WSAEDISCON, + WSAHOST_NOT_FOUND, WSANO_DATA +}; + +/* winsock2 error strings */ +static const char *str[] = { + "No error", "Interrupted system call", + "Bad file number", "Permission denied", + "Bad address", "Invalid argument", + "Too many open sockets", "Operation would block", + "Operation now in progress", + "Operation already in progress", + "Socket operation on non-socket", + "Destination address required", + "Message too long", + "Protocol wrong type for socket", + "Bad protocol option", "Protocol not supported", + "Socket type not supported", + "Operation not supported on socket", + "Protocol family not supported", + "Address family not supported", + "Address already in use", + "Can't assign requested address", + "Network is down", "Network is unreachable", + "Net connection reset", + "Software caused connection abort", + "Connection reset by peer", + "No buffer space available", + "Socket is already connected", + "Socket is not connected", + "Can't send after socket shutdown", + "Too many references, can't splice", + "Connection timed out", + "Connection refused", + "Too many levels of symbolic links", + "File name too long", "Host is down", + "No route to host", "Directory not empty", + "Too many users", "Disc quota exceeded", + "Stale NFS file handle", + "Too many level of remote in path", + "Network system is unavailable", + "Winsock version out of range", + "WSAStartup not yet called", + "Graceful shutdown in progress", + "Host not found", + "No host data of that type was found" +}; + +/* -------------------------------------------------------------------------- */ + +INTERNAL const char *_socket_win32_strerror(void) +{ + unsigned int i = 0, err_count = sizeof(err) / sizeof(err[0]); + + for (i = 0; i < err_count; i ++) + if (errno == err[i]) return str[i]; + + return NULL; +} + +/* -------------------------------------------------------------------------- */ + +INTERNAL ssize_t _socket_sendfile(SOCKET out, int in, off_t *off, size_t len) +{ + HANDLE handle = INVALID_FILE_HANDLE; + off_t current = 0; + + if (off) { + current = _lseek(in, 0, SEEK_CUR); + /* try seeking to the given offset */ + if (_lseek(in, *off, SEEK_SET) == -1) { errno = EINVAL; return -1; } + } + + /* get the windows API file handle */ + if ( (handle = (HANDLE) _get_osfhandle(in)) == INVALID_FILE_HANDLE) { + errno = EBADF; return -1; + } + + /* don't use the TCP_CORK-like feature, since TransmitFile() is blocking; + that way it's still possible to stop writing if the header blocks */ + if (! TransmitFile(out, handle, len, 0, NULL, NULL, 0)) { + errno = EIO; return -1; + } + + if (off) { + /* restore the file offset and increment the given one */ + _lseek(in, current, SEEK_SET); + *off += len; + } + + return len; +} + +/* -------------------------------------------------------------------------- */ +#elif (defined (__linux__)) +/* -------------------------------------------------------------------------- */ + +INTERNAL ssize_t _socket_sendfile(SOCKET out, int in, off_t *off, size_t len) +{ + return sendfile(out, in, off, len); +} + +/* -------------------------------------------------------------------------- */ +#elif (defined (__FreeBSD__)) +/* -------------------------------------------------------------------------- */ + +INTERNAL ssize_t _socket_sendfile(SOCKET out, int in, off_t *off, size_t len) +{ + off_t written = 0, current = 0, p = 0; + int ret = -1; + + if (off) { current = lseek(in, 0, SEEK_CUR); p = *off; } + + if ( (ret = sendfile(in, out, p, len, NULL, & written, 0)) == 0) { + ret = written; + if (off) { + /* restore the file offset and increment the given one */ + lseek(in, current, SEEK_SET); + *off += written; + } + } + + return ret; +} + +/* -------------------------------------------------------------------------- */ +#elif (defined(MAC_OS_X_VERSION_10_5) || defined(__MAC_10_5)) +/* -------------------------------------------------------------------------- */ + +INTERNAL ssize_t _socket_sendfile(SOCKET out, int in, off_t *off, size_t len) +{ + off_t l = len; + int ret = 0; + + if ( (ret = sendfile(out, in, ((off) ? *off : 0), & l, NULL, 0)) == 0) { + if (off) *off += l; + ret = l; + } + + return ret; +} + +/* -------------------------------------------------------------------------- */ +#elif (defined (__sun)) +/* -------------------------------------------------------------------------- */ + +INTERNAL ssize_t _socket_sendfile(SOCKET out, int in, off_t *off, size_t len) +{ + size_t written = 0, current = 0; + int ret = 0; + struct sendfilevec vector; + + if (off) current = lseek(in, 0, SEEK_CUR); + + memset(& vector, 0, sizeof(vector)); + vector.sfv_fd = in; + vector.sfv_off = (off) ? *off : 0; + vector.sfv_len = len; + + if ( (ret = sendfilev(out, & vector, 1, & written)) == 0) { + ret = written; + if (off) { + /* restore the file offset and increment the given one */ + lseek(in, current, SEEK_SET); + *off += written; + } + } + + return ret; +} + +/* -------------------------------------------------------------------------- */ +#else +/* -------------------------------------------------------------------------- */ + +/* use the generic implementation */ +INTERNAL ssize_t _socket_sendfile( + UNUSED SOCKET out, + UNUSED int in, + UNUSED off_t *off, + UNUSED size_t len +) +{ + errno = ENOSYS; + return -1; +} + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ + +/* + This is a small compatibility layer provided for OS or libc which do not + provide the POSIX protocol independant network functions. The assumption is + made that such an OS/libc probably does not either properly implement + IPv6, so only the classic BSD IPv4 API is used for better portability. +*/ + +/* -------------------------------------------------------------------------- */ +#ifdef _POSIX_EMULATION +/* -------------------------------------------------------------------------- */ + +ASKL_API int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) +{ + /** @brief *Very* minimal getaddrinfo() implementation */ + /* + This is a *very* minimal implementation of getaddrinfo(), to be able + to compile and run with an OS/libc which is not POSIX compliant. + This small implementation only meets the needs of ASKL and use the + most common BSD functions. It only supports IPv4. + */ + + struct addrinfo *ai = NULL; + int len = sizeof(struct sockaddr_in); + struct sockaddr_in *sa = NULL; + + /* not a full implementation, bail out if no hint was given */ + if (! res || ! hints) return EAI_FAIL; + + /* only support AI_NUMERICHOST */ + if (! (hints->ai_flags & AI_NUMERICHOST)) return EAI_FAIL; + + /* only support IPv4 */ + if (hints->ai_family != AF_INET) return EAI_FAMILY; + + /* exit conforming to the specs if no node nor service was given */ + if (! node && ! service) return EAI_NONAME; + + if ( ! (ai = malloc(sizeof(*ai))) ) return EAI_MEMORY; + ai->ai_family = hints->ai_family; ai->ai_socktype = hints->ai_socktype; + ai->ai_protocol = hints->ai_protocol; ai->ai_addrlen = len; + + if (! (ai->ai_addr = malloc(len)) ) { free(ai); return EAI_MEMORY; } + sa = (void *) ai->ai_addr; memset(sa, 0, len); + + sa->sin_family = AF_INET; + sa->sin_addr.s_addr = (hints->ai_flags & AI_PASSIVE) ? + (0x0) : (node ? inet_addr(node) : -1); + if (service) sa->sin_port = htons((short) atoi(service)); + + ai->ai_next = NULL; *res = ai; + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int getnameinfo(const struct sockaddr *s, int salen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags) +{ + /** @brief *Very* minimal getnameinfo() implementation */ + /* + This is a *very* minimal implementation of getnameinfo(), to be able + to compile and run with an OS/libc which is not POSIX compliant. + This small implementation only meets the needs of naer and use the + most common BSD functions. It only supports IPv4. + */ + + const char *ip = NULL; + struct sockaddr_in *sa = (void *) s; + + /* neither hostname nor service name were requested */ + if (! host && ! serv) return EAI_NONAME; + + /* wrong address */ + if (! sa || salen != sizeof(*sa)) return EAI_FAMILY; + + /* minimal implementation, only support NI_NUMERICHOST & NI_NUMERICSERV */ + if (host && (flags & NI_NUMERICHOST) ) { + pthread_mutex_lock(& _not_reentrant); + if ((ip = inet_ntoa(sa->sin_addr))) strncpy(host, ip, hostlen - 1); + pthread_mutex_unlock(& _not_reentrant); + } else return EAI_FAIL; + + if (serv && (flags & NI_NUMERICSERV) ) + snprintf(serv, servlen - 1, "%i", ntohs(sa->sin_port)); + else return EAI_FAIL; + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API void freeaddrinfo(struct addrinfo *res) +{ + /** @brief *Very* minimal freeaddrinfo() implementation */ + /* + This is a *very* minimal implementation of freeaddrinfo(), to be able + to compile and run with an OS/libc which is not POSIX compliant. + This small implementation only meets the needs of ASKL and use the + most common BSD functions. It only supports IPv4. + */ + + if (res && res->ai_addr) free(res->ai_addr); free(res); +} + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ + +/* + * The following implements sendfd() and recvfd() to send or receive sockets + * over Unix domain sockets. + * + */ + +/* -------------------------------------------------------------------------- */ +#ifdef WIN32 +/* -------------------------------------------------------------------------- */ + +#ifndef AF_UNIX +#define AF_UNIX 0x0 +#endif + +ASKL_API int socketpair(UNUSED int d, UNUSED int t, UNUSED int p, SOCKET sv[2]) +{ + SOCKET s = INVALID_SOCKET; + struct sockaddr_in addr; + int len = sizeof(addr); + + sv[0] = sv[1] = INVALID_SOCKET; + + if ( (s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { + _socket_perror(ERR(socketpair, socket)); + goto _err_sock; + } + + memset(& addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(0); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(s, (SOCKADDR *) & addr, len) == SOCKET_ERROR) { + _socket_perror(ERR(socketpair, bind)); + goto _err_pair; + } + + if (listen(s, 1) == SOCKET_ERROR) { + _socket_perror(ERR(socketpair, listen)); + goto _err_pair; + } + + if (getsockname(s, (struct sockaddr *) & addr, & len) == SOCKET_ERROR) { + _socket_perror(ERR(socketpair, getsockname)); + goto _err_pair; + } + + if ( (sv[1] = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { + _socket_perror(ERR(socketpair, socket)); + goto _err_pair; + } + + if (connect(sv[1], (struct sockaddr *) & addr, len) == SOCKET_ERROR) { + _socket_perror(ERR(socketpair, connect)); + goto _err_conn; + } + + sv[0] = accept(s, (struct sockaddr *) & addr, & len); + if (sv[0] == INVALID_SOCKET) { + _socket_perror(ERR(socketpair, accept)); + goto _err_conn; + } + + closesocket(s); + + return 0; + +_err_conn: + closesocket(sv[1]); sv[1] = INVALID_SOCKET; +_err_pair: + closesocket(s); +_err_sock: + return -1; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int socket_sendfd(SOCKET socket, SOCKET fd) +{ + WSAPROTOCOL_INFO info; + DWORD pid = 0; + + /* the receiver must give us a pid */ + if (recv(socket, (char *) & pid, sizeof(pid), 0x0) == -1) { + _socket_perror(ERR(socket_sendfd, recv)); + return -1; + } + + if (WSADuplicateSocket(fd, pid, & info) == -1) { + _socket_perror(ERR(socket_sendfd, WSADuplicateSocket)); + return -1; + } + + /* send the raw struct over the wire */ + if (send(socket, (char *) & info, sizeof(info), 0x0) == -1) { + _socket_perror(ERR(socket_sendfd, send)); + return -1; + } + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API SOCKET socket_recvfd(SOCKET socket) +{ + WSAPROTOCOL_INFO info; + DWORD pid = _getpid(); + SOCKET ret = INVALID_SOCKET; + + if (send(socket, (char *) & pid, sizeof(pid), 0x0) == -1) { + _socket_perror(ERR(socket_recvfd, send)); + return INVALID_SOCKET; + } + + if (recv(socket, (char *) & info, sizeof(info), 0x0) == -1) { + _socket_perror(ERR(socket_recvfd, recv)); + return INVALID_SOCKET; + } + + if ( (ret = WSASocket(-1, -1, -1, & info, 0, 0x0)) == INVALID_SOCKET) { + _socket_perror(ERR(socket_recvfd, WSASocket)); + return INVALID_SOCKET; + } + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API SOCKET dupsocket(SOCKET s) +{ + HANDLE h; + BOOL ret = DuplicateHandle( + GetCurrentProcess(), + (HANDLE) s, + GetCurrentProcess(), + & h, + 0, + FALSE, + DUPLICATE_SAME_ACCESS + ); + + if (! ret) { + WSASetLastError(WSAEBADF); + _socket_perror(ERR(dupsocket, DuplicateHandle)); + return INVALID_SOCKET; + } + + return (SOCKET) h; +} + +/* -------------------------------------------------------------------------- */ +#else +/* -------------------------------------------------------------------------- */ + +/* + * Copyright (c) 2000 Sampo Kellomaki , All Rights Reserved. + * This module may be copied under the same terms as the perl itself. + * + */ + +/* I test here for __sun for lack of anything better, but I + * mean Solaris 2.6. The idea of undefining SCM_RIGHTS is + * to force the headers to behave BSD 4.3 way which I have + * tested to work. + * + * In general, if you have compilation errors, you might consider + * adding a test for your platform here. + */ +#if defined(__sun) +#undef SCM_RIGHTS +#endif + +#ifdef SCM_RIGHTS + +/* It seems various versions of glibc headers (i.e. + * /usr/include/socketbits.h) miss one or more of these */ + +#ifndef CMSG_DATA +# define CMSG_DATA(cmsg) ((cmsg)->cmsg_data) +#endif + +#ifndef CMSG_NXTHDR +# define CMSG_NXTHDR(mhdr, cmsg) __cmsg_nxthdr (mhdr, cmsg) +#endif + +#ifndef CMSG_FIRSTHDR +# define CMSG_FIRSTHDR(mhdr) \ + ((size_t) (mhdr)->msg_controllen >= sizeof (struct cmsghdr) \ + ? (struct cmsghdr *) (mhdr)->msg_control : (struct cmsghdr *) NULL) +#endif + +#ifndef CMSG_ALIGN +# define CMSG_ALIGN(len) (((len) + sizeof (size_t) - 1) \ + & ~(sizeof (size_t) - 1)) +#endif + +#ifndef CMSG_SPACE +# define CMSG_SPACE(len) (CMSG_ALIGN (len) \ + + CMSG_ALIGN (sizeof (struct cmsghdr))) +#endif + +#ifndef CMSG_LEN +# define CMSG_LEN(len) (CMSG_ALIGN (sizeof (struct cmsghdr)) + (len)) +#endif + +union fdmsg { + struct cmsghdr h; + char buf[CMSG_SPACE(sizeof(int))]; +}; +#endif + +ASKL_API int socket_sendfd(int sock, int fd) +{ + int ret = 0; + struct iovec iov[1]; + struct msghdr msg; + + iov[0].iov_base = & ret; /* Don't send any data. Note: der Mouse + * says + * that might work better if at least one + * byte is sent. */ + iov[0].iov_len = 1; + + msg.msg_iov = iov; + msg.msg_iovlen = 1; + msg.msg_name = 0; + msg.msg_namelen = 0; + + { + #ifdef SCM_RIGHTS + /* New BSD 4.4 way (ouch, why does this have to be so convoluted). */ + union fdmsg cmsg; + struct cmsghdr *h; + + msg.msg_control = cmsg.buf; + msg.msg_controllen = sizeof(union fdmsg); + msg.msg_flags = 0; + + h = CMSG_FIRSTHDR(&msg); + h->cmsg_len = CMSG_LEN(sizeof(int)); + h->cmsg_level = SOL_SOCKET; + h->cmsg_type = SCM_RIGHTS; + *CMSG_DATA(h) = fd; + #else + /* Old BSD 4.3 way. Not tested. */ + msg.msg_accrights = & fd; + msg.msg_accrightslen = sizeof(fd); + #endif + + ret = (sendmsg(sock, & msg, 0) < 0) ? 0 : 1; + } + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int socket_recvfd(int sock) +{ + int count; + int ret = 0; + struct iovec iov[1]; + struct msghdr msg; + + iov[0].iov_base = & ret; /* don't receive any data */ + iov[0].iov_len = 1; + + msg.msg_iov = iov; + msg.msg_iovlen = 1; + msg.msg_name = NULL; + msg.msg_namelen = 0; + + { + #ifdef SCM_RIGHTS + union fdmsg cmsg; + struct cmsghdr *h; + + msg.msg_control = cmsg.buf; + msg.msg_controllen = sizeof(union fdmsg); + msg.msg_flags = 0; + + h = CMSG_FIRSTHDR(& msg); + h->cmsg_len = CMSG_LEN(sizeof(int)); + h->cmsg_level = SOL_SOCKET; /* Linux does not set these */ + h->cmsg_type = SCM_RIGHTS; /* upon return */ + *CMSG_DATA(h) = -1; + + if ((count = recvmsg(sock, & msg, 0)) < 0) { + ret = 0; + } else { + h = CMSG_FIRSTHDR(& msg); /* can realloc? */ + if ( h == NULL + || h->cmsg_len != CMSG_LEN(sizeof(int)) + || h->cmsg_level != SOL_SOCKET + || h->cmsg_type != SCM_RIGHTS ) { + /* This should really never happen */ + if (h) + fprintf(stderr, + "%s:%d: protocol failure: %u %d %d\n", + __FILE__, __LINE__, + (unsigned int) h->cmsg_len, + h->cmsg_level, h->cmsg_type); + else + fprintf(stderr, + "%s:%d: protocol failure: NULL cmsghdr*\n", + __FILE__, __LINE__); + ret = 0; + } else { + ret = *CMSG_DATA(h); + } + } + #else + int receive_fd; + msg.msg_accrights = & receive_fd; + msg.msg_accrightslen = sizeof(receive_fd); + + ret = (recvmsg(sock, & msg, 0) < 0) ? 0 : receive_fd; + #endif + } + + return ret; +} + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ diff --git a/shims/askl_map/compat/askl_socket_compat.h b/shims/askl_map/compat/askl_socket_compat.h new file mode 100644 index 0000000..d5f2bab --- /dev/null +++ b/shims/askl_map/compat/askl_socket_compat.h @@ -0,0 +1,384 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2026 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifndef ASKL_SOCKET_COMPAT_H + +#define ASKL_SOCKET_COMPAT_H + +/* -------------------------------------------------------------------------- */ +#ifdef WIN32 /* WIN32 Winsock2 compatibility module */ +/* -------------------------------------------------------------------------- */ + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#undef socklen_t +#include +#include +#include +#if defined(_MSC_VER) && (_MSC_VER < 1300) +#include +#endif + +#ifndef off_t + #ifdef _off_t + #define off_t _off_t + #else + #define off_t long + #endif +#endif + +#ifndef socklen_t + #define socklen_t size_t +#endif + +#ifndef INVALID_FILE_HANDLE + #define INVALID_FILE_HANDLE ((HANDLE)INVALID_HANDLE_VALUE) +#endif + +#ifndef ERR + #ifndef STRINGIFY + #define STRINGIFY(x) #x + #endif + #ifndef STR + #define STR(x) STRINGIFY(x) + #endif + #define ERR(c, f) #c "()::" #f "() @ " __FILE__ ":" STR(__LINE__) +#endif + +/* map the WIN32 API functions to their BSD counterparts */ +#define ioctl(s, i, l) ioctlsocket((s), (i), (l)) + +/* map useful Winsock2 error codes to the standard BSD constants */ +#undef EINTR +#define EINTR WSAEINTR +#undef EWOULDBLOCK +#define EWOULDBLOCK WSAEWOULDBLOCK +#undef EAGAIN +#define EAGAIN EWOULDBLOCK +#undef EINPROGRESS +#define EINPROGRESS WSAEINPROGRESS +#undef EALREADY +#define EALREADY WSAEALREADY +#undef ESPIPE +#define ESPIPE EWOULDBLOCK +#undef EISCONN +#define EISCONN WSAEISCONN + +/* Winsock2 does not use errno - work around with some macros */ +#define ERRNO ( (errno = WSAGetLastError()) ) +#define _socket_perror(s) \ +(fprintf(stderr, "%s: %s\n", (s), _socket_win32_strerror())) + +#if defined(_MSC_VER) + /* include the needed libs */ + #pragma comment ( lib, "ws2_32.lib" ) + #pragma comment ( lib, "pthreadVC2.lib" ) + #if (_MSC_VER < 1300) + /* Microsoft Visual C++ 6 does not support POSIX networking functions */ + #define _POSIX_EMULATION + #endif +#elif defined(__GNUC__) + /* for some reasons, gai_strerror() does not link in Dev-C++ */ + #undef gai_strerror + #define gai_strerror(i) ("unknown error") +#endif + +#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0501 + /* Windows 2k ws2_32.dll does not provide the POSIX networking functions */ + #ifndef _POSIX_EMULATION + #define _POSIX_EMULATION + #endif +#endif + +/* -------------------------------------------------------------------------- */ + +ASKL_API int socketpair( + UNUSED int d, + UNUSED int t, + UNUSED int p, + SOCKET sv[2] +); + +/** + * @ingroup socket + * @fn int socketpair(int d, int t, int p, int sv[2]) + * + * @param d the communication domain (ignored on Windows) + * @param t the socket type (ignored on Windows) + * @param p the protocol (ignored on Windows) + * @param sv an array of two integers that will receive the created sockets + * + * @return 0 on success, -1 if an error occurs + * + * This function creates a pair of connected sockets, storing them in + * @p sv[0] and @p sv[1], emulating @c socketpair(AF_UNIX, SOCK_STREAM, 0, sv) + * using a loopback TCP connection. + * + * The caller is responsible for closing both sockets when they are no + * longer needed. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API SOCKET dupsocket(SOCKET s); + +/** + * @ingroup socket + * @fn SOCKET dupsocket(SOCKET s) + * @param s an existing socket descriptor or handle + * @return on success, a new socket descriptor/handle referring to the + * same underlying endpoint; on error, INVALID_SOCKET (Windows) + * or -1 (POSIX) is returned + * + * This function duplicates a socket descriptor. + * + * On POSIX systems, it is an alias for @c dup(). + * On Windows, it uses DuplicateHandle() to create a new Winsock-compatible + * handle referring to the same underlying socket. + * + * The returned socket must eventually be closed independently with + * @c closesocket() (Windows) or @c close() (POSIX). + */ + +/* -------------------------------------------------------------------------- */ + +INTERNAL const char *_socket_win32_strerror(void); + +/** + * @ingroup socket + * @fn const char *_socket_win32_strerror(void) + * + * @param void + * + * @return a pointer to a static, human-readable error message string, + * or NULL if the current error code is unknown + * + * This private helper translates the current Winsock error code stored + * in @c errno into a human-readable error string. + * + * It is only available on Windows builds and is used internally by + * the @_socket_perror macro to report socket-related errors. + * + * The returned pointer refers to static storage and must not be freed + * or modified by the caller. + */ + +/* -------------------------------------------------------------------------- */ +#else /* POSIX compatibility */ +/* -------------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include + +#if defined(_USE_BIG_FDS) && defined(HAS_POLL) +#include +#endif + +#define _socket_perror perror +#define ERRNO errno +#define SOCKET int +#define closesocket(s) close((s)) +#define dupsocket(s) dup((s)) +#define INVALID_SOCKET -1 + +#if ! defined(TCP_CORK) && defined(TCP_NOPUSH) + #define TCP_CORK TCP_NOPUSH +#endif + +/* sendfile */ +#if defined(__linux__) + #include +#else + #if defined(__FreeBSD__) + #include + #include + #elif defined(__sun) + #include + #endif +#endif + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ + +INTERNAL ssize_t _socket_sendfile(SOCKET out, int in, off_t *off, size_t len); + +/** + * @ingroup socket + * @fn ssize_t _socket_sendfile(int out, int in, off_t *off, size_t len) + * + * @param out destination socket file descriptor + * @param in source file descriptor (regular file) + * @param off optional pointer to a file offset, updated on success + * @param len maximum number of bytes to transfer + * + * @return the number of bytes sent, or -1 if an error occurs + * + * This is a low-level, platform-specific helper used to implement sendfile(). + * On platforms without a native sendfile-like system call, it returns -1 and + * sets errno to ENOSYS. + */ + +/* -------------------------------------------------------------------------- */ +#ifdef _POSIX_EMULATION +/* -------------------------------------------------------------------------- */ + +/* + This is a small compatibility layer provided for OS or libc which do not + implement the POSIX protocol independant network functions. The assumption is + made that such an OS/libc probably does not either properly implement + IPv6, so only the classic BSD IPv4 API is used for a better portability. +*/ + +#if (defined(_MSC_VER) && (_MSC_VER < 1300)) +/* define the addrinfo structure */ +struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; +}; + +/* use the classic sockaddr_in */ +#define sockaddr_storage sockaddr_in + +/* netdb.h definitions */ +#define AI_PASSIVE 0x0001 /* Socket address is intended for `bind'. */ +#define AI_NUMERICHOST 0x0004 /* Don't use name resolution. */ +#define EAI_NONAME -2 /* NAME or SERVICE is unknown. */ +#define EAI_FAIL -4 /* Non-recoverable failure in name res. */ +#define EAI_FAMILY -6 /* `ai_family' not supported. */ +#define EAI_MEMORY -10 /* Memory allocation failure. */ +#define NI_NUMERICHOST 1 /* Don't try to look up hostname. */ +#define NI_NUMERICSERV 2 /* Don't convert port number to name. */ + +/* default size for the host and service buffers */ +#define NI_MAXHOST 1025 +#define NI_MAXSERV 32 +#endif + +/* use the minimalist implementation of the POSIX functions */ +#undef getaddrinfo +#undef getnameinfo +#undef freeaddrinfo +#define getaddrinfo _getaddrinfo +#define getnameinfo _getnameinfo +#define freeaddrinfo _freeaddrinfo + +/* remove gai_strerror */ +#undef gai_strerror +#define gai_strerror(i) "unknown error." + +/* -------------------------------------------------------------------------- */ + +ASKL_API int getaddrinfo( + const char *node, + const char *service, + const struct addrinfo *hints, + struct addrinfo **res +); + +ASKL_API int getnameinfo( + const struct sockaddr *s, + int salen, + char *host, + size_t hostlen, + char *serv, + size_t servlen, + int flags +); + +ASKL_API void freeaddrinfo(struct addrinfo *res); + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ + +/* a small private macro for easily printing errors */ +#define _gai_perror(s, i) (fprintf(stderr, "%s: %s\n", (s), gai_strerror((i)))) + +ASKL_API int socket_sendfd(SOCKET sock, SOCKET fd); + +/** + * @ingroup socket + * @fn int socket_sendfd(int sock, int fd) + * + * @param sock a connected Unix-domain (or emulated) socket + * @param fd an open file descriptor to send + * + * @return 1 on success, 0 or -1 on error depending on platform + * + * This function sends a file descriptor over a Unix-domain socket using + * SCM_RIGHTS on POSIX systems or WSADuplicateSocket() on Windows. + */ + +ASKL_API SOCKET socket_recvfd(SOCKET sock); + +/** + * @ingroup socket + * @fn int socket_recvfd(int sock) + * + * @param sock a connected Unix-domain (or emulated) socket + * + * @return on success, the received file descriptor (or socket handle); + * on error, a non-positive value is returned + * + * This function receives a file descriptor sent over a Unix-domain socket + * using @ref socket_sendfd(). + * + * On POSIX systems, the descriptor is received via SCM_RIGHTS ancillary + * data. On Windows, the function uses the WSADuplicateSocket() mechanism + * internally to reconstruct a duplicate socket handle in the current + * process. + * + * A strictly positive return value is the received descriptor. Any + * non-positive value indicates a failure; the exact error reporting + * conventions are platform-dependent (0 or -1). + * + * @see socket_sendfd() + */ + +#endif diff --git a/shims/askl_map/compat/askl_stdc_compat.c b/shims/askl_map/compat/askl_stdc_compat.c new file mode 100644 index 0000000..b6a988a --- /dev/null +++ b/shims/askl_map/compat/askl_stdc_compat.c @@ -0,0 +1,198 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2025 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#include "askl_stdc_compat.h" + +/* -------------------------------------------------------------------------- */ +#ifdef WIN32 /* strtoll()/strtoull() compatibility */ +/* -------------------------------------------------------------------------- */ + +#ifndef _strtoi64 +/* Code from cURL, copyright notice below: */ + +/* + * COPYRIGHT AND PERMISSION NOTICE + * + * Copyright (c) 1996 - 2006, Daniel Stenberg, . + * + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * 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 OF THIRD PARTY RIGHTS. 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. + * + * Except as contained in this notice, the name of a copyright holder shall not + * be used in advertising or otherwise to promote the sale, use or other dealings + * in this Software without prior written authorization of the copyright holder. + * + */ + +/** + * Returns the value of c in the given base, or -1 if c cannot + * be interpreted properly in that base (i.e., is out of range, + * is a null, etc.). + * + * @param c the character to interpret according to base + * @param base the base in which to interpret c + * + * @return the value of c in base, or -1 if c isn't in range + */ + +static int _getch(char c, int base) +{ + int value = -1; + + if (c <= '9' && c >= '0') + value = c - '0'; + else if (c <= 'Z' && c >= 'A') + value = c - 'A' + 10; + else if (c <= 'z' && c >= 'a') + value = c - 'a' + 10; + + if (value >= base) value = -1; + + return value; +} + +/** + * Emulated version of the strtoll function. This extracts a long long + * value from the given input string and returns it. + */ +int64_t strtoll(const char *nptr, char **endptr, int base) +{ + char *end; + int is_negative = 0; + int overflow; + int i; + int64_t value = 0; + int64_t newval; + + /* skip leading whitespace. */ + end = (char *) nptr; while (isspace((int) end[0])) end++; + + /* handle the sign, if any. */ + if (end[0] == '-') { + is_negative = 1; end++; + } else if (end[0] == '+') { + end++; + } else if (end[0] == '\0') { + /* nothing but perhaps some whitespace -- there was no number. */ + if (endptr) *endptr = end; + return 0; + } + + /* handle special beginnings, if present and allowed. */ + if (end[0] == '0' && end[1] == 'x') + if (base == 16 || base == 0) { end += 2; base = 16; } + else if (end[0] == '0') + if (base == 8 || base == 0) { end++; base = 8; } + + /* matching strtol, if the base is 0 and it doesn't look like + * the number is octal or hex, assume it's base 10. + */ + if (base == 0) base = 10; + + /* loop handling digits. */ + value = 0; overflow = 0; + + for (i = _getch(end[0], base); i != -1; end++, i = _getch(end[0], base)) { + newval = base * value + i; + if (newval < value) { + /* overflow */ + overflow = 1; + break; + } else value = newval; + } + + if (!overflow) { + /* fix the sign */ + if (is_negative) value *= -1; + } else { + value = (is_negative) ? LLONG_MIN : LLONG_MAX; + errno = ERANGE; + } + + if (endptr) *endptr = end; + + return value; +} +#endif + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ +#if defined(_MSC_VER) || defined(_WIN32) || \ + ! (defined(__APPLE__) || defined(_DARWIN_C_SOURCE) || \ + defined(_GNU_SOURCE) || defined(_DEFAULT_SOURCE) || \ + defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || \ + (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L) || \ + (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700)) +/* -------------------------------------------------------------------------- */ + +char *strndup(const char *s, size_t len) +{ + char *ret; + size_t slen; + + if (! s) goto _err_params; + + for (slen = 0; slen < len; slen ++) + if (s[slen] == '\0') break; + + if (slen >= SIZE_MAX - 1 || ! (ret = malloc(slen + 1))) { + errno = ENOMEM; + return NULL; + } + + memcpy(ret, s, slen); ret[slen] = '\0'; + + return ret; + +_err_params: + errno = EINVAL; + return NULL; +} + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ diff --git a/shims/askl_map/compat/askl_stdc_compat.h b/shims/askl_map/compat/askl_stdc_compat.h new file mode 100644 index 0000000..97e0c20 --- /dev/null +++ b/shims/askl_map/compat/askl_stdc_compat.h @@ -0,0 +1,241 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2025 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifndef ASKL_STDC_COMPAT_H + +#define ASKL_STDC_COMPAT_H + +#include + +/* math.h */ + +#ifndef NAN + +#if defined(_MSC_VER) && (_MSC_VER < 1300) + #include + #include + #define NAN _Nan._D + #define isnan _isnan +#else + #define NAN (0.0 / 0.0) +#endif + +#endif + +#ifndef INF +#define INF (1e+999) +#if defined(_MSC_VER) && (_MSC_VER < 1300) +#define isinf(x) (!_finite((x)) && !_isnan((x))) +#endif +#endif + +/* long long support */ + +#ifndef LLONG_MAX + /* Minimum and maximum values a `signed long long int' can hold. */ + #ifdef _I64_MAX + #define LLONG_MAX _I64_MAX + #else + #define LLONG_MAX 9223372036854775807LL + #endif +#endif + +#ifndef LLONG_MIN + #ifdef _I64_MIN + #define LLONG_MIN ((__int64) _I64_MIN) + #else + #define LLONG_MIN (-LLONG_MAX - 1LL) + #endif +#endif + +#ifndef ULLONG_MAX + /* Maximum value an `unsigned long long int' can hold. (Minimum is 0.) */ + #ifdef _UI64_MAX + #define ULLONG_MAX _UI64_MAX + #else + #define ULLONG_MAX 18446744073709551615ULL + #endif +#endif + +/* -------------------------------------------------------------------------- */ +#ifdef WIN32 /* WIN32 compatibility for common functions or keywords */ +/* -------------------------------------------------------------------------- */ + +#define DIR_SEP_CHR '\\' +#define DIR_SEP_STR "\\" + +/* on Win32, both \ and / are valid path separators */ +#define isdirsepchr(c) ( ((c) == '\\' || (c) == '/') ) + +#ifndef snprintf + #define snprintf _snprintf +#endif + +#ifndef win32error + #define win32error(s) \ + do { \ + DWORD __win32_error = GetLastError(); \ + LPSTR __win32_errmsg = NULL; \ + FormatMessageA( \ + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, \ + NULL, \ + __win32_error, \ + MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), \ + (LPSTR) & __win32_errmsg, \ + 256, \ + NULL \ + ); \ + fprintf(stderr, s ": %s\n", __win32_errmsg); \ + LocalFree(__win32_errmsg); \ + } while (0) +#endif + +#if (defined(_MSC_VER) && (_MSC_VER < 1300)) + /* C99 booleans */ + #define bool int + #define true 1 + #define false 0 + /* C99 int types */ + #define int8_t char + #define uint8_t unsigned char + #define int16_t short + #define uint16_t unsigned short + #define int32_t int + #define uint32_t unsigned int + #define int64_t __int64 + #define uint64_t unsigned __int64 + /* C99 limits */ + #define INT8_MIN SCHAR_MIN + #define INT8_MAX SCHAR_MAX + #define UINT8_MAX UCHAR_MAX + #define INT16_MIN SHRT_MIN + #define INT16_MAX SHRT_MAX + #define UINT16_MAX USHRT_MAX + #define INT32_MIN INT_MIN + #define INT32_MAX INT_MAX + #define UINT32_MAX UINT_MAX + #define INT64_MIN (-9223372036854775807i64 - 1) + #define INT64_MAX 9223372036854775807i64 + #define UINT64_MAX 18446744073709551615ui64 + + #ifdef _WIN64 + #define intptr_t __int64 + #define uintptr_t unsigned __int64 + #define INTPTR_MIN INT64_MIN + #define INTPTR_MAX INT64_MAX + #define UINTPTR_MAX UINT64_MAX + #else + #define intptr_t int + #define uintptr_t unsigned int + #define INTPTR_MIN INT_MIN + #define INTPTR_MAX INT_MAX + #define UINTPTR_MAX UINT_MAX + #endif + + #define intmax_t __int64 + #define uintmax_t unsigned __int64 + #define INTMAX_MIN INT64_MIN + #define INTMAX_MAX INT64_MAX + #define UINTMAX_MAX UINT64_MAX +#endif + +typedef uint32_t unaligned_uint32_t; + +#ifndef _strtoi64 + ASKL_API int64_t strtoll(const char *nptr, char **endptr, int base); + #define strtoull (uint64_t) strtoll +#else + #define strtoll _strtoi64 + #define strtoull (uint64_t) _strtoi64 +#endif + +#if (defined(_MSC_VER) && (_MSC_VER < 1800)) + /* va_copy is available from MSVC2013 onward */ + #define va_copy(a, b) do { a = (b); } while(0) +#endif + +#define INVALID_FILE_HANDLE ((HANDLE)INVALID_HANDLE_VALUE) + +/* -------------------------------------------------------------------------- */ +#else +/* -------------------------------------------------------------------------- */ + +#include + +#define DIR_SEP_CHR '/' +#define DIR_SEP_STR "/" + +#define isdirsepchr(c) ( ((c) == '/') ) + +#if (! defined(strtoll) && defined(strtoq)) + #define strtoll strtoq +#endif + +#if (! defined(strtoull) && defined(strtouq)) + #define strtoull strtouq +#endif + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ + +#ifdef _MAX_PATH + #define FILNAMSIZ (_MAX_PATH) +#else + #define FILNAMSIZ 1025 +#endif + +#ifndef PATH_MAX + #ifdef MAXPATHLEN + #define PATH_MAX MAXPATHLEN + #else + #if FILENAME_MAX > 1024 + #define PATH_MAX FILENAME_MAX + #else + #define PATH_MAX (FILNAMSIZ - 1) + #endif + #endif +#endif + +#if defined(_MSC_VER) || defined(_WIN32) || \ + ! (defined(__APPLE__) || defined(_DARWIN_C_SOURCE) || \ + defined(_GNU_SOURCE) || defined(_DEFAULT_SOURCE) || \ + defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || \ + (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L) || \ + (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700)) +ASKL_API char *strndup(const char *s, size_t len); +#endif + +#endif diff --git a/shims/askl_map/compat/askl_time_compat.c b/shims/askl_map/compat/askl_time_compat.c new file mode 100644 index 0000000..e9099ea --- /dev/null +++ b/shims/askl_map/compat/askl_time_compat.c @@ -0,0 +1,152 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2025 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#include "askl_time_compat.h" + +/* -------------------------------------------------------------------------- */ +#ifdef WIN32 +/* -------------------------------------------------------------------------- */ + +static LARGE_INTEGER freq; + +ASKL_API void monotonic_timer_init(void) +{ + QueryPerformanceFrequency(& freq); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int monotonic_timer(struct timespec *ts) +{ + LARGE_INTEGER t; + + if (! ts) return -1; + + QueryPerformanceCounter(& t); + + ts->tv_sec = t.QuadPart / freq.QuadPart; + ts->tv_nsec = (1000000000 * (t.QuadPart % freq.QuadPart)) / freq.QuadPart; + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +/* This code was released to public domain by Wu Yongwei. */ + +ASKL_API int gettimeofday(struct timeval *tv, struct timezone *tz) +{ + FILETIME ft; + LARGE_INTEGER li; + uint64_t t; + static int tzflag; + + if (tv) { + GetSystemTimeAsFileTime(& ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + t = li.QuadPart; /* In 100-nanosecond intervals */ + t -= EPOCHFILETIME; /* Offset to the Epoch time */ + t /= 10; /* In microseconds */ + tv->tv_sec = (long) (t / 1000000); + tv->tv_usec = (long) (t % 1000000); + } + + if (tz) { + if (! tzflag) { + _tzset(); tzflag ++; + } + tz->tz_minuteswest = _timezone / 60; + tz->tz_dsttime = _daylight; + } + + return 0; +} + +/* -------------------------------------------------------------------------- */ +#elif (defined(__APPLE__)) +/* -------------------------------------------------------------------------- */ + +static mach_timebase_info_data_t tb; + +ASKL_API void monotonic_timer_init(void) +{ + mach_timebase_info(& tb); +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int monotonic_timer(struct timespec *ts) +{ + uint64_t ns = 0; + + if (! ts) return -1; + + ns = (mach_absolute_time() * (uint64_t) tb.numer) / (uint64_t) tb.denom; + ts->tv_sec = ns / 1000000000; + ts->tv_nsec = ns - (ts->tv_sec * 1000000000); + + return 0; +} + +/* -------------------------------------------------------------------------- */ +#else +/* -------------------------------------------------------------------------- */ + +ASKL_API void monotonic_timer_init(void) +{ + ; +} + +/* -------------------------------------------------------------------------- */ + +ASKL_API int monotonic_timer(struct timespec *ts) +{ + if (! ts) { + debug("monotonic_timer(): bad parameters.\n"); + return -1; + } + + if (clock_gettime(CLOCK_MONOTONIC, ts) == -1) { + perror(ERR(monotonic_timer, clock_gettime)); + return -1; + } + + return 0; +} + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ diff --git a/shims/askl_map/compat/askl_time_compat.h b/shims/askl_map/compat/askl_time_compat.h new file mode 100644 index 0000000..e029889 --- /dev/null +++ b/shims/askl_map/compat/askl_time_compat.h @@ -0,0 +1,155 @@ +/******************************************************************************* + * ASKL. * + * Copyright (c) 2025 Raphael Prevost * + * * + * This software is a computer program whose purpose is to provide a * + * framework for developing and prototyping network services. * + * * + * This software is governed by the CeCILL license under French law and * + * abiding by the rules of distribution of free software. You can use, * + * modify and/ or redistribute the software under the terms of the CeCILL * + * license as circulated by CEA, CNRS and INRIA at the following URL * + * "http://www.cecill.info". * + * * + * As a counterpart to the access to the source code and rights to copy, * + * modify and redistribute granted by the license, users are provided only * + * with a limited warranty and the software's author, the holder of the * + * economic rights, and the successive licensors have only limited * + * liability. * + * * + * In this respect, the user's attention is drawn to the risks associated * + * with loading, using, modifying and/or developing or reproducing the * + * software by the user in light of its specific status of free software, * + * that may mean that it is complicated to manipulate, and that also * + * therefore means that it is reserved for developers and experienced * + * professionals having in-depth computer knowledge. Users are therefore * + * encouraged to load and test the software's suitability as regards their * + * requirements in conditions enabling the security of their systems and/or * + * data to be ensured and, more generally, to use and operate it in the * + * same conditions as regards security. * + * * + * The fact that you are presently reading this means that you have had * + * knowledge of the CeCILL license and that you accept its terms. * + * * + ******************************************************************************/ + +#ifndef ASKL_TIME_COMPAT_H + +#define ASKL_TIME_COMPAT_H + +/* -------------------------------------------------------------------------- */ +#ifdef WIN32 +/* -------------------------------------------------------------------------- */ + +/* This code was released to public domain by Wu Yongwei. */ +#define WIN32_LEAN_AND_MEAN +#include +#include + +#ifndef __GNUC__ +#define EPOCHFILETIME (116444736000000000i64) +#else +#define EPOCHFILETIME (116444736000000000LL) +#endif + +#ifdef _MSC_VER +#define _TIMEVAL_DEFINED +#endif + +#ifndef _TIMEVAL_DEFINED +#define _TIMEVAL_DEFINED +struct timeval { + long tv_sec; /* seconds */ + long tv_usec; /* microseconds */ +}; +#endif + +#ifndef _TIMEZONE_DEFINED +#define _TIMEZONE_DEFINED +struct timezone { + int tz_minuteswest; /* minutes W of Greenwich */ + int tz_dsttime; /* type of dst correction */ +}; +#endif + +#if (defined(_MSC_VER) || defined(__MINGW32__)) + #include + #ifndef sleep + #define sleep(t) _sleep((t) * 1000) + #endif +#else + #include + #ifndef sleep + #define sleep(t) Sleep((t) * 1000) + #endif +#endif + +#ifndef usleep + #define usleep(t) Sleep((t) / 1000) +#endif + +ASKL_API int gettimeofday(struct timeval *tv, struct timezone *tz); + +/** + * @fn int gettimeofday(struct timeval *tv, struct timezone *tz) + * @param tv a pointer to a @c timeval structure to receive the current time + * @param tz an optional pointer to a @c timezone structure, or @c NULL + * @return 0 on success, -1 on error + * + * This function provides a minimal implementation of the POSIX + * @c gettimeofday() call on Windows. + * + * The current system time is returned in @p tv as seconds and microseconds + * since the Unix epoch. If @p tz is not @c NULL, the @c tz_minuteswest and + * @c tz_dsttime fields are filled using the CRT globals @c _timezone and + * @c _daylight. + * + * On POSIX systems, the native @c gettimeofday() is used instead. + */ + +/* -------------------------------------------------------------------------- */ +#elif (defined(__APPLE__)) +/* -------------------------------------------------------------------------- */ + +#include +#include + +/* -------------------------------------------------------------------------- */ +#else +/* -------------------------------------------------------------------------- */ + +#include + +/* -------------------------------------------------------------------------- */ +#endif +/* -------------------------------------------------------------------------- */ + +ASKL_API void monotonic_timer_init(void); + +/** + * @fn void monotonic_timer_init(void) + * @param void + * @return void + * + * This function initializes the monotonic timer backend. + */ + +/* -------------------------------------------------------------------------- */ + +ASKL_API int monotonic_timer(struct timespec *ts); + +/** + * @fn int monotonic_timer(struct timespec *ts) + * @param ts a pointer to a @c timespec structure that will receive the time + * @return 0 on success, -1 if an error occurs or @p ts is @c NULL + * + * This function retrieves a monotonic time source that is not subject to + * adjustments of the system wall clock. + * + * The returned value is suitable for measuring time intervals, but not + * for representing calendar time. + */ + +/* -------------------------------------------------------------------------- */ + +#endif diff --git a/shims/askl_map/shim.h b/shims/askl_map/shim.h new file mode 100644 index 0000000..71619e2 --- /dev/null +++ b/shims/askl_map/shim.h @@ -0,0 +1,343 @@ +// c_cpp_hash_tables_benchmark/shims/askl_map/shim.h +#include +#include +#include +#include +#include +#include + +extern "C" { + +#ifndef _ENABLE_HASHMAP +#define _ENABLE_HASHMAP +#endif + +#include "compat/askl_compat_layer.h" +#include "compat/askl_compat_layer.c" +#include "askl_variant.h" +#include "askl_variant.c" +#include "askl_htable.h" +#include "askl_rwlock.c" +#include "askl_htable.c" + +} + +template< typename > struct askl_map +{ + static constexpr const char *label = "ASKL Map"; + static constexpr const char *color = "rgb( 170, 90, 190 )"; + static constexpr bool tombstone_like_mechanism = true; +}; + +/* -------------------------------------------------------------------------- */ +/* Blueprint-specific adapters */ +/* -------------------------------------------------------------------------- */ + +#define ASKL_MAP_BINARY_KEY_ADAPTER( blueprint ) \ + \ +static inline const char *askl_map_##blueprint##_key_data( \ + const blueprint::key_type &key \ +) \ +{ \ + return reinterpret_cast< const char * >( &key ); \ +} \ + \ +static inline std::size_t askl_map_##blueprint##_key_size( \ + const blueprint::key_type &key \ +) \ +{ \ + (void) key; \ + return sizeof( blueprint::key_type ); \ +} \ + \ +static inline blueprint::key_type askl_map_##blueprint##_key_from_askl( \ + const char *ptr, \ + std::size_t len \ +) \ +{ \ + blueprint::key_type key; \ + (void) len; \ + std::memcpy( &key, ptr, sizeof( key ) ); \ + return key; \ +} + +#define ASKL_MAP_INTEGRAL_VALUE_ADAPTER( blueprint ) \ + \ +static inline void askl_map_##blueprint##_value_free_askl( Variant value ) \ +{ \ + (void) value; \ +} \ + \ +static inline Variant askl_map_##blueprint##_value_to_askl( \ + const blueprint::value_type &value \ +) \ +{ \ + return variant_from_integer( static_cast< uint64_t >( value ) ); \ +} \ + \ +static inline const blueprint::value_type *askl_map_##blueprint##_value_ref_from_askl( \ + Variant value, \ + blueprint::value_type &scratch \ +) \ +{ \ + scratch = static_cast< blueprint::value_type >( variant_to_integer( value ) ); \ + return &scratch; \ +} + +/* + * ASKL Variant cannot hold uint64_struct448_murmur::value_type by value. + * Store it out-of-line and put the pointer in the integer Variant payload. + * + * This is a benchmark adapter, not a recommended public ASKL value model. + */ +#define ASKL_MAP_HEAP_VALUE_ADAPTER( blueprint ) \ + \ +static inline void askl_map_##blueprint##_value_free_askl( Variant value ) \ +{ \ + if ( is_integer( value ) ) { \ + uintptr_t ptr = static_cast< uintptr_t >( variant_to_integer( value ) ); \ + delete reinterpret_cast< blueprint::value_type * >( ptr ); \ + } \ +} \ + \ +static inline Variant askl_map_##blueprint##_value_to_askl( \ + const blueprint::value_type &value \ +) \ +{ \ + blueprint::value_type *copy = new ( std::nothrow ) blueprint::value_type( value ); \ + return variant_from_integer( static_cast< uint64_t >( \ + reinterpret_cast< uintptr_t >( copy ) \ + ) ); \ +} \ + \ +static inline const blueprint::value_type *askl_map_##blueprint##_value_ref_from_askl( \ + Variant value, \ + blueprint::value_type &scratch \ +) \ +{ \ + if ( is_integer( value ) ) { \ + uintptr_t ptr = static_cast< uintptr_t >( variant_to_integer( value ) ); \ + if ( ptr ) \ + return reinterpret_cast< const blueprint::value_type * >( ptr ); \ + } \ + scratch = blueprint::value_type(); \ + return &scratch; \ +} + +/* uint32 -> uint32 --------------------------------------------------------- */ + +#ifdef UINT32_UINT32_MURMUR_ENABLED + +ASKL_MAP_BINARY_KEY_ADAPTER( uint32_uint32_murmur ) +ASKL_MAP_INTEGRAL_VALUE_ADAPTER( uint32_uint32_murmur ) + +#endif + +/* uint64 -> struct448 ------------------------------------------------------ */ + +#ifdef UINT64_STRUCT448_MURMUR_ENABLED + +ASKL_MAP_BINARY_KEY_ADAPTER( uint64_struct448_murmur ) +ASKL_MAP_HEAP_VALUE_ADAPTER( uint64_struct448_murmur ) + +#endif + +/* cstring -> uint64 -------------------------------------------------------- */ + +#ifdef CSTRING_UINT64_FNV1A_ENABLED + +static inline const char *askl_map_cstring_uint64_fnv1a_key_data( + const cstring_uint64_fnv1a::key_type &key +) +{ + return key; +} + +static inline std::size_t askl_map_cstring_uint64_fnv1a_key_size( + const cstring_uint64_fnv1a::key_type &key +) +{ + (void) key; + return std::strlen( key ); +} + +static inline cstring_uint64_fnv1a::key_type askl_map_cstring_uint64_fnv1a_key_from_askl( + const char *ptr, + std::size_t len +) +{ + (void) len; + return const_cast< char * >( ptr ); +} + +ASKL_MAP_INTEGRAL_VALUE_ADAPTER( cstring_uint64_fnv1a ) + +#endif + +/* -------------------------------------------------------------------------- */ +/* Specialization macro */ +/* -------------------------------------------------------------------------- */ + +#define ASKL_MAP_SPECIALIZATION( blueprint ) \ + \ +typedef struct \ +{ \ + Map *table; \ +} askl_map_##blueprint; \ + \ +typedef struct \ +{ \ + _Bucket *bucket; \ + bool from_find; \ + blueprint::key_type key; \ + blueprint::value_type value; \ +} askl_map_##blueprint##_itr; \ + \ +static inline _Bucket *askl_map_##blueprint##_next_live( _Bucket *bucket ) \ +{ \ + while ( bucket && !bucket->item.key.len ) \ + bucket = bucket->next; \ + return bucket; \ +} \ + \ +static inline void askl_map_##blueprint##_free_live_values( Map *table ) \ +{ \ + _Bucket *bucket = nullptr; \ + if ( !table ) \ + return; \ + for ( bucket = table->_index; bucket; bucket = bucket->next ) \ + if ( bucket->item.key.len ) \ + askl_map_##blueprint##_value_free_askl( bucket->item.val ); \ +} \ + \ +template<> struct askl_map< blueprint > \ +{ \ + using table_type = askl_map_##blueprint; \ + using itr_type = askl_map_##blueprint##_itr; \ + \ + static constexpr const char *label = "ASKL Map"; \ + static constexpr const char *color = "rgb( 170, 90, 190 )"; \ + static constexpr bool tombstone_like_mechanism = true; \ + \ + static table_type create_table() \ + { \ + table_type table; \ + table.table = map_alloc( nullptr ); \ + return table; \ + } \ + \ + static itr_type find( table_type &table, const blueprint::key_type &key ) \ + { \ + itr_type itr; \ + itr.bucket = nullptr; \ + itr.from_find = false; \ + itr.key = key; \ + itr.value = blueprint::value_type(); \ + \ + Variant value = {{ 0 }}; \ + _Item *item = _get_item( \ + table.table, \ + askl_map_##blueprint##_key_data( key ), \ + askl_map_##blueprint##_key_size( key ), \ + &value \ + ); \ + \ + if ( item ) { \ + itr.bucket = (_Bucket *)((char *)item - offsetof( _Bucket, item )); \ + itr.from_find = true; \ + itr.key = key; \ + itr.value = *askl_map_##blueprint##_value_ref_from_askl( value, itr.value ); \ + } \ + \ + return itr; \ + } \ + \ + static void insert( table_type &table, const blueprint::key_type &key ) \ + { \ + blueprint::value_type value = blueprint::value_type(); \ + Variant rejected = map_set( \ + table.table, \ + askl_map_##blueprint##_key_data( key ), \ + askl_map_##blueprint##_key_size( key ), \ + askl_map_##blueprint##_value_to_askl( value ) \ + ); \ + askl_map_##blueprint##_value_free_askl( rejected ); \ + } \ + \ + static void erase( table_type &table, const blueprint::key_type &key ) \ + { \ + Variant removed = map_remove( \ + table.table, \ + askl_map_##blueprint##_key_data( key ), \ + askl_map_##blueprint##_key_size( key ) \ + ); \ + askl_map_##blueprint##_value_free_askl( removed ); \ + } \ + \ + static itr_type begin_itr( table_type &table ) \ + { \ + itr_type itr; \ + itr.bucket = askl_map_##blueprint##_next_live( table.table->_index ); \ + itr.from_find = false; \ + itr.key = blueprint::key_type(); \ + itr.value = blueprint::value_type(); \ + return itr; \ + } \ + \ + static bool is_itr_valid( table_type &table, itr_type &itr ) \ + { \ + (void) table; \ + return itr.bucket != nullptr; \ + } \ + \ + static void increment_itr( table_type &table, itr_type &itr ) \ + { \ + (void) table; \ + \ + itr.from_find = false; \ + if ( itr.bucket ) \ + itr.bucket = askl_map_##blueprint##_next_live( itr.bucket->next ); \ + } \ + \ + static const blueprint::key_type &get_key_from_itr( table_type &table, itr_type &itr ) \ + { \ + (void) table; \ + \ + if ( itr.from_find ) \ + return itr.key; \ + \ + itr.key = askl_map_##blueprint##_key_from_askl( \ + itr.bucket->item.key.str, \ + itr.bucket->item.key.len \ + ); \ + return itr.key; \ + } \ + \ + static const blueprint::value_type &get_value_from_itr( table_type &table, itr_type &itr ) \ + { \ + (void) table; \ + return *askl_map_##blueprint##_value_ref_from_askl( \ + itr.bucket->item.val, \ + itr.value \ + ); \ + } \ + \ + static void destroy_table( table_type &table ) \ + { \ + askl_map_##blueprint##_free_live_values( table.table ); \ + map_free( table.table ); \ + table.table = nullptr; \ + } \ +}; + +#ifdef UINT32_UINT32_MURMUR_ENABLED +ASKL_MAP_SPECIALIZATION( uint32_uint32_murmur ) +#endif + +#ifdef UINT64_STRUCT448_MURMUR_ENABLED +ASKL_MAP_SPECIALIZATION( uint64_struct448_murmur ) +#endif + +#ifdef CSTRING_UINT64_FNV1A_ENABLED +ASKL_MAP_SPECIALIZATION( cstring_uint64_fnv1a ) +#endif