From cdf4430a9e933277a8ca2f7c76a4050b7e8d7dd5 Mon Sep 17 00:00:00 2001 From: lda Date: Sun, 26 May 2024 22:01:06 +0200 Subject: [PATCH 01/18] [FIX] Fix issue with const strings which may be invalidated --- src/Int64.c | 399 +++++++++++++++++++++++++++++++++++++ src/Memory.c | 14 +- src/UInt64.c | 265 ++++++++++++++++++++++++ src/include/Args.h | 82 ++++++++ src/include/Array.h | 187 +++++++++++++++++ src/include/Base64.h | 99 +++++++++ src/include/Cron.h | 140 +++++++++++++ src/include/Db.h | 169 ++++++++++++++++ src/include/Graph.h | 175 ++++++++++++++++ src/include/HashMap.h | 185 +++++++++++++++++ src/include/HeaderParser.h | 125 ++++++++++++ src/include/Http.h | 211 ++++++++++++++++++++ src/include/HttpClient.h | 104 ++++++++++ src/include/HttpRouter.h | 91 +++++++++ src/include/HttpServer.h | 234 ++++++++++++++++++++++ src/include/Int.h | 122 ++++++++++++ src/include/Int64.h | 252 +++++++++++++++++++++++ src/include/Io.h | 222 +++++++++++++++++++++ src/include/Json.h | 323 ++++++++++++++++++++++++++++++ src/include/Log.h | 196 ++++++++++++++++++ src/include/Memory.h | 235 ++++++++++++++++++++++ src/include/Queue.h | 105 ++++++++++ src/include/Rand.h | 81 ++++++++ src/include/Runtime.h | 53 +++++ src/include/Sha.h | 76 +++++++ src/include/Str.h | 129 ++++++++++++ src/include/Stream.h | 223 +++++++++++++++++++++ src/include/Tls.h | 109 ++++++++++ src/include/UInt64.h | 252 +++++++++++++++++++++++ src/include/Uri.h | 69 +++++++ src/include/Util.h | 117 +++++++++++ tools/int64.c | 145 ++++++++++++++ tools/uint64.c | 119 +++++++++++ 33 files changed, 5302 insertions(+), 6 deletions(-) create mode 100644 src/Int64.c create mode 100644 src/UInt64.c create mode 100644 src/include/Args.h create mode 100644 src/include/Array.h create mode 100644 src/include/Base64.h create mode 100644 src/include/Cron.h create mode 100644 src/include/Db.h create mode 100644 src/include/Graph.h create mode 100644 src/include/HashMap.h create mode 100644 src/include/HeaderParser.h create mode 100644 src/include/Http.h create mode 100644 src/include/HttpClient.h create mode 100644 src/include/HttpRouter.h create mode 100644 src/include/HttpServer.h create mode 100644 src/include/Int.h create mode 100644 src/include/Int64.h create mode 100644 src/include/Io.h create mode 100644 src/include/Json.h create mode 100644 src/include/Log.h create mode 100644 src/include/Memory.h create mode 100644 src/include/Queue.h create mode 100644 src/include/Rand.h create mode 100644 src/include/Runtime.h create mode 100644 src/include/Sha.h create mode 100644 src/include/Str.h create mode 100644 src/include/Stream.h create mode 100644 src/include/Tls.h create mode 100644 src/include/UInt64.h create mode 100644 src/include/Uri.h create mode 100644 src/include/Util.h create mode 100644 tools/int64.c create mode 100644 tools/uint64.c diff --git a/src/Int64.c b/src/Int64.c new file mode 100644 index 0000000..21f07f5 --- /dev/null +++ b/src/Int64.c @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 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 + +#ifdef INT64_NATIVE +#define Int64Sign(x) ((int) (((UInt64) (x)) >> 63)) +#else +#define Int64Sign(x) ((int) ((x).i[1] >> 31)) +#endif + +size_t +Int64Str(Int64 x, int base, char *out, size_t len) +{ + static const char symbols[] = "0123456789ABCDEF"; + + size_t i = len - 1; + size_t j = 0; + + int neg = Int64Sign(x); + + Int64 base64 = Int64Create(0, base); + + /* We only have symbols up to base 16 */ + if (base < 2 || base > 16) + { + return 0; + } + + /* + * This algorithm doesn't work on INT64_MIN. + * + * But it works on all other integers in the range, so we + * just scoot the range in by one for now. It's a hack and + * I'm not a huge fan of it, but this function is mostly + * used in Json, which shouldn't have a range this large + * anyway (Json is limited to -2^53 -> 2^53-1). + * + * Proper fixes are always welcome. + */ + if (Int64Eq(x, Int64Create(0x80000000, 0x00000000))) + { + x = Int64Add(x, Int64Create(0, 1)); + } +#if 0 + else if (Int64Eq(x, Int64Create(0x7FFFFFFF, 0xFFFFFFFF))) + { + x = Int64Sub(x, Int64Create(0, 1)); + } +#endif + + if (base != 2 && base != 8 && base != 16 && neg) + { + x = Int64Neg(x); + } + + do + { + Int64 mod = Int64Rem(x, base64); + Int32 low = Int64Low(mod); + + out[i] = symbols[low]; + i--; + x = Int64Div(x, base64); + } while (Int64Gt(x, Int64Create(0, 0))); + + if (base != 2 && base != 8 && base != 16) + { + /* + * Binary, octal, and hexadecimal are known to + * be bit representations. Everything else (notably + * decimal) should include the negative sign. + */ + if (neg) + { + out[i] = '-'; + i--; + } + } + + while (++i < len) + { + out[j++] = out[i]; + } + + out[j] = '\0'; + + return j; +} + +#ifndef INT64_NATIVE + +/* No native 64-bit support, add our own */ + +Int64 +Int64Create(UInt32 high, UInt32 low) +{ + Int64 x; + + x.i[0] = low; + x.i[1] = high; + + return x; +} + +Int64 +Int64Add(Int64 x, Int64 y) +{ + Int64 z = Int64Create(0, 0); + int carry; + + z.i[0] = x.i[0] + y.i[0]; + carry = z.i[0] < x.i[0]; + z.i[1] = x.i[1] + y.i[1] + carry; + + return z; +} + +Int64 +Int64Sub(Int64 x, Int64 y) +{ + return Int64Add(x, Int64Neg(y)); +} + +Int64 +Int64Mul(Int64 x, Int64 y) +{ + Int64 z = Int64Create(0, 0); + + int xneg = Int64Sign(x); + int yneg = Int64Sign(y); + + if (xneg) + { + x = Int64Neg(x); + } + + if (yneg) + { + y = Int64Neg(y); + } + + /* while (y > 0) */ + while (Int64Gt(y, Int64Create(0, 0))) + { + /* if (y & 1 != 0) */ + if (Int64Neq(Int64And(y, Int64Create(0, 1)), Int64Create(0, 0))) + { + z = Int64Add(z, x); + } + + x = Int64Sll(x, 1); + y = Int64Sra(y, 1); + } + + if (xneg != yneg) + { + z = Int64Neg(z); + } + + return z; +} + +typedef struct +{ + Int64 q; + Int64 r; +} Int64Ldiv; + +static Int64Ldiv +Int64LongDivision(Int64 n, Int64 d) +{ + Int64Ldiv o; + + int i; + + int nneg = Int64Sign(n); + int dneg = Int64Sign(d); + + o.q = Int64Create(0, 0); + o.r = Int64Create(0, 0); + + if (Int64Eq(d, Int64Create(0, 0))) + { + raise(SIGFPE); + return o; + } + + if (nneg) + { + n = Int64Neg(n); + } + + if (dneg) + { + d = Int64Neg(d); + } + + for (i = 63; i >= 0; i--) + { + Int64 bit = Int64And(Int64Sra(n, i), Int64Create(0, 1)); + + o.r = Int64Sll(o.r, 1); + o.r = Int64Or(o.r, bit); + + if (Int64Geq(o.r, d)) + { + o.r = Int64Sub(o.r, d); + o.q = Int64Or(o.q, Int64Sll(Int64Create(0, 1), i)); + } + } + + if (nneg != dneg) + { + o.r = Int64Neg(o.r); + o.q = Int64Neg(o.q); + } + + return o; +} + +Int64 +Int64Div(Int64 x, Int64 y) +{ + return Int64LongDivision(x, y).q; +} + +Int64 +Int64Rem(Int64 x, Int64 y) +{ + return Int64LongDivision(x, y).r; +} + +Int64 +Int64Sll(Int64 x, int y) +{ + Int64 z; + + if (!y) + { + return x; + } + + z = Int64Create(0, 0); + + if (y < 32) + { + z.i[1] = (x.i[0] >> (32 - y)) | (x.i[1] << y); + z.i[0] = x.i[0] << y; + } + else + { + z.i[1] = x.i[0] << (y - 32); + } + + return z; +} + +Int64 +Int64Sra(Int64 x, int y) +{ + Int64 z; + + int neg = Int64Sign(x); + + if (!y) + { + return x; + } + + z = Int64Create(0, 0); + + if (y < 32) + { + z.i[0] = (x.i[1] << (32 - y)) | (x.i[0] >> y); + z.i[1] = x.i[1] >> y; + } + else + { + z.i[0] = x.i[1] >> (y - 32); + } + + if (neg) + { + Int64 mask = Int64Create(0xFFFFFFFF, 0xFFFFFFFF); + + z = Int64Or(Int64Sll(mask, (64 - y)), z); + } + + return z; +} + +Int64 +Int64And(Int64 x, Int64 y) +{ + return Int64Create(x.i[1] & y.i[1], x.i[0] & y.i[0]); +} + +Int64 +Int64Or(Int64 x, Int64 y) +{ + return Int64Create(x.i[1] | y.i[1], x.i[0] | y.i[0]); +} + +Int64 +Int64Xor(Int64 x, Int64 y) +{ + return Int64Create(x.i[1] ^ y.i[1], x.i[0] ^ y.i[0]); +} + +Int64 +Int64Not(Int64 x) +{ + return Int64Create(~(x.i[1]), ~(x.i[0])); +} + +int +Int64Eq(Int64 x, Int64 y) +{ + return x.i[0] == y.i[0] && x.i[1] == y.i[1]; +} + +int +Int64Lt(Int64 x, Int64 y) +{ + int xneg = Int64Sign(x); + int yneg = Int64Sign(y); + + if (xneg != yneg) + { + return xneg > yneg; + } + else + { + if (xneg) + { + /* Both negative */ + return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]); + } + else + { + /* Both positive */ + return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]); + } + } +} + +int +Int64Gt(Int64 x, Int64 y) +{ + int xneg = Int64Sign(x); + int yneg = Int64Sign(y); + + if (xneg != yneg) + { + return xneg < yneg; + } + else + { + if (xneg) + { + /* Both negative */ + return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]); + } + else + { + /* Both positive */ + return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]); + } + } + +} + +#endif diff --git a/src/Memory.c b/src/Memory.c index 4083143..01c98c5 100644 --- a/src/Memory.c +++ b/src/Memory.c @@ -40,10 +40,12 @@ #define MEMORY_HEXDUMP_WIDTH 16 #endif +#define MEMORY_FILE_SIZE 256 + struct MemoryInfo { size_t size; - const char *file; + char file[MEMORY_FILE_SIZE]; int line; void *pointer; }; @@ -227,7 +229,7 @@ MemoryAllocate(size_t size, const char *file, int line) MEM_BOUND_UPPER(p, size) = MEM_BOUND; a->size = MEM_SIZE_ACTUAL(size); - a->file = file; + strncpy(a->file, file, MEMORY_FILE_SIZE - 1); a->line = line; a->pointer = p; @@ -270,7 +272,7 @@ MemoryReallocate(void *p, size_t size, const char *file, int line) { MemoryDelete(a); a->size = MEM_SIZE_ACTUAL(size); - a->file = file; + strncpy(a->file, file, MEMORY_FILE_SIZE - 1); a->line = line; a->pointer = new; @@ -293,7 +295,7 @@ MemoryReallocate(void *p, size_t size, const char *file, int line) if (a) { a->size = 0; - a->file = file; + strncpy(a->file, file, MEMORY_FILE_SIZE - 1); a->line = line; a->pointer = p; hook(MEMORY_BAD_POINTER, a, hookArgs); @@ -322,7 +324,7 @@ MemoryFree(void *p, const char *file, int line) pthread_mutex_lock(&lock); if (hook) { - a->file = file; + strncpy(a->file, file, MEMORY_FILE_SIZE - 1); a->line = line; hook(MEMORY_FREE, a, hookArgs); } @@ -337,7 +339,7 @@ MemoryFree(void *p, const char *file, int line) a = malloc(sizeof(MemoryInfo)); if (a) { - a->file = file; + strncpy(a->file, file, MEMORY_FILE_SIZE - 1); a->line = line; a->size = 0; a->pointer = p; diff --git a/src/UInt64.c b/src/UInt64.c new file mode 100644 index 0000000..ae8eff7 --- /dev/null +++ b/src/UInt64.c @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 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 + +size_t +UInt64Str(UInt64 x, int base, char *out, size_t len) +{ + static const char symbols[] = "0123456789ABCDEF"; + + size_t i = len - 1; + size_t j = 0; + + UInt64 base64 = UInt64Create(0, base); + + /* We only have symbols up to base 16 */ + if (base < 2 || base > 16) + { + return 0; + } + + do + { + UInt64 mod = UInt64Rem(x, base64); + UInt32 low = UInt64Low(mod); + + out[i] = symbols[low]; + i--; + x = UInt64Div(x, base64); + } while (UInt64Gt(x, UInt64Create(0, 0))); + + while (++i < len) + { + out[j++] = out[i]; + } + + out[j] = '\0'; + + return j; +} + +#ifndef UINT64_NATIVE + +/* No native 64-bit support, add our own */ + +UInt64 +UInt64Create(UInt32 high, UInt32 low) +{ + UInt64 x; + + x.i[0] = low; + x.i[1] = high; + + return x; +} + +UInt64 +UInt64Add(UInt64 x, UInt64 y) +{ + UInt64 z = UInt64Create(0, 0); + int carry; + + z.i[0] = x.i[0] + y.i[0]; + carry = z.i[0] < x.i[0]; + z.i[1] = x.i[1] + y.i[1] + carry; + + return z; +} + +UInt64 +UInt64Sub(UInt64 x, UInt64 y) +{ + UInt64 twosCompl = UInt64Add(UInt64Not(y), UInt64Create(0, 1)); + + return UInt64Add(x, twosCompl); +} + +UInt64 +UInt64Mul(UInt64 x, UInt64 y) +{ + UInt64 z = UInt64Create(0, 0); + + /* while (y > 0) */ + while (UInt64Gt(y, UInt64Create(0, 0))) + { + /* if (y & 1 != 0) */ + if (UInt64Neq(UInt64And(y, UInt64Create(0, 1)), UInt64Create(0, 0))) + { + z = UInt64Add(z, x); + } + + x = UInt64Sll(x, 1); + y = UInt64Srl(y, 1); + } + + return z; +} + +typedef struct +{ + UInt64 q; + UInt64 r; +} UInt64Ldiv; + +static UInt64Ldiv +UInt64LongDivision(UInt64 n, UInt64 d) +{ + UInt64Ldiv o; + + int i; + + o.q = UInt64Create(0, 0); + o.r = UInt64Create(0, 0); + + if (UInt64Eq(d, UInt64Create(0, 0))) + { + raise(SIGFPE); + return o; + } + + for (i = 63; i >= 0; i--) + { + UInt64 bit = UInt64And(UInt64Srl(n, i), UInt64Create(0, 1)); + + o.r = UInt64Sll(o.r, 1); + o.r = UInt64Or(o.r, bit); + + if (UInt64Geq(o.r, d)) + { + o.r = UInt64Sub(o.r, d); + o.q = UInt64Or(o.q, UInt64Sll(UInt64Create(0, 1), i)); + } + } + + return o; +} + +UInt64 +UInt64Div(UInt64 x, UInt64 y) +{ + return UInt64LongDivision(x, y).q; +} + +UInt64 +UInt64Rem(UInt64 x, UInt64 y) +{ + return UInt64LongDivision(x, y).r; +} + +UInt64 +UInt64Sll(UInt64 x, int y) +{ + UInt64 z; + + if (!y) + { + return x; + } + + z = UInt64Create(0, 0); + + if (y < 32) + { + z.i[1] = (x.i[0] >> (32 - y)) | (x.i[1] << y); + z.i[0] = x.i[0] << y; + } + else + { + z.i[1] = x.i[0] << (y - 32); + } + + return z; +} + +UInt64 +UInt64Srl(UInt64 x, int y) +{ + UInt64 z; + + if (!y) + { + return x; + } + + z = UInt64Create(0, 0); + + if (y < 32) + { + z.i[0] = (x.i[1] << (32 - y)) | (x.i[0] >> y); + z.i[1] = x.i[1] >> y; + } + else + { + z.i[0] = x.i[1] >> (y - 32); + } + + return z; +} + +UInt64 +UInt64And(UInt64 x, UInt64 y) +{ + return UInt64Create(x.i[1] & y.i[1], x.i[0] & y.i[0]); +} + +UInt64 +UInt64Or(UInt64 x, UInt64 y) +{ + return UInt64Create(x.i[1] | y.i[1], x.i[0] | y.i[0]); +} + +UInt64 +UInt64Xor(UInt64 x, UInt64 y) +{ + return UInt64Create(x.i[1] ^ y.i[1], x.i[0] ^ y.i[0]); +} + +UInt64 +UInt64Not(UInt64 x) +{ + return UInt64Create(~(x.i[1]), ~(x.i[0])); +} + +int +UInt64Eq(UInt64 x, UInt64 y) +{ + return x.i[0] == y.i[0] && x.i[1] == y.i[1]; +} + +int +UInt64Lt(UInt64 x, UInt64 y) +{ + return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]); +} + +int +UInt64Gt(UInt64 x, UInt64 y) +{ + return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]); +} + +#endif diff --git a/src/include/Args.h b/src/include/Args.h new file mode 100644 index 0000000..3108de3 --- /dev/null +++ b/src/include/Args.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_ARGS_H +#define CYTOPLASM_ARGS_H + +/*** + * @Nm Args + * @Nd Getopt-style argument parser that operates on arrays. + * @Dd May 12 2023 + * @Xr Array + * + * .Nm + * provides a simple argument parser in the style of + * .Xr getopt 3 . + * It exists because the runtime passes the program arguments as + * an Array, and it is often useful to parse the arguments to + * provide the standard command line interface. + */ + +#include + +/** + * All state is stored in this structure, instead of global + * variables. This makes + * .Nm + * thread-safe and easy to reset. + */ +typedef struct ArgParseState +{ + int optInd; + int optErr; + int optOpt; + char *optArg; + + int optPos; +} ArgParseState; + +/** + * Initialize the variables in the given parser state structure + * to their default values. This should be called before + * .Fn ArgParse + * is called with the parser state. It should also be called if + * .Fn ArgParse + * will be used again on a different array, or the same array all + * over again. + */ +extern void ArgParseStateInit(ArgParseState *); + +/** + * Parse command line arguments stored in the given array, using + * the given state and option string. This function behaves + * identically to the POSIX + * .Fn getopt + * function, and should be used in exactly the same way. Refer to + * your system's + * .Xr getopt 3 + * page for details. + */ +extern int ArgParse(ArgParseState *, Array *, const char *); + +#endif /* CYTOPLASM_ARGS_H */ diff --git a/src/include/Array.h b/src/include/Array.h new file mode 100644 index 0000000..0ba1335 --- /dev/null +++ b/src/include/Array.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_ARRAY_H +#define CYTOPLASM_ARRAY_H + +/*** + * @Nm Array + * @Nd A simple dynamic array data structure. + * @Dd November 24 2022 + * @Xr HashMap Queue + * + * These functions implement a simple array data structure that is + * automatically resized as necessary when new values are added. This + * implementation does not actually store the values of the items in it; + * it only stores pointers to the data. As such, you will still have to + * manually maintain all your data. The advantage of this is that these + * functions don't have to copy data, and thus don't care how big the + * data is. Furthermore, arbitrary data can be stored in the array. + * .Pp + * This array implementation is optimized for storage space and + * appending. Deletions are expensive in that all the items of the list + * above a deletion are moved down to fill the hole where the deletion + * occurred. Insertions are also expensive in that all the elements + * above the given index must be shifted up to make room for the new + * element. + * .Pp + * Due to these design choices, this array implementation is best + * suited to linear writing, and then linear or random reading. + */ + +#include +#include + +/** + * The functions in this API operate on an array structure which is + * opaque to the caller. + */ +typedef struct Array Array; + +/** + * Allocate a new array. This function returns a pointer that can be + * used with the other functions in this API, or NULL if there was an + * error allocating memory for the array. + */ +extern Array *ArrayCreate(void); + +/** + * Deallocate an array. Note that this function does not free any of + * the values stored in the array; it is the caller's job to manage the + * memory for each item. Typically, the caller would iterate over all + * the items in the array and free them before freeing the array. + */ +extern void ArrayFree(Array *); + +/** + * Get the size, in number of elements, of the given array. + */ +extern size_t ArraySize(Array *); + +/** + * Get the element at the specified index from the specified array. + * This function will return NULL if the array is NULL, or the index + * is out of bounds. Otherwise, it will return a pointer to a value + * put into the array using + * .Fn ArrayInsert + * or + * .Fn ArraySet . + */ +extern void *ArrayGet(Array *, size_t); + +/** + * Insert the specified element at the specified index in the specified + * array. This function will shift the element currently at that index, + * and any elements after it before inserting the given element. + * .Pp + * This function returns a boolean value indicating whether or not it + * suceeded. + */ +extern int ArrayInsert(Array *, size_t, void *); + +/** + * Set the value at the specified index in the specified array to the + * specified value. This function will return the old value at that + * index, if any. + */ +extern void *ArraySet(Array *, size_t, void *); + +/** + * Append the specified element to the end of the specified array. This + * function uses + * .Fn ArrayInsert + * under the hood to insert an element at the end. It thus has the same + * return value as + * .Fn ArrayInsert . + */ +extern int ArrayAdd(Array *, void *); + +/** + * Remove the element at the specified index from the specified array. + * This function returns the element removed, if any. + */ +extern void *ArrayDelete(Array *, size_t); + +/** + * Sort the specified array using the specified sort function. The + * sort function compares two elements. It takes two void pointers as + * parameters, and returns an integer. The return value indicates to + * the sorting algorithm how the elements relate to each other. A + * return value of 0 indicates that the elements are identical. A + * return value greater than 0 indicates that the first item is + * ``bigger'' than the second item and should thus appear after it in + * the array. A return value less than 0 indicates the opposite: the + * second element should appear after the first in the array. + */ +extern void ArraySort(Array *, int (*) (void *, void *)); + +/** + * Remove all duplicates from an array by using the given comparison + * function to sort the array, then remove matching values. This + * function returns a new array with all duplicates removed. The + * original array is unaffected. Note that arrays only store pointers + * to values, usually values on the heap. Thus, it is possible to lose + * pointers to duplicate values and have them leak. + * .P + * This is a relatively expensive operation. The array must first be + * duplicated. Then it is sorted, then it is iterated from beginning + * to end to remove duplicate entires. Note that the comparison + * function is executed on each element at least twice. + */ +extern Array *ArrayUnique(Array *, int (*) (void *, void *)); + + +/** + * Reverses the order of all elements in the array into a new array on + * the heap, to be freed using + * .Fn ArrayFree . + */ +extern Array *ArrayReverse(Array *); + +/** + * If possible, reduce the amount of memory allocated to this array + * by calling + * .Fn Realloc + * on the internal structure to perfectly fit the elements in the + * array. This function is intended to be used by functions that return + * relatively read-only arrays that will be long-lived. + */ +extern int ArrayTrim(Array *); + +/** + * Convert a variadic arguments list into an Array. In most cases, the + * Array API is much easier to work with than + * .Fn va_arg + * and friends. + */ +extern Array *ArrayFromVarArgs(size_t, va_list); + +/** + * Duplicate an existing array. Note that arrays only hold pointers to + * their data, not the data itself, so the duplicated array will point + * to the same places in memory as the original array. + */ +extern Array *ArrayDuplicate(Array *); + +#endif /* CYTOPLASM_ARRAY_H */ diff --git a/src/include/Base64.h b/src/include/Base64.h new file mode 100644 index 0000000..b36a48c --- /dev/null +++ b/src/include/Base64.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_BASE64_H +#define CYTOPLASM_BASE64_H + +/*** + * @Nm Base64 + * @Nd A simple base64 encoder/decoder with unpadded base64 support. + * @Dd September 30 2022 + * @Xr Sha2 + * + * This is an efficient yet simple base64 encoding and decoding API + * that supports regular base64, as well as the Matrix specification's + * extension to base64, called ``unpadded base64.'' This API provides + * the ability to convert between the two, instead of just implementing + * unpadded base64. + */ + +#include + +/** + * This function computes the amount of bytes needed to store a message + * of the specified number of bytes as base64. + */ +extern size_t + Base64EncodedSize(size_t); + +/** + * This function computes the amount of bytes needed to store a decoded + * representation of the encoded message. It takes a pointer to the + * encoded string because it must read a few bytes off the end in order + * to accurately compute the size. + */ +extern size_t + Base64DecodedSize(const char *, size_t); + +/** + * Encode the specified number of bytes from the specified buffer as + * base64. This function returns a string on the heap that should be + * freed with + * .Fn Free , + * or NULL if a memory allocation error ocurred. + */ +extern char * + Base64Encode(const char *, size_t); + +/** + * Decode the specified number of bytes from the specified buffer of + * base64. This function returns a string on the heap that should be + * freed with + * .Fn Free , + * or NULL if a memory allocation error occured. + */ +extern char * + Base64Decode(const char *, size_t); + +/** + * Remove the padding from a specified base64 string. This function + * modifies the specified string in place. It thus has no return value + * because it cannot fail. If the passed pointer is invalid, the + * behavior is undefined. + */ +extern void + Base64Unpad(char *, size_t); + +/** + * Add padding to an unpadded base64 string. This function takes a + * pointer to a pointer because it may be necessary to grow the memory + * allocated to the string. This function returns a boolean value + * indicating whether the pad operation was successful. In practice, + * this means it will only fail if a bigger string is necessary, but it + * could not be automatically allocated on the heap. + */ +extern int + Base64Pad(char **, size_t); + +#endif /* CYTOPLASM_BASE64_H */ diff --git a/src/include/Cron.h b/src/include/Cron.h new file mode 100644 index 0000000..e78900d --- /dev/null +++ b/src/include/Cron.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_CRON_H +#define CYTOPLASM_CRON_H + +/*** + * @Nm Cron + * @Nd Basic periodic job scheduler. + * @Dd December 24 2022 + * + * This is an extremely basic job scheduler. So basic, in fact, that + * it currently runs all jobs on a single thread, which means that it + * is intended for short-lived jobs. In the future, it might be + * extended to support a one-thread-per-job model, but for now, jobs + * should take into consideration the fact that they are sharing their + * thread, so they should not be long-running. + * .Pp + * .Nm + * works by ``ticking'' at an interval defined by the caller of + * .Fn CronCreate . + * At each tick, all the registered jobs are queried, and if they are + * due to run again, their function is executed. As much as possible, + * .Nm + * tries to tick at constant intervals, however it is possible that one + * or more jobs may overrun the tick duration. If this happens, + * .Nm + * ticks again immediately after all the jobs for the previous tick + * have completed. This is in an effort to compensate for lost time, + * however it is important to note that when jobs overrun the tick + * interval, the interval is pushed back by the amount that it was + * overrun. Because of this, + * .Nm + * is best suited for scheduling jobs that should happen + * ``aproximately'' every so often; it is not a real-time scheduler + * by any means. + */ + +#include + +/** + * All functions defined here operate on a structure opaque to the + * caller. + */ +typedef struct Cron Cron; + +/** + * A job function is a function that takes a void pointer and returns + * nothing. The pointer is passed when the job is scheduled, and + * is expected to remain valid until the job is no longer registered. + * The pointer is passed each time the job executes. + */ +typedef void (JobFunc) (void *); + +/** + * Create a new + * .Nm + * object that all other functions operate on. Like most of the other + * APIs in this project, it must be freed with + * .Fn CronFree + * when it is no longer needed. + * .Pp + * This function takes the tick interval in milliseconds. + */ +extern Cron * CronCreate(UInt32); + +/** + * Schedule a one-off job to be executed only at the next tick, and + * then immediately discarded. This is useful for scheduling tasks that + * only have to happen once, or very infrequently depending on + * conditions other than the current time, but don't have to happen + * immediately. The caller simply indicates that it wishes for the task + * to execute at some time in the future. How far into the future this + * practically ends up being is determined by how long it takes for + * other registered jobs to finish, and what the tick interval is. + * .Pp + * This function takes a job function and a pointer to pass to that + * function when it is executed. + */ +extern void + CronOnce(Cron *, JobFunc *, void *); + +/** + * Schedule a repetitive task to be executed at aproximately the given + * interval. As stated above, this is as fuzzy interval; depending on + * the jobs being run and the tick interval, tasks may not execute at + * exactly the scheduled time, but they will eventually execute. + * .Pp + * This function takes an interval in milliseconds, a job function, + * and a pointer to pass to that function when it is executed. + */ +extern void + CronEvery(Cron *, unsigned long, JobFunc *, void *); + +/** + * Start ticking the clock and executing registered jobs. + */ +extern void + CronStart(Cron *); + +/** + * Stop ticking the clock. Jobs that are already executing will + * continue to execute, but when they are finished, no new jobs will + * be executed until + * .Fn CronStart + * is called. + */ +extern void + CronStop(Cron *); + +/** + * Discard all job references and free all memory associated with the + * given + * .Nm Cron + * instance. + */ +extern void + CronFree(Cron *); + +#endif /* CYTOPLASM_CRON_H */ diff --git a/src/include/Db.h b/src/include/Db.h new file mode 100644 index 0000000..618a2e2 --- /dev/null +++ b/src/include/Db.h @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_DB_H +#define CYTOPLASM_DB_H + +/*** + * @Nm Db + * @Nd A minimal flat-file database with mutex locking and cache. + * @Dd April 27 2023 + * @Xr Json + * + * Cytoplasm operates on a flat-file database instead of a + * traditional relational database. This greatly simplifies the + * persistent storage code, and creates a relatively basic API, + * described here. + */ + +#include + +#include +#include + +/** + * All functions in this API operate on a database structure that is + * opaque to the caller. + */ +typedef struct Db Db; + +/** + * When an object is locked, a reference is returned. This reference + * is owned by the current thread, and the database is inaccessible to + * other threads until all references have been returned to the + * database. + * .Pp + * This reference is opaque, but can be manipulated by the functions + * defined here. + */ +typedef struct DbRef DbRef; + +/** + * Open a data directory. This function takes a path to open, and a + * cache size in bytes. If the cache size is 0, then caching is + * disabled and objects are loaded off the disk every time they are + * locked. Otherwise, objects are stored in the cache, and they are + * evicted in a least-recently-used manner. + */ +extern Db * DbOpen(char *, size_t); + +/** + * Close the database. This function will flush anything in the cache + * to the disk, and then close the data directory. It assumes that + * all references have been unlocked. If a reference has not been + * unlocked, undefined behavior results. + */ +extern void DbClose(Db *); + +/** + * Set the maximum cache size allowed before + * .Nm + * starts evicting old objects. If this is set to 0, everything in the + * cache is immediately evicted and caching is disabled. If the + * database was opened with a cache size of 0, setting this will + * initialize the cache, and subsequent calls to + * .Fn DbLock + * will begin caching objects. + */ +extern void DbMaxCacheSet(Db *, size_t); + +/** + * Create a new object in the database with the specified name. This + * function will fail if the object already exists in the database. It + * takes a variable number of C strings, with the exact number being + * specified by the second parameter. These C strings are used to + * generate a filesystem path at which to store the object. These paths + * ensure each object is uniquely identifiable, and provides semantic + * meaning to an object. + */ +extern DbRef * DbCreate(Db *, size_t,...); + +/** + * Lock an existing object in the database. This function will fail + * if the object does not exist. It takes a variable number of C + * strings, with the exact number being specified by the second + * parameter. These C strings are used to generate the filesystem path + * at which to load the object. These paths ensure each object is + * uniquely identifiable, and provides semantic meaning to an object. + */ +extern DbRef * DbLock(Db *, size_t,...); + +/** + * Immediately and permanently remove an object from the database. + * This function assumes the object is not locked, otherwise undefined + * behavior will result. + */ +extern int DbDelete(Db *, size_t,...); + +/** + * Unlock an object and return it back to the database. This function + * immediately syncs the object to the filesystem. The cache is a + * read cache; writes are always immediate to ensure data integrity in + * the event of a system failure. + */ +extern int DbUnlock(Db *, DbRef *); + +/** + * Check the existence of the given database object in a more efficient + * manner than attempting to lock it with + * .Fn DbLock . + * This function does not lock the object, nor does it load it into + * memory if it exists. + */ +extern int DbExists(Db *, size_t,...); + +/** + * List all of the objects at a given path. Unlike the other varargs + * functions, this one does not take a path to a specific object; it + * takes a directory to be iterated, where each path part is its own + * C string. Note that the resulting list only contains the objects + * in the specified directory, it does not list any subdirectories. + * .Pp + * The array returned is an array of C strings containing the object + * name. + */ +extern Array * DbList(Db *, size_t,...); + +/** + * Free the list returned by + * .Fn DbListFree . + */ +extern void DbListFree(Array *); + +/** + * Convert a database reference into JSON that can be manipulated. + * At this time, the database actually stores objects as JSON on the + * disk, so this function just returns an internal pointer, but in the + * future it may have to be generated by decompressing a binary blob, + * or something of that nature. + */ +extern HashMap * DbJson(DbRef *); + +/** + * Free the existing JSON associated with the given reference, and + * replace it with new JSON. This is more efficient than duplicating + * a separate object into the database reference. + */ +extern int DbJsonSet(DbRef *, HashMap *); + +#endif diff --git a/src/include/Graph.h b/src/include/Graph.h new file mode 100644 index 0000000..02e4e02 --- /dev/null +++ b/src/include/Graph.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_GRAPH_H +#define CYTOPLASM_GRAPH_H + +/*** + * @Nm Graph + * @Nd Extremely simple graph, implemented as an adjacency matrix. + * @Dd July 15 2023 + * + * .Nm + * is a basic graph data structure originally written for a computer + * science class on data structures and algorithms, in which it + * received full credit. This is an adaptation of the original + * implementation that follows the Cytoplasm style and uses Cytoplasm + * APIs when convenient. + * .P + * .Nm + * stores data in an adjacency matrix, which means the storage + * complexity is O(N^2), where N is the number of vertices (called + * Nodes in this implementation) in the graph. However, this makes the + * algorithms fast and efficient. + * .P + * Nodes are identified by index, so the first node is 0, the second + * is 1, and so on. This data structure does not support storing + * arbitrary data as nodes; rather, the intended use case is to add + * all your node data to an Array, thus giving each node an index, + * and then manipulating the graph with that index. This allows access + * to node data in O(1) time in call cases, and is the most memory + * efficient. + * .P + * .Nm + * can be used to store a variety of types of graphs, although it is + * primarily suited to directed and weighted graphs. + */ + +#include + +/** + * The functions provided here operate on an opaque graph structure. + * This structure really just stores a matrix in a contiguous block of + * memory, as well as the number of nodes in the graph, but the + * structure is kept opaque so that it remains internally consistent. + * It also maintains the style of the Cytoplasm library. + */ +typedef struct Graph Graph; + +/** + * An Edge is really just a weight, which is easily represented by an + * integer. However, it makes sense to alias this to Edge for clarity, + * both in the documentation and in the implementation. + */ +typedef int Edge; + +/** + * A Node is really just a row or column in the matrix, which is easily + * represented by an unsigned integer. However, it makes sense to alias + * this to Node for clarity, both in the documentation and the + * implementation. + */ +typedef size_t Node; + +/** + * Create a new graph structure with the given number of vertices. + */ +extern Graph *GraphCreate(size_t); + +/** + * Create a new graph data structure with the given number of vertices + * and the given adjacency matrix. The adjacency matrix is copied + * verbatim into the graph data structure without any validation. + */ +extern Graph *GraphCreateWithEdges(size_t, Edge *); + +/** + * Free all memory associated with the given graph. Since graphs are + * just a collection of numbers, they do not depend on each other in + * any way. + */ +extern void GraphFree(Graph *); + +/** + * Get the weight of the edge connecting the node specified first to + * the node specified second. If this is a directed graph, it does not + * necessarily follow that there is an edge from the node specified + * second to the node specified first. It also does not follow that + * such an edge, if it exists, has the same weight. + * .P + * This function will return -1 if the graph is invalid or either node + * is out of bounds. It will return 0 if there is no such edge from the + * node specified first to the node specified second. + */ +extern Edge GraphEdgeGet(Graph *, Node, Node); + +/** + * Set the weight of the edge connecting the node specified first to + * the node specified second. If this is not a directed graph, this + * function will have to be called twice, the second time reversing the + * order of the nodes. To remove the edge, specify a weight of 0. + */ +extern Edge GraphEdgeSet(Graph *, Node, Node, Edge); + +/** + * Get the number of nodes in the given graph. This operation is a + * simple memory access that happens in O(1) time. + */ +extern size_t GraphCountNodes(Graph *); + +/** + * Perform a breadth-first search on the given graph, starting at the + * specified node. This function returns a list of nodes in the order + * they were touched. The size of the list is stored in the unsigned + * integer pointed to by the last argument. + * .P + * If an error occurs, NULL will be returned. Otherwise, the returned + * pointer should be freed with the Memory API when it is no longer + * needed. + */ +extern Node * GraphBreadthFirstSearch(Graph *, Node, size_t *); + +/** + * Perform a depth-first search on the given graph, starting at the + * specified node. This function returns a list of nodes in the order + * they were touched. The size of the list is stored in the unsigned + * integer pointed to by the last argument. + * .P + * If an error occurs, NULL will be returned. Otherwise the returned + * pointer should be freed with the Memory API when it is no longer + * needed. + */ +extern Node *GraphDepthFirstSearch(Graph *, Node, size_t *); + +/** + * Perform a topological sort on the given graph. This function returns + * a list of nodes in topological ordering, though note that this is + * probably not the only topological ordering that exists for the + * graph. The size of the list is stored in the unsigned integer + * pointed to by the last argument. It should always be the number of + * nodes in the graph, but is provided for consistency and convenience. + * .P + * If an error occurs, NULL will be returned. Otherwise the returned + * pointer should be freed with the Memory API when it is no longer + * needed. + */ +extern Node *GraphTopologicalSort(Graph *, size_t *); + +/** + * Transpose the given graph, returning a brand new graph that is the + * result of the transposition. + */ +extern Graph * GraphTranspose(Graph *); + +#endif /* CYTOPLASM_GRAPH_H */ diff --git a/src/include/HashMap.h b/src/include/HashMap.h new file mode 100644 index 0000000..1359400 --- /dev/null +++ b/src/include/HashMap.h @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_HASHMAP_H +#define CYTOPLASM_HASHMAP_H + +/*** + * @Nm HashMap + * @Nd A simple hash map implementation. + * @Dd October 11 2022 + * @Xr Array Queue + * + * This is the public interface for Cytoplasm's hash map + * implementation. This hash map is designed to be simple, + * well-documented, and generally readable and understandable, yet also + * performant enough to be useful, because it is used extensively + * throughout the project. + * .Pp + * Fundamentally, this is an entirely generic map implementation. It + * can be used for many general purposes, but it is designed only to + * implement the features Cytoplasm needs to be functional. One + * example of a Cytoplasm-specific feature is that keys cannot be + * arbitrary data; they are NULL-terminated C strings. + */ + +#include + +#include + +/** + * These functions operate on an opaque structure, which the caller + * has no knowledge about. + */ +typedef struct HashMap HashMap; + +/** + * Create a new hash map that is ready to be used with the rest of the + * functions defined here. + */ +extern HashMap * HashMapCreate(void); + +/** + * Free the specified hash map such that it becomes invalid and any + * future use results in undefined behavior. Note that this function + * does not free the values stored in the hash map, but since it stores + * the keys internally, it will free the keys. You should use + * .Fn HashMapIterate + * to free the values stored in this map appropriately before calling + * this function. + */ +extern void HashMapFree(HashMap *); + +/** + * Control the maximum load of the hash map before it is expanded. + * When the hash map reaches the given capacity, it is grown. You + * don't want to only grow hash maps when they are full, because that + * makes them perform very poorly. The maximum load value is a + * percentage of how full the hash map is, and it should be between + * 0 and 1, where 0 means that no elements will cause the map to be + * expanded, and 1 means that the hash map must be completely full + * before it is expanded. The default maximum load on a new hash map + * is 0.75, which should be good enough for most purposes, however, + * this function exists specifically so that the maximum load can be + * fine-tuned. + */ +extern void HashMapMaxLoadSet(HashMap *, float); + +/** + * Use a custom hashing function with the given hash map. New hash + * maps have a sane hashing function that should work okay for most + * use cases, but if you have a better hashing function, it can be + * specified this way. Do not change the hash function after keys have + * been added; doing so results in undefined behavior. Only set a new + * hash function immediately after constructing a new hash map, before + * anything has been added to it. + * .Pp + * The hash function takes a pointer to a C string, and is expected + * to return a fairly unique numerical hash value which will be + * converted into an array index. + */ +extern void +HashMapFunctionSet(HashMap *, unsigned long (*) (const char *)); + +/** + * Set the given string key to the given value. Note that the key is + * copied into the hash map's own memory space, but the value is not. + * It is the caller's job to ensure that the value pointer remains + * valid for the life of the hash map, and are freed when no longer + * needed. + */ +extern void * HashMapSet(HashMap *, char *, void *); + +/** + * Retrieve the value for the given key, or return NULL if no such + * key exists in the hash map. + */ +extern void * HashMapGet(HashMap *, const char *); + +/** + * Remove a value specified by the given key from the hash map, and + * return it to the caller to deal with. This function returns NULL + * if no such key exists. + */ +extern void * HashMapDelete(HashMap *, const char *); + +/** + * Iterate over all the keys and values of a hash map. This function + * works very similarly to + * .Xr getopt 3 , + * where calls are repeatedly made in a while loop until there are no + * more items to go over. The difference is that this function does not + * rely on globals; it takes pointer pointers, and stores all + * necessary state inside the hash map itself. + * .Pp + * Note that this function is not thread-safe; two threads cannot be + * iterating over any given hash map at the same time, though they + * can each be iterating over different hash maps. + * .Pp + * This function can be tricky to use in some scenarios, as it + * continues where it left off on each call, until there are no more + * elements to go through in the hash map. If you are not iterating + * over the entire map in one go, and happen to break the loop, then + * the next time you attempt to iterate the hash map, you'll start + * somewhere in the middle, which is most likely not the intended + * behavior. Thus, it is always recommended to iterate over the entire + * hash map if you're going to use this function. + * .Pp + * Also note that the behavior of this function is undefined if + * insertions or deletions occur during the iteration. This + * functionality has not been tested, and will likely not work. + */ +extern int HashMapIterate(HashMap *, char **, void **); + +/** + * A reentrant version of + * .Fn HashMapIterate + * that allows the caller to overcome the flaws of that function by + * storing the cursor outside of the hash map structure itself. This + * allows multiple threads to iterate over the same hash map at the + * same time, and it allows the iteration to be halted midway through + * without causing any unintended side effects. + * .Pp + * The cursor should be initialized to 0 at the start of iteration. + */ +extern int +HashMapIterateReentrant(HashMap *, char **, void **, size_t *); + +/** + * Collect the string keys of a hash map and return them as an array. + * The returned array holds pointers to the strings stored in the + * hash map, so the strings should NOT be freed; it is sufficient to + * free the array itself. Likewise, once the hash map is freed, the + * array elements are invalid and the array should be freed. + */ +extern Array * HashMapKeys(HashMap *); + +/** + * Collect the values of a hash map and return them as an array. The + * returned array holds the same pointers to the values as the hash + * map. + */ +extern Array * HashMapValues(HashMap *); + +#endif /* CYTOPLASM_HASHMAP_H */ diff --git a/src/include/HeaderParser.h b/src/include/HeaderParser.h new file mode 100644 index 0000000..ffb3d0b --- /dev/null +++ b/src/include/HeaderParser.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_HEADERPARSER_H +#define CYTOPLASM_HEADERPARSER_H + +/*** + * @Nm HeaderParser + * @Nd Parse simple C header files. + * @Dd April 29 2023 + * + * .Nm + * is an extremely simple parser that lacks most of the functionality + * one would expect from a C code parser. It simply maps a stream + * of tokens into a few categories, parsing major ``milestones'' in + * a header, without actually understanding any of the details. + * .Pp + * This exists because it is used to generate man pages from headers. + * See + * .Xr hdoc 1 + * for example usage of this parser. + */ + +#include +#include + +#define HEADER_EXPR_MAX 4096 + +/** + * Headers are parsed as expressions. These are the expressions that + * this parser recognizes. + */ +typedef enum HeaderExprType +{ + HP_COMMENT, + HP_PREPROCESSOR_DIRECTIVE, + HP_TYPEDEF, + HP_DECLARATION, + HP_GLOBAL, + HP_UNKNOWN, + HP_SYNTAX_ERROR, + HP_PARSE_ERROR, + HP_EOF +} HeaderExprType; + +/** + * A representation of a function declaration. + */ +typedef struct HeaderDeclaration +{ + char returnType[64]; + char name[32]; /* Enforced by ANSI C */ + Array *args; +} HeaderDeclaration; + +/** + * A global variable declaration. The type must be of the same size + * as the function declaration's return type due to the way parsing + * them is implemented. + */ +typedef struct HeaderGlobal +{ + char type[64]; + char name[HEADER_EXPR_MAX - 64]; +} HeaderGlobal; + +/** + * A representation of a single header expression. Note that that state + * structure is entirely internally managed, so it should not be + * accessed or manipulated by functions outside the functions defined + * here. + * .Pp + * The type field should be used to determine which field in the data + * union is valid. + */ +typedef struct HeaderExpr +{ + HeaderExprType type; + union + { + char text[HEADER_EXPR_MAX]; + HeaderDeclaration declaration; + HeaderGlobal global; + struct + { + int lineNo; + char *msg; + } error; + } data; + + struct + { + Stream *stream; + int lineNo; + } state; +} HeaderExpr; + +/** + * Parse the next expression into the given header expression structure. + * To parse an entire C header, this function should be called in a + * loop until the type of the expression is HP_EOF. + */ +extern void HeaderParse(Stream *, HeaderExpr *); + +#endif /* CYTOPLASM_HEADERPARSER_H */ diff --git a/src/include/Http.h b/src/include/Http.h new file mode 100644 index 0000000..d2de020 --- /dev/null +++ b/src/include/Http.h @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_HTTP_H +#define CYTOPLASM_HTTP_H + +/*** + * @Nm Http + * @Nd Encode and decode various parts of the HTTP protocol. + * @Dd March 12 2023 + * @Xr HttpClient HttpServer HashMap Queue Memory + * + * .Nm + * is a collection of utility functions and type definitions that are + * useful for dealing with HTTP. HTTP is not a complex protocol, but + * this API makes it a lot easier to work with. + * .Pp + * Note that this API doesn't target any particular HTTP version, but + * it is currently used with HTTP 1.0 clients and servers, and + * therefore may be lacking functionality added in later HTTP versions. + */ + +#include + +#include +#include + +#define HTTP_FLAG_NONE 0 +#define HTTP_FLAG_TLS (1 << 0) + +/** + * The request methods defined by the HTTP standard. These numeric + * constants should be preferred to strings when building HTTP APIs + * because they are more efficient. + */ +typedef enum HttpRequestMethod +{ + HTTP_METHOD_UNKNOWN, + HTTP_GET, + HTTP_HEAD, + HTTP_POST, + HTTP_PUT, + HTTP_DELETE, + HTTP_CONNECT, + HTTP_OPTIONS, + HTTP_TRACE, + HTTP_PATCH +} HttpRequestMethod; + +/** + * An enumeration that corresponds to the actual integer values of the + * valid HTTP response codes. + */ +typedef enum HttpStatus +{ + HTTP_STATUS_UNKNOWN = 0, + + /* Informational responses */ + HTTP_CONTINUE = 100, + HTTP_SWITCHING_PROTOCOLS = 101, + HTTP_EARLY_HINTS = 103, + + /* Successful responses */ + HTTP_OK = 200, + HTTP_CREATED = 201, + HTTP_ACCEPTED = 202, + HTTP_NON_AUTHORITATIVE_INFORMATION = 203, + HTTP_NO_CONTENT = 204, + HTTP_RESET_CONTENT = 205, + HTTP_PARTIAL_CONTENT = 206, + + /* Redirection messages */ + HTTP_MULTIPLE_CHOICES = 300, + HTTP_MOVED_PERMANENTLY = 301, + HTTP_FOUND = 302, + HTTP_SEE_OTHER = 303, + HTTP_NOT_MODIFIED = 304, + HTTP_TEMPORARY_REDIRECT = 307, + HTTP_PERMANENT_REDIRECT = 308, + + /* Client error messages */ + HTTP_BAD_REQUEST = 400, + HTTP_UNAUTHORIZED = 401, + HTTP_FORBIDDEN = 403, + HTTP_NOT_FOUND = 404, + HTTP_METHOD_NOT_ALLOWED = 405, + HTTP_NOT_ACCEPTABLE = 406, + HTTP_PROXY_AUTH_REQUIRED = 407, + HTTP_REQUEST_TIMEOUT = 408, + HTTP_CONFLICT = 409, + HTTP_GONE = 410, + HTTP_LENGTH_REQUIRED = 411, + HTTP_PRECONDITION_FAILED = 412, + HTTP_PAYLOAD_TOO_LARGE = 413, + HTTP_URI_TOO_LONG = 414, + HTTP_UNSUPPORTED_MEDIA_TYPE = 415, + HTTP_RANGE_NOT_SATISFIABLE = 416, + HTTP_EXPECTATION_FAILED = 417, + HTTP_TEAPOT = 418, + HTTP_UPGRADE_REQUIRED = 426, + HTTP_PRECONDITION_REQUIRED = 428, + HTTP_TOO_MANY_REQUESTS = 429, + HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, + HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451, + + /* Server error responses */ + HTTP_INTERNAL_SERVER_ERROR = 500, + HTTP_NOT_IMPLEMENTED = 501, + HTTP_BAD_GATEWAY = 502, + HTTP_SERVICE_UNAVAILABLE = 503, + HTTP_GATEWAY_TIMEOUT = 504, + HTTP_VERSION_NOT_SUPPORTED = 505, + HTTP_VARIANT_ALSO_NEGOTIATES = 506, + HTTP_NOT_EXTENDED = 510, + HTTP_NETWORK_AUTH_REQUIRED = 511 +} HttpStatus; + +/** + * Convert an HTTP status enumeration value into a string description + * of the status, which is to be used in server response to a client, + * or a client response to a user. For example, calling + * .Fn HttpStatusToString "HTTP_GATEWAY_TIMEOUT" + * (or + * .Fn HttpStatusToString "504" ) + * produces the string "Gateway Timeout". Note that the returned + * pointers point to static space, so their manipulation is forbidden. + */ +extern const char * HttpStatusToString(const HttpStatus); + +/** + * Convert a string into a numeric code that can be used throughout + * the code of a program in an efficient manner. See the definition + * of HttpRequestMethod. This function does case-sensitive matching, + * and does not trim or otherwise process the input string. + */ +extern HttpRequestMethod HttpRequestMethodFromString(const char *); + +/** + * Convert a numeric code as defined by HttpRequestMethod into a + * string that can be sent to a server. Note that the returned pointers + * point to static space, so their manipulation is forbidden. + */ +extern const char * HttpRequestMethodToString(const HttpRequestMethod); + +/** + * Encode a C string such that it can safely appear in a URL by + * performing the necessary percent escaping. A new string on the + * heap is returned. It should be freed with + * .Fn Free , + * defined in the + * .Xr Memory 3 + * API. + */ +extern char * HttpUrlEncode(char *); + +/** + * Decode a percent-encoded string into a C string, ignoring encoded + * null characters entirely, because those would do nothing but cause + * problems. + */ +extern char * HttpUrlDecode(char *); + +/** + * Decode an encoded parameter string in the form of + * ``key=val&key2=val2'' into a hash map whose values are C strings. + * This function properly decodes keys and values using the functions + * defined above. + */ +extern HashMap * HttpParamDecode(char *); + +/** + * Encode a hash map whose values are strings as an HTTP parameter + * string suitable for GET or POST requests. + */ +extern char * HttpParamEncode(HashMap *); + +/** + * Read HTTP headers from a stream and return a hash map whose values + * are strings. All keys are lowercased to make querying them + * consistent and not dependent on the case that was read from the + * stream. This is useful for both client and server code, since the + * headers are in the same format. This function should be used after + * parsing the HTTP status line, because it does not parse that line. + * It will stop when it encounters the first blank line, which + * indicates that the body is beginning. After this function completes, + * the body may be immediately read from the stream without any + * additional processing. + */ +extern HashMap * HttpParseHeaders(Stream *); + +#endif diff --git a/src/include/HttpClient.h b/src/include/HttpClient.h new file mode 100644 index 0000000..f757041 --- /dev/null +++ b/src/include/HttpClient.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_HTTPCLIENT_H +#define CYTOPLASM_HTTPCLIENT_H + +/*** + * @Nm HttpClient + * @Nd Extremely simple HTTP client. + * @Dd April 29 2023 + * @Xr Http HttpServer Tls + * + * .Nm + * HttpClient + * builds on the HTTP API to provide a simple yet functional HTTP + * client. It aims at being easy to use and minimal, yet also + * efficient. + */ + +#include + +#include +#include + +/** + * A server response is represented by a client context. It is + * opaque, so the functions defined in this API should be used to + * fetch data from and manipulate it. + */ +typedef struct HttpClientContext HttpClientContext; + +/** + * Make an HTTP request. This function takes the request method, + * any flags defined in the HTTP API, the port, hostname, and path, + * all in that order. It returns NULL if there was an error making + * the request. Otherwise it returns a client context. Note that this + * function does not actually send any data, it simply makes the + * connection. Use + * .Fn HttpRequestHeader + * to add headers to the request. Then, send headers with + * .Fn HttpRequestSendHeaders . + * Finally, the request body, if any, can be written to the output + * stream, and then the request can be fully sent using + * .Fn HttpRequestSend . + */ +extern HttpClientContext * + HttpRequest(HttpRequestMethod, int, unsigned short, char *, char *); + +/** + * Set a request header to send to the server when making the + * request. + */ +extern void HttpRequestHeader(HttpClientContext *, char *, char *); + +/** + * Send the request headers to the server. This must be called before + * the request body can be written or a response body can be read. + */ +extern void HttpRequestSendHeaders(HttpClientContext *); + +/** + * Flush the request stream to the server. This function should be + * called before the response body is read. + */ +extern HttpStatus HttpRequestSend(HttpClientContext *); + +/** + * Get the headers returned by the server. + */ +extern HashMap * HttpResponseHeaders(HttpClientContext *); + +/** + * Get the stream used to write the request body and read the + * response body. + */ +extern Stream * HttpClientStream(HttpClientContext *); + +/** + * Free all memory associated with the client context. This closes the + * connection, if it was still open. + */ +extern void HttpClientContextFree(HttpClientContext *); + +#endif /* CYTOPLASM_HTTPCLIENT_H */ diff --git a/src/include/HttpRouter.h b/src/include/HttpRouter.h new file mode 100644 index 0000000..6816178 --- /dev/null +++ b/src/include/HttpRouter.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_HTTPROUTER_H +#define CYTOPLASM_HTTPROUTER_H + +/*** + * @Nm HttpRouter + * @Nd Simple HTTP request router with regular expression support. + * @Dd April 29 2023 + * @Xr HttpServer Http + * + * .Nm + * provides a simple mechanism for assigning functions to an HTTP + * request path. It is a simple tree data structure that parses the + * registered request paths and maps functions onto each part of the + * path. Then, requests can be easily routed to their appropriate + * handler functions. + */ + +#include + +/** + * The router structure is opaque and thus managed entirely by the + * functions defined in this API. + */ +typedef struct HttpRouter HttpRouter; + +/** + * A function written to handle an HTTP request takes an array + * consisting of the matched path parts in the order they appear in + * the path, and a pointer to caller-provided arguments, if any. + * It returns a pointer that the caller is assumed to know how to + * handle. + */ +typedef void *(HttpRouteFunc) (Array *, void *); + +/** + * Create a new empty routing tree. + */ +extern HttpRouter * HttpRouterCreate(void); + +/** + * Free all the memory associated with the given routing tree. + */ +extern void HttpRouterFree(HttpRouter *); + +/** + * Register the specified route function to be executed upon requests + * for the specified HTTP path. The path is parsed by splitting at + * each path separator. Each part of the path is a regular expression + * that matches the entire path part. A regular expression cannot + * match more than one path part. This allows for paths like + * .Pa /some/path/(.*)/parts + * to work as one would expect. + */ +extern int HttpRouterAdd(HttpRouter *, char *, HttpRouteFunc *); + +/** + * Route the specified request path using the specified routing + * tree. This function will parse the path and match it to the + * appropriate route handler function. The return value is a boolean + * value that indicates whether or not an appropriate route function + * was found. If an appropriate function was found, then the void + * pointer is passed to it as arguments that it is expected to know + * how to handle, and the pointer to a void pointer is where the + * route function's response will be placed. + */ +extern int HttpRouterRoute(HttpRouter *, char *, void *, void **); + +#endif /* CYTOPLASM_HTTPROUTER_H */ diff --git a/src/include/HttpServer.h b/src/include/HttpServer.h new file mode 100644 index 0000000..f8e449a --- /dev/null +++ b/src/include/HttpServer.h @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_HTTPSERVER_H +#define CYTOPLASM_HTTPSERVER_H + +/*** + * @Nm HttpServer + * @Nd Extremely simple HTTP server. + * @Dd December 13 2022 + * @Xr Http HttpClient + * + * .Nm + * builds on the + * .Xr Http 3 + * API, and provides a very simple, yet very functional API for + * creating an HTTP server. It aims at being easy to use and minimal, + * yet also efficient. It uses non-blocking I/O, is fully + * multi-threaded, and is very configurable. It can be set up in just + * two function calls and minimal supporting code. + * .Pp + * This API should be familar to those that have dealt with the HTTP + * server libraries of other programming languages, particularly Java. + * In fact, much of the terminology used in this API came from Java, + * and you'll notice that the way responses are sent and received very + * closely resembles Java. + */ + +#include + +#include + +#include +#include + +/** + * The functions on this API operate on an opaque structure. + */ +typedef struct HttpServer HttpServer; + +/** + * Each request receives a context structure. It is opaque, so the + * functions defined in this API should be used to fetch data from + * it. These functions allow the handler to figure out the context of + * the request, which includes the path requested, any parameters, + * and the headers and method used to make the request. The context + * also provides the means by which the handler responds to the + * request, allowing it to set the status code, headers, and body. + */ +typedef struct HttpServerContext HttpServerContext; + +/** + * The request handler function is executed when an HTTP request is + * received. It takes a request context, and a pointer as specified + * in the server configuration. + */ +typedef void (HttpHandler) (HttpServerContext *, void *); + +/** + * The number of arguments to + * .Fn HttpServerCreate + * has grown so large that arguments are now stuffed into a + * configuration structure, which is in turn passed to + * .Fn HttpServerCreate . + * This configuration is copied by value into the internal + * structures of the server. It is copied with very minimal + * validation, so ensure that all values are sensible. It may + * make sense to use + * .Fn memset + * to zero out everything in here before assigning values. + */ +typedef struct HttpServerConfig +{ + unsigned short port; + unsigned int threads; + unsigned int maxConnections; + + int flags; /* Http(3) flags */ + char *tlsCert; /* File path */ + char *tlsKey; /* File path */ + + HttpHandler *handler; + void *handlerArgs; +} HttpServerConfig; + +/** + * Create a new HTTP server using the specified configuration. + * This will set up all internal structures used by the server, + * and bind the socket and start listening for connections. However, + * it will not start accepting connections. + */ +extern HttpServer * HttpServerCreate(HttpServerConfig *); + +/** + * Retrieve the configuration that was used to instantiate the given + * server. Note that this configuration is not necessarily the exact + * one that was provided; even though its values are the same, it + * should be treated as an entirely separate configuration with no + * connection to the original. + */ +extern HttpServerConfig * HttpServerConfigGet(HttpServer *); + +/** + * Free the resources associated with the given HTTP server. Note that + * the server can only be freed after it has been stopped. Calling this + * function while the server is still running results in undefined + * behavior. + */ +extern void HttpServerFree(HttpServer *); + +/** + * Attempt to start the HTTP server, and return immediately with the + * status. This API is fully multi-threaded and asynchronous, so the + * caller can continue working while the HTTP server is running in a + * separate thread and managing a pool of threads to handle responses. + */ +extern int HttpServerStart(HttpServer *); + +/** + * Typically, at some point after calling + * .Fn HttpServerStart , + * the program will have no more work to do, so it will want to wait + * for the HTTP server to finish. This is accomplished via this + * function, which joins the HTTP worker thread to the calling thread, + * pausing the calling thread until the HTTP server has stopped. + */ +extern void HttpServerJoin(HttpServer *); + +/** + * Stop the HTTP server. Only the execution of this function will + * cause the proper shutdown of the HTTP server. If the main program + * is joined to the HTTP thread, then either another thread or a + * signal handler will have to stop the server using this function. + * The typical use case is to install a signal handler that executes + * this function on a global HTTP server. + */ +extern void HttpServerStop(HttpServer *); + +/** + * Get the request headers for the request represented by the given + * context. The data in the returned hash map should be treated as + * read only and should not be freed; it is managed entirely by the + * server. + */ +extern HashMap * HttpRequestHeaders(HttpServerContext *); + +/** + * Get the request method used to make the request represented by + * the given context. + */ +extern HttpRequestMethod HttpRequestMethodGet(HttpServerContext *); + +/** + * Get the request path for the request represented by the given + * context. The return value of this function should be treated as + * read-only, and should not be freed; it is managed entirely by the + * server. + */ +extern char * HttpRequestPath(HttpServerContext *); + +/** + * Retrieve the parsed GET parameters for the request represented by + * the given context. The returned hash map should be treated as + * read-only, and should not be freed; it is managed entirely by the + * server. + */ +extern HashMap * HttpRequestParams(HttpServerContext *); + +/** + * Set a response header to return to the client. The old value for + * the given header is returned, if any, otherwise NULL is returned. + */ +extern char * HttpResponseHeader(HttpServerContext *, char *, char *); + +/** + * Set the response status to return to the client. + */ +extern void HttpResponseStatus(HttpServerContext *, HttpStatus); + +/** + * Get the current response status that will be sent to the client + * making the request represented by the given context. + */ +extern HttpStatus HttpResponseStatusGet(HttpServerContext *); + +/** + * Send the response headers to the client that made the request + * represented by the specified context. This function must be called + * before the response body can be written, otherwise a malformed + * response will be sent. + */ +extern void HttpSendHeaders(HttpServerContext *); + +/** + * Get a stream that is both readable and writable. Reading from the + * stream reads the request body that the client sent, if there is one. + * Note that the rquest headers have already been read, so the stream + * is correctly positioned at the beginning of the body of the request. + * .Fn HttpSendHeaders + * must be called before the stream is written, otherwise a malformed + * HTTP response will be sent. An HTTP handler should properly set all + * the headers it itends to send, send those headers, and then write + * the response body to this stream. + * .Pp + * Note that the stream does not need to be closed by the HTTP + * handler; in fact doing so results in undefined behavior. The stream + * is managed entirely by the server itself, so it will close it when + * necessary. This allows the underlying protocol to differ: for + * instance, an HTTP/1.1 connection may stay for multiple requests and + * responses. + */ +extern Stream * HttpServerStream(HttpServerContext *); + +#endif /* CYTOPLASM_HTTPSERVER_H */ diff --git a/src/include/Int.h b/src/include/Int.h new file mode 100644 index 0000000..7933a69 --- /dev/null +++ b/src/include/Int.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_INT_H +#define CYTOPLASM_INT_H + +/*** + * @Nm Int + * @Nd Fixed-width integer types. + * @Dd April 27 2023 + * + * This header provides cross-platform, fixed-width integer types. + * Specifically, it uses preprocessor magic to define the following + * types: + * .Bl -bullet -offset indent + * .It + * Int8 and UInt8 + * .It + * Int16 and UInt16 + * .It + * Int32 and UInt32 + * .El + * .Pp + * Note that there is no 64-bit integer type, because the ANSI C + * standard makes no guarantee that such a type will exist, even + * though it does on most platforms. + * .Pp + * The reason Cytoplasm provides its own header for this is + * because ANSI C does not define fixed-width types, and while it + * should be safe to rely on C99 fixed-width types in most cases, + * there may be cases where even that is not possible. + * + * @ignore-typedefs + */ + +#include + +#define BIT32_MAX 4294967295UL +#define BIT16_MAX 65535UL +#define BIT8_MAX 255UL + +#ifndef UCHAR_MAX +#error Size of char data type is unknown. Define UCHAR_MAX. +#endif + +#ifndef USHRT_MAX +#error Size of short data type is unknown. Define USHRT_MAX. +#endif + +#ifndef UINT_MAX +#error Size of int data type is unknown. Define UINT_MAX. +#endif + +#ifndef ULONG_MAX +#error Size of long data type is unknown. Define ULONG_MAX. +#endif + +#if UCHAR_MAX == BIT8_MAX +typedef signed char Int8; +typedef unsigned char UInt8; + +#else +#error Unable to determine suitable data type for 8-bit integers. +#endif + +#if UINT_MAX == BIT16_MAX +typedef signed int Int16; +typedef unsigned int UInt16; + +#elif USHRT_MAX == BIT16_MAX +typedef signed short Int16; +typedef unsigned short UInt16; + +#elif UCHAR_MAX == BIT16_MAX +typedef signed char Int16; +typedef unsigned char UInt16; + +#else +#error Unable to determine suitable data type for 16-bit integers. +#endif + +#if ULONG_MAX == BIT32_MAX +typedef signed long Int32; +typedef unsigned long UInt32; + +#elif UINT_MAX == BIT32_MAX +typedef signed int Int32; +typedef unsigned int UInt32; + +#elif USHRT_MAX == BIT32_MAX +typedef signed short Int32; +typedef unsigned short UInt32; + +#elif UCHAR_MAX == BIT32_MAX +typedef signed char Int32; +typedef unsigned char UInt32; + +#else +#error Unable to determine suitable data type for 32-bit integers. +#endif + +#endif /* CYTOPLASM_INT_H */ diff --git a/src/include/Int64.h b/src/include/Int64.h new file mode 100644 index 0000000..08083c1 --- /dev/null +++ b/src/include/Int64.h @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_INT64_H +#define CYTOPLASM_INT64_H + +/*** + * @Nm Int64 + * @Nd Fixed-width 64 bit integers. + * @Dd August 11, 2023 + * + * .Pp + * ANSI C89 (or C99 for that matter) provides no required mechanism + * for 64 bit integers. Nevertheless, many compilers provide them as + * extensions. However, since it is not a gaurantee, and to be fully + * standards-compliant and thus portable, a platform-agnostic interface + * is required. This header provides such an interface. If the platform + * has a 64 bit integer type, that is used, and native operations are + * performed by C preprocessor macro expansion. Otherwise, a + * compatibility layer is provided, which implements 64-bit + * arithmetic on an array of 2 32-bit numbers which are provided by + * .Xr Int 3 . + * .Pp + * Note that 64-bit emulation is certainly not as performant as using + * native 64-bit operations, so whenever possible, the native + * operations should be preferred. However, since C provides no required + * 64 bit integer on 32-bit and less platforms, this API can be used as + * a "good enough" fallback mechanism. + * .Pp + * Also note that this implementation, both in the native and + * non-native forms, makes some assumptions: + * .Bl -bullet -width Ds + * .It + * When a cast from a larger integer to a smaller integer is performed, + * the upper bits are truncated, not the lower bits. + * .It + * Negative numbers are represented in memory and in registers in two's + * compliment form. + * .El + * .Pp + * This API may provide unexpected output if these assumptions are + * false for a given platform. + * + * @ignore-typedefs + */ + +#include +#include + +#include + +#ifndef INT64_FORCE_EMULATED + +#define BIT64_MAX 18446744073709551615UL + +#if UINT_MAX == BIT64_MAX +typedef signed int Int64; + +#define INT64_NATIVE + +#elif ULONG_MAX == BIT64_MAX +typedef signed long Int64; + +#define INT64_NATIVE + +#endif + +#endif /* ifndef INT64_FORCE_EMULATED */ + +#ifdef INT64_NATIVE + +#define Int64Create(high, low) ((Int64) (((UInt64) (high) << 32) | (low))) +#define Int64Neg(x) (-(x)) + +#define Int64Low(a) ((UInt32) (a)) +#define Int64High(a) ((UInt32) ((a) >> 32)) + +#define Int64Add(a, b) ((a) + (b)) +#define Int64Sub(a, b) ((a) - (b)) +#define Int64Mul(a, b) ((a) * (b)) +#define Int64Div(a, b) ((a) / (b)) +#define Int64Rem(a, b) ((a) % (b)) + +#define Int64Sll(a, b) ((a) << (b)) +#define Int64Sra(a, b) ((a) >> (b)) + +#define Int64And(a, b) ((a) & (b)) +#define Int64Or(a, b) ((a) | (b)) +#define Int64Xor(a, b) ((a) ^ (b)) +#define Int64Not(a) (~(a)) + +#define Int64Eq(a, b) ((a) == (b)) +#define Int64Lt(a, b) ((a) < (b)) +#define Int64Gt(a, b) ((a) > (b)) + +#define Int64Neq(a, b) ((a) != (b)) +#define Int64Leq(a, b) ((a) <= (b)) +#define Int64Geq(a, b) ((a) >= (b)) + +#else + +#define Int64Neg(x) (Int64Add(Int64Not(x), Int64Create(0, 1))) + +/** + * The internal bit representation of a signed integer is identical + * to an unsigned integer, the difference is in the algorithms and + * the way the bits are interpreted. + */ +typedef UInt64 Int64; + +/** + * Create a new signed 64 bit integer using the given high and low + * bits. + */ +extern Int64 Int64Create(UInt32, UInt32); + +/** + * Add two signed 64 bit integers together. + */ +extern Int64 Int64Add(Int64, Int64); + +/** + * Subtract the second 64 bit integer from the first. + */ +extern Int64 Int64Sub(Int64, Int64); + +/** + * Multiply two 64 bit integers together. The non-native version of + * this function uses the Russian Peasant method of multiplication, + * which should afford more performance than a naive multiplication by + * addition, but it is still rather slow and depends on the size of + * the integers being multiplied. + */ +extern Int64 Int64Mul(Int64, Int64); + +/** + * Divide the first 64 bit integer by the second and return the + * quotient. The non-native version of this function uses naive binary + * long division, which is slow, but gauranteed to finish in constant + * time. + */ +extern Int64 Int64Div(Int64, Int64); + +/** + * Divide the first 64 bit integer by the second and return the + * remainder. The non-native version of this function uses naive binary + * long division, which is slow, but gauranteed to finish in constant + * time. + */ +extern Int64 Int64Rem(Int64, Int64); + +/** + * Perform a left logical bit shift of a 64 bit integer. The second + * parameter is how many places to shift, and is declared as a regular + * integer because anything more than 64 does not make sense. + */ +extern Int64 Int64Sll(Int64, int); + +/** + * Perform a right arithmetic bit shift of a 64 bit integer. The second + * parameter is how many places to shift, and is declared as a regular + * integer because anything more than 64 does not make sense. + * .Pp + * Note that on platforms that use the native 64-bit implementation, + * this is technically implementation-defined, and may in fact be a + * logical shift instead of an arithmetic shift. Note that typically + * this operation is not performed on signed integers. + */ +extern Int64 Int64Sra(Int64, int); + +/** + * Perform a bitwise AND (&) of the provided 64 bit integers. + */ +extern Int64 Int64And(Int64, Int64); + +/** + * Perform a bitwise OR (|) of the provided 64 bit integers. + */ +extern Int64 Int64Or(Int64, Int64); + +/** + * Perform a bitwise XOR (^) of the provided 64 bit integers. + */ +extern Int64 Int64Xor(Int64, Int64); + +/** + * Perform a bitwise NOT (~) of the provided 64 bit integer. + */ +extern Int64 Int64Not(Int64); + +/** + * Perform a comparison of the provided 64 bit integers and return a C + * boolean that is true if and only if they are equal. + */ +extern int Int64Eq(Int64, Int64); + +/** + * Perform a comparison of the provided 64 bit integers and return a C + * boolean that is true if and only if the second operand is strictly + * less than the first. + */ +extern int Int64Lt(Int64, Int64); + +/** + * Perform a comparison of the provided 64 bit integers and return a C + * boolean that is true if and only if the second operand is strictly + * greater than the first. + */ +extern int Int64Gt(Int64, Int64); + +#define Int64Low(a) ((a).i[0]) +#define Int64High(a) ((a).i[1]) + +#define Int64Neq(a, b) (!Int64Eq(a, b)) +#define Int64Leq(a, b) (Int64Eq(a, b) || Int64Lt(a, b)) +#define Int64Geq(a, b) (Int64Eq(a, b) || Int64Gt(a, b)) + +#endif + +#define INT64_STRBUF 65 /* Base 2 representation with '\0' */ + +/** + * Convert a 64 bit integer to a string in an arbitrary base + * representation specified by the second parameter, using the provided + * buffer and length specified by the third and fourth parameters. To + * guarantee that the string will fit in the buffer, allocate it of + * size INT64_STRBUF or larger. Note that a buffer size smaller than + * INT64_STRBUF will invoke undefined behavior. + */ +extern size_t Int64Str(Int64, int, char *, size_t); + +#endif /* CYTOPLASM_INT64_H */ diff --git a/src/include/Io.h b/src/include/Io.h new file mode 100644 index 0000000..06afb77 --- /dev/null +++ b/src/include/Io.h @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_IO_H +#define CYTOPLASM_IO_H + +/*** + * @Nm Io + * @Nd Source/sink-agnostic I/O for implementing custom streams. + * @Dd April 29 2023 + * @Xr Stream Tls + * + * Many systems provide platform-specific means of implementing custom + * streams using file pointers. However, POSIX does not define a way + * of programmatically creating custom streams. + * .Nm + * therefore fills this gap in POSIX by mimicking all of the + * functionality of these platform-specific functions, but in pure + * POSIX C. It defines a number of callback funtions to be executed + * in place of the standard POSIX I/O functions, which are used to + * implement arbitrary streams that may not be to a file or socket. + * Additionally, streams can now be pipelined; the sink of one stream + * may be the source of another lower-level stream. Additionally, all + * streams, regardless of their source or sink, share the same API, so + * streams can be handled in a much more generic manner. This allows + * the HTTP client and server libraries to seemlessly support TLS and + * plain connections without having to handle each separately. + * .Pp + * .Nm + * was heavily inspired by GNU's + * .Fn fopencookie + * and BSD's + * .Fn funopen . + * It aims to combine the best of both of these functions into a single + * API that is intuitive and easy to use. + */ + +#include +#include +#include +#include + +#ifndef IO_BUFFER +#define IO_BUFFER 4096 +#endif + +/** + * An opaque structure analogous to a POSIX file descriptor. + */ +typedef struct Io Io; + +/** + * Read input from the source of a stream. This function should + * attempt to read the specified number of bytes of data from the + * given cookie into the given buffer. It should behave identically + * to the POSIX + * .Xr read 2 + * system call, except instead of using an integer descriptor as the + * first parameter, a pointer to an implementation-defined cookie + * stores any information the function needs to read from the source. + */ +typedef ssize_t (IoReadFunc) (void *, void *, size_t); + +/** + * Write output to a sink. This function should attempt to write the + * specified number of bytes of data from the given buffer into the + * stream described by the given cookie. It should behave identically + * to the POSIX + * .Xr write 2 + * system call, except instead of using an integer descriptor as the + * first parameter, a pointer to an implementation-defined cookie + * stores any information the function needs to write to the sink. + */ +typedef ssize_t (IoWriteFunc) (void *, void *, size_t); + +/** + * Repositions the offset of the stream described by the specified + * cookie. This function should behave identically to the POSIX + * .Xr lseek 2 + * system call, except instead of using an integer descriptor as the + * first parameter, a pointer to an implementation-defined cookie + * stores any information the function needs to seek the stream. + */ +typedef off_t (IoSeekFunc) (void *, off_t, int); + +/** + * Close the given stream, making future reads or writes result in + * undefined behavior. This function should also free all memory + * associated with the cookie. It should behave identically to the + * .Xr close 2 + * system call, except instead of using an integer descriptor for the + * parameter, a pointer to an implementation-defined cookie stores any + * information the function needs to close the stream. + */ +typedef int (IoCloseFunc) (void *); + +/** + * A simple mechanism for grouping together a set of stream functions, + * to be passed to + * .Fn IoCreate . + */ +typedef struct IoFunctions +{ + IoReadFunc *read; + IoWriteFunc *write; + IoSeekFunc *seek; + IoCloseFunc *close; +} IoFunctions; + +/** + * Create a new stream using the specified cookie and the specified + * I/O functions. + */ +extern Io * IoCreate(void *, IoFunctions); + +/** + * Read the specified number of bytes from the specified stream into + * the specified buffer. This calls the stream's underlying IoReadFunc, + * which should behave identically to the POSIX + * .Xr read 2 + * system call. + */ +extern ssize_t IoRead(Io *, void *, size_t); + +/** + * Write the specified number of bytes from the specified stream into + * the specified buffer. This calls the stream's underlying + * IoWriteFunc, which should behave identically to the POSIX + * .Xr write 2 + * system call. + */ +extern ssize_t IoWrite(Io *, void *, size_t); + +/** + * Seek the specified stream using the specified offset and whence + * value. This calls the stream's underlying IoSeekFunc, which should + * behave identically to the POSIX + * .Xr lseek 2 + * system call. + */ +extern off_t IoSeek(Io *, off_t, int); + +/** + * Close the specified stream. This calls the stream's underlying + * IoCloseFunc, which should behave identically to the POSIX + * .Xr close 2 + * system call. + */ +extern int IoClose(Io *); + +/** + * Print a formatted string to the given stream. This is a + * re-implementation of the standard library function + * .Xr vfprintf 3 , + * and behaves identically. + */ +extern int IoVprintf(Io *, const char *, va_list); + +/** + * Print a formatted string to the given stream. This is a + * re-implementation of the standard library function + * .Xr fprintf 3 , + * and behaves identically. + */ +extern int IoPrintf(Io *, const char *,...); + +/** + * Read all the bytes from the first stream and write them to the + * second stream. Neither stream is closed upon the completion of this + * function. This can be used for quick and convenient buffered + * copying of data from one stream into another. + */ +extern ssize_t IoCopy(Io *, Io *); + +/** + * Wrap a POSIX file descriptor to take advantage of this API. The + * common use case for this function is when a regular file descriptor + * needs to be accessed by an application that uses this API to also + * access non-POSIX streams. + */ +extern Io * IoFd(int); + +/** + * Open or create a file for reading or writing. The specified file + * name is opened for reading or writing as specified by the given + * flags and mode. This function is a simple convenience wrapper around + * the POSIX + * .Xr open 2 + * system call that passes the opened file descriptor into + * .Fn IoFd . + */ +extern Io * IoOpen(const char *, int, mode_t); + +/** + * Wrap a standard C file pointer to take advantage of this API. The + * common use case for this function is when a regular C file pointer + * needs to be accessed by an application that uses this API to also + * access custom streams. + */ +extern Io * IoFile(FILE *); + +#endif /* CYTOPLASM_IO_H */ diff --git a/src/include/Json.h b/src/include/Json.h new file mode 100644 index 0000000..4185260 --- /dev/null +++ b/src/include/Json.h @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_JSON_H +#define CYTOPLASM_JSON_H + +/*** + * @Nm Json + * @Nd A fully-featured JSON API. + * @Dd March 12 2023 + * @Xr HashMap Array Stream + * + * .Nm + * is a fully-featured JSON API for C using the array and hash map + * APIs. It can parse JSON, ans serialize an in-memory structure to + * JSON. It build on the foundation of Array and HashMap because that's + * all JSON really is, just arrays and maps. + * .Nm + * also provides a structure for encapsulating an arbitrary value and + * identifying its type, making it easy for a strictly-typed language + * like C to work with loosely-typed JSON data. + * .Nm + * is very strict and tries to adhere as closely as possible to the + * proper definition of JSON. It will fail on syntax errors of any + * kind, which is fine for a Matrix homeserver that can just return + * M_BAD_JSON if anything in here fails, but this behavior may not be + * suitable for other purposes. + * .Pp + * This JSON implementation focuses primarily on serialization and + * deserialization to and from streams. It does not provide facilities + * for handling JSON strings; it only writes JSON to output streams, + * and reads them from input streams. Of course, you can use the + * POSIX + * .Xr fmemopen 3 + * and + * .Xr open_memstream 3 + * functions if you want to deal with JSON strings, but JSON is + * intended to be an exchange format. Data should be converted to JSON + * right when it is leaving the program, and converted from JSON to the + * in-memory format as soon as it is coming in. + * .Pp + * JSON objects are represented as hash maps consisting entirely of + * JsonValue structures, and arrays are represented as arrays + * consisting entirely of JsonValue structures. When generating a + * JSON object, any attempt to stuff a value into a hash map or array + * without first encoding it as a JsonValue will result in undefined + * behavior. + */ + +#include +#include +#include +#include + +#include +#include + +#define JSON_DEFAULT -1 +#define JSON_PRETTY 0 + +/** + * This opaque structure encapsulates all the possible types that can + * be stored in JSON. It is managed entirely by the functions defined + * in this API. It is important to note that strings, integers, floats, + * booleans, and the NULL value are all stored by value, but objects + * and arrays are stored by reference. That is, it doesn't store these + * itself, just pointers to them, however, the data + * .Em is + * freed when using + * .Fn JsonFree . + */ +typedef struct JsonValue JsonValue; + +/** + * These are the types that can be used to identify a JsonValue + * and act on it accordingly. + */ +typedef enum JsonType +{ + JSON_NULL, /* Maps to a C NULL */ + JSON_OBJECT, /* Maps to a HashMap of JsonValues */ + JSON_ARRAY, /* Maps to an Array of JsonValues */ + JSON_STRING, /* Maps to a null-terminated C string */ + JSON_INTEGER, /* Maps to an Int64 */ + JSON_FLOAT, /* Maps to a C double */ + JSON_BOOLEAN /* Maps to a C integer of either 0 or 1 */ +} JsonType; + +/** + * Determine the type of the specified JSON value. + */ +extern JsonType JsonValueType(JsonValue *); + +/** + * Encode a JSON object as a JSON value that can be added to another + * object, or an array. + */ +extern JsonValue * JsonValueObject(HashMap *); + +/** + * Unwrap a JSON value that represents an object. This function will + * return NULL if the value is not actually an object. + */ +extern HashMap * JsonValueAsObject(JsonValue *); + +/** + * Encode a JSON array as a JSON value that can be added to an object + * or another array. + */ +extern JsonValue * JsonValueArray(Array *); + +/** + * Unwrap a JSON value that represents an array. This function will + * return NULL if the value is not actually an array. + */ +extern Array * JsonValueAsArray(JsonValue *); + +/** + * Encode a C string as a JSON value that can be added to an object or + * an array. + */ +extern JsonValue * JsonValueString(char *); + +/** + * Unwrap a JSON value that represents a string. This function will + * return NULL if the value is not actually a string. + */ +extern char * JsonValueAsString(JsonValue *); + +/** + * Encode a number as a JSON value that can be added to an object or + * an array. + */ +extern JsonValue * JsonValueInteger(Int64); + +/** + * Unwrap a JSON value that represents a number. This function will + * return 0 if the value is not actually a number, which may be + * misleading. Check the type of the value before making assumptions + * about its value. + */ +extern Int64 JsonValueAsInteger(JsonValue *); + +/** + * Encode a floating point number as a JSON value that can be added + * to an object or an array. + */ +extern JsonValue * JsonValueFloat(double); + +/** + * Unwrap a JSON value that represents a floating point number. This + * function will return 0 if the value is not actually a floating + * point number, which may be misleading. Check the type of the value + * before making assumptions about its type. + */ +extern double JsonValueAsFloat(JsonValue *); + +/** + * Encode a C integer according to the way C treats integers in boolean + * expressions as a JSON value that can be added to an object or an + * array. + */ +extern JsonValue * JsonValueBoolean(int); + +/** + * Unwrap a JSON value that represents a boolean. This function will + * return 0 if the value is not actually a boolean, which may be + * misleading. Check the type of the value before making assumptions + * about its type. + */ +extern int JsonValueAsBoolean(JsonValue *); + +/** + * This is a special case that represents a JSON null. Because the + * Array and HashMap APIs do not accept NULL values, this function + * should be used to represent NULL in JSON. Even though a small + * amount of memory is allocated just to be a placeholder for nothing, + * this keeps the APIs clean. + */ +extern JsonValue * JsonValueNull(void); + +/** + * Free the memory being used by a JSON value. Note that this will + * recursively free all Arrays, HashMaps, and other JsonValues that are + * reachable from the given value, including any strings attached to + * this value. + */ +extern void JsonValueFree(JsonValue *); + +/** + * Recursively duplicate the given JSON value. This returns a new + * JSON value that is completely identical to the specified value, but + * in no way connected to it. + */ +extern JsonValue * JsonValueDuplicate(JsonValue *); + +/** + * Recursively duplicate the given JSON object. This returns a new + * JSON object that is completely identical to the specified object, + * but in no way connect to it. + */ +extern HashMap * JsonDuplicate(HashMap *); + +/** + * Recursively free a JSON object by iterating over all of its values + * and freeing them using + * .Fn JsonValueFree . + */ +extern void JsonFree(HashMap *); + +/** + * Encode the given string in such a way that it can be safely + * embedded in a JSON stream. This entails: + * .Bl -bullet -offset indent + * .It + * Escaping quotes, backslashes, and other special characters using + * their backslash escape. + * .It + * Encoding bytes that are not UTF-8 using escapes. + * .It + * Wrapping the entire string in double quotes. + * .El + * .Pp + * This function is only provided via the public + * .Nm + * API so that it is accessible to custom JSON encoders, such as the + * CanonicalJson encoder. This will typically be used for encoding + * object keys; to encode values, just use + * .Fn JsonEncodeValue . + * .Pp + * This function returns the number of bytes written to the stream, + * or if the stream is NULL, the number of bytes that would have + * been written. + */ +extern int JsonEncodeString(const char *, Stream *); + +/** + * Serialize a JSON value as it would appear in JSON output. This is + * a recursive function that also encodes all child values reachable + * from the given value. This function is exposed via the public + * .Nm + * API so that it is accessible to custom JSON encoders. Normal users + * that are not writing custom encoders should in most cases just use + * .Fn JsonEncode + * to encode an entire object. + * .Pp + * The third parameter is an integer that represents the indent level + * of the value to be printed, or a negative number if pretty-printing + * should be disabled and JSON should be printed as minimized as + * possible. To pretty-print a JSON object, set this to + * .Va JSON_PRETTY . + * To get minified output, set it to + * .Va JSON_DEFAULT . + * .Pp + * This function returns the number of bytes written to the stream, + * or if the stream is NULL, the number of bytes that would have + * been written. + */ +extern int JsonEncodeValue(JsonValue *, Stream *, int); + +/** + * Encode a JSON object as it would appear in JSON output, writing it + * to the given output stream. This function is recursive; it will + * serialize everything accessible from the passed object. The third + * parameter has the same behavior as described above. + * .Pp + * This function returns the number of bytes written to the stream, + * or if the stream is NULL, the number of bytes that would have + * been written. + */ +extern int JsonEncode(HashMap *, Stream *, int); + +/** + * Decode a JSON object from the given input stream and parse it into + * a hash map of JSON values. + */ +extern HashMap * JsonDecode(Stream *); + +/** + * A convenience function that allows the caller to retrieve and + * arbitrarily deep keys within a JSON object. It takes a root JSON + * object, the number of levels deep to go, and then that number of + * keys as a varargs list. All keys must have objects as values, with + * the exception of the last one, which is the value that will be + * returned. Otherwise, NULL indicates the specified path doas not + * exist. + */ +extern JsonValue * JsonGet(HashMap *, size_t,...); + +/** + * A convenience function that allows the caller to set arbitrarily + * deep keys within a JSON object. It takes a root JSON object, the + * number of levels deep to go, and then that number of keys as a + * varargs list. All keys must have object as values, with the + * exception of the last one, which is the value that will be set. + * The value currently at that key, if any, will be returned. + * This function will create any intermediate objects as necessary to + * set the proper key. + */ +extern JsonValue * JsonSet(HashMap *, JsonValue *, size_t,...); + +#endif /* CYTOPLASM_JSON_H */ diff --git a/src/include/Log.h b/src/include/Log.h new file mode 100644 index 0000000..d0300ca --- /dev/null +++ b/src/include/Log.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_LOG_H +#define CYTOPLASM_LOG_H + +/*** + * @Nm Log + * @Nd A simple logging framework for logging to multiple destinations. + * @Dd April 27 2023 + * @Xr Stream + * + * .Nm + * is a simple C logging library that allows for colorful outputs, + * timestamps, and custom log levels. It also features the ability to + * have multiple logs open at one time, although Cytoplasm primarily + * utilizes the global log. All logs are thread safe. + */ + +#include +#include +#include + +#include + +#define LOG_FLAG_COLOR (1 << 0) +#define LOG_FLAG_SYSLOG (1 << 1) + +/** + * A log is defined as a configuration that describes the properties + * of the log. This opaque structure can be manipulated by the + * functions defined in this API. + */ +typedef struct LogConfig LogConfig; + +/** + * Create a new log configuration with sane defaults that can be used + * immediately with the logging functions. + */ +extern LogConfig * LogConfigCreate(void); + +/** + * Get the global log configuration, creating a new one with + * .Fn LogConfigCreate + * if necessary. + */ +extern LogConfig * LogConfigGlobal(void); + +/** + * Free the given log configuration. Note that this does not close the + * underlying stream associated with the log, if there is one. Also + * note that to avoid memory leaks, the global log configuration must + * also be freed, but it cannot be used after it is freed. + */ +extern void LogConfigFree(LogConfig *); + +/** + * Set the current log level on the specified log configuration. + * This indicates that only messages at or above this level should be + * logged; all others are silently discarded. The passed log level + * should be one of the log levels defined by + * .Xr syslog 3 . + * Refer to that page for a complete list of acceptable log levels, + * and note that passing an invalid log level will result in undefined + * behavior. + */ +extern void LogConfigLevelSet(LogConfig *, int); + +/** + * Cause the log output to be indented two more spaces than it was + * previously. This can be helpful when generating stack traces or + * other hierarchical output. This is a simple convenience wrapper + * around + * .Fn LogConfigIndentSet . + */ +extern void LogConfigIndent(LogConfig *); + +/** + * Cause the log output to be indented two less spaces than it was + * previously. This is a simple convenience wrapper around + * .Fn LogConfigIndentSet . + */ +extern void LogConfigUnindent(LogConfig *); + +/** + * Indent future log output using the specified config by some + * arbitrary amount. + */ +extern void LogConfigIndentSet(LogConfig *, size_t); + +/** + * Set the file stream that logging output should be written to. This + * defaults to standard output, but it can be set to standard error, + * or any other arbitrary stream. Passing a NULL value for the stream + * pointer sets the log output to the standard output. Note that the + * output stream is only used if + * .Va LOG_FLAG_SYSLOG + * is not set. + */ +extern void LogConfigOutputSet(LogConfig *, Stream *); + +/** + * Set a number of boolean options on a log configuration. This + * function uses bitwise operators, so multiple options can be set with + * a single function call using bitwise OR operators. The flags are + * defined as preprocessor macros, and are as follows: + * .Bl -tag -width Ds + * .It LOG_FLAG_COLOR + * When set, enable color-coded output on TTYs. Note that colors are + * implemented as ANSI escape sequences, and are not written to file + * streams that are not actually connected to a TTY, to prevent those + * sequences from being written to a file. + * .Xr isatty 3 + * is checked before writing any terminal sequences. + * .It LOG_FLAG_SYSLOG + * When set, log output to the syslog using + * .Xr syslog 3 , + * instead of logging to the file set by + * .Fn LogConfigOutputSet . + * This flag always overrides the stream set by that function, + * regardless of when it was set, even if it was set after this flag + * was set. + * .El + */ +extern void LogConfigFlagSet(LogConfig *, int); + +/** + * Clear a boolean flag from the specified log format. See above for + * the list of flags. + */ +extern void LogConfigFlagClear(LogConfig *, int); + +/** + * Set a custom timestamp to be prepended to each message if the + * output is not going to the system log. Consult your system's + * documentation for + * .Xr strftime 3 . + * A value of NULL disables the timestamp output before messages. + */ +extern void LogConfigTimeStampFormatSet(LogConfig *, char *); + +/** + * This function does the actual logging of messages using a + * specified configuration. It takes the configuration, the log + * level, a format string, and then a list of arguments, all in that + * order. This function only logs messages if their level is above + * or equal to the currently configured log level, making it easy to + * turn some messages on or off. + * .Pp + * This function has the same usage as + * .Xr vprintf 3 . + * Consult that page for the list of format specifiers and their + * arguments. This function is typically not used directly, see the + * other log functions for the most common use cases. + */ +extern void Logv(LogConfig *, int, const char *, va_list); + +/** + * Log a message using + * .Fn Logv . + * with the specified configuration. This function has the same usage + * as + * .Xr printf 3 . + */ +extern void LogTo(LogConfig *, int, const char *, ...); + +/** + * Log a message to the global log using + * .Fn Logv . + * This function has the same usage as + * .Xr printf 3 . + */ +extern void Log(int, const char *, ...); + +#endif diff --git a/src/include/Memory.h b/src/include/Memory.h new file mode 100644 index 0000000..5d5ecec --- /dev/null +++ b/src/include/Memory.h @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_MEMORY_H +#define CYTOPLASM_MEMORY_H + +/*** + * @Nm Memory + * @Nd Smart memory management. + * @Dd January 9 2023 + * + * .Nm + * is an API that allows for smart memory management and profiling. It + * wraps the standard library functions + * .Xr malloc 3 , + * .Xr realloc 3 , + * and + * .Xr free 3 , + * and offers identical semantics, while providing functionality that + * the standard library doesn't have, such as getting statistics on the + * total memory allocated on the heap, and getting the size of a block + * given a pointer. Additionally, thanks to preprocessor macros, the + * exact file and line number at which an allocation, re-allocation, or + * free occured can be obtained given a pointer. Finally, all the + * blocks allocated on the heap can be iterated and evaluated, and a + * callback function can be executed every time a memory operation + * occurs. + * .Pp + * In the future, this API could include a garbage collector that + * automatically frees memory it detects as being no longer in use. + * However, this feature does not yet exist. + * .Pp + * A number of macros are available, which make the + * .Nm + * API much easier to use. They are as follows: + * .Bl -bullet -offset indent + * .It + * .Fn Malloc "x" + * .It + * .Fn Realloc "x" "y" + * .It + * .Fn Free "x" + * .El + * .Pp + * These macros expand to + * .Fn MemoryAllocate , + * .Fn MemoryReallocate , + * and + * .Fn MemoryFree + * with the second and third parameters set to __FILE__ and __LINE__. + * This allows + * .Nm + * to be used exactly how the standard library functions would be + * used. In fact, the functions to which these macros expand are not + * intended to be used directly; for the best results, use these + * macros. + */ +#include + +/** + * These values are passed into the memory hook function to indicate + * the action that just happened. + */ +typedef enum MemoryAction +{ + MEMORY_ALLOCATE, + MEMORY_REALLOCATE, + MEMORY_FREE, + MEMORY_BAD_POINTER, + MEMORY_CORRUPTED +} MemoryAction; + +#define Malloc(x) MemoryAllocate(x, __FILE__, __LINE__) +#define Realloc(x, s) MemoryReallocate(x, s, __FILE__, __LINE__) +#define Free(x) MemoryFree(x, __FILE__, __LINE__) + +/** + * The memory information is opaque, but can be accessed using the + * functions defined by this API. + */ +typedef struct MemoryInfo MemoryInfo; + +/** + * Allocate the specified number of bytes on the heap. This function + * has the same semantics as + * .Xr malloc 3 , + * except that it takes the file name and line number at which the + * allocation occurred. + */ +extern void * MemoryAllocate(size_t, const char *, int); + +/** + * Change the size of the object pointed to by the given pointer + * to the given number of bytes. This function has the same semantics + * as + * .Xr realloc 3 , + * except that it takes the file name and line number at which the + * reallocation occurred. + */ +extern void * MemoryReallocate(void *, size_t, const char *, int); + +/** + * Free the memory at the given pointer. This function has the same + * semantics as + * .Xr free 3 , + * except that it takes the file name and line number at which the + * free occurred. + */ +extern void MemoryFree(void *, const char *, int); + +/** + * Get the total number of bytes that the program has allocated on the + * heap. This operation iterates over all heap allocations made with + * .Fn MemoryAllocate + * and then returns a total count, in bytes. + */ +extern size_t MemoryAllocated(void); + +/** + * Iterate over all heap allocations made with + * .Fn MemoryAllocate + * and call + * .Fn MemoryFree + * on them. This function immediately invalidates all pointers to + * blocks on the heap, and any subsequent attempt to read or write to + * data on the heap will result in undefined behavior. This is + * typically called at the end of the program, just before exit. + */ +extern void MemoryFreeAll(void); + +/** + * Fetch information about an allocation. This function takes a raw + * pointer, and if + * . Nm + * knows about the pointer, it returns a structure that can be used + * to obtain information about the block of memory that the pointer + * points to. + */ +extern MemoryInfo * MemoryInfoGet(void *); + +/** + * Get the size in bytes of the block of memory represented by the + * specified memory info structure. + */ +extern size_t MemoryInfoGetSize(MemoryInfo *); + +/** + * Get the file name in which the block of memory represented by the + * specified memory info structure was allocated. + */ +extern const char * MemoryInfoGetFile(MemoryInfo *); + +/** + * Get the line number on which the block of memory represented by the + * specified memory info structure was allocated. + */ +extern int MemoryInfoGetLine(MemoryInfo *); + +/** + * Get a pointer to the block of memory represented by the specified + * memory info structure. + */ +extern void * MemoryInfoGetPointer(MemoryInfo *); + +/** + * This function takes a pointer to a function that takes the memory + * info structure, as well as a void pointer for caller-provided + * arguments. It iterates over all the heap memory currently allocated + * at the time of calling, executing the function on each allocation. + */ +extern void MemoryIterate(void (*) (MemoryInfo *, void *), void *); + +/** + * Specify a function to be executed whenever a memory operation + * occurs. The MemoryAction argument specifies the operation that + * occurred on the block of memory represented by the memory info + * structure. The function also takes a void pointer to caller-provided + * arguments. + */ +extern void MemoryHook(void (*) (MemoryAction, MemoryInfo *, void *), void *); + +/** + * The default memory hook, which has sane behavior and is installed + * at runtime. This function does not use any memory on the heap, + * except for the MemoryInfo passed to it, which it assumes to be + * valid. Everything else happens on the stack only, to ensure that + * the hook doesn't make any memory problems worse. + */ +extern void MemoryDefaultHook(MemoryAction, MemoryInfo *, void *); + +/** + * Read over the block of memory represented by the given memory info + * structure and generate a hexadecimal and ASCII string for each + * chunk of the block. This function takes a callback function that + * takes the following parameters in order: + * .Bl -bullet -offset indent + * .It + * The current offset from the beginning of the block of memory in + * bytes. + * .It + * A null-terminated string containing the next 16 bytes of the block + * encoded as space-separated hex values. + * .It + * A null-terminated string containing the ASCII representation of the + * same 16 bytes of memory. This ASCII representation is safe to print + * to a terminal or other text device, because non-printable characters + * are encoded as a . (period). + * .It + * Caller-passed pointer. + * .El + */ +extern void +MemoryHexDump(MemoryInfo *, void (*) (size_t, char *, char *, void *), void *); + +#endif diff --git a/src/include/Queue.h b/src/include/Queue.h new file mode 100644 index 0000000..35821d3 --- /dev/null +++ b/src/include/Queue.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_QUEUE_H +#define CYTOPLASM_QUEUE_H + +/*** + * @Nm Queue + * @Nd A simple static queue data structure. + * @Dd November 25 2022 + * @Xr Array HashMap + * + * .Nm + * implements a simple queue data structure that is statically sized. + * This implementation does not actually store the values of the items + * in it; it only stores pointers to the data. As such, you will have + * to manually maintain data and make sure it remains valid as long as + * it is in the queue. The advantage of this is that + * .Nm + * doesn't have to copy data, and thus doesn't care how big the data + * is. Furthermore, any arbitrary data can be stored in the queue. + * .Pp + * This queue implementation operates on the heap. It is a circular + * queue, and it does not grow as it is used. Once the size is set, + * the queue never gets any bigger. + */ + +#include + +/** + * These functions operate on a queue structure that is opaque to the + * caller. + */ +typedef struct Queue Queue; + +/** + * Allocate a new queue that is able to store the specified number of + * items in it. + */ +extern Queue * QueueCreate(size_t); + +/** + * Free the memory associated with the specified queue structure. Note + * that this function does not free any of the values stored in the + * queue; it is the caller's job to manage memory for each item. + * Typically, the caller would dequeue all the items in the queue and + * deal with them before freeing the queue itself. + */ +extern void QueueFree(Queue *); + +/** + * Push an element into the queue. This function returns a boolean + * value indicating whether or not the push succeeded. Pushing items + * into the queue will fail if the queue is full. + */ +extern int QueuePush(Queue *, void *); + +/** + * Pop an element out of the queue. This function returns NULL if the + * queue is empty. Otherwise, it returns a pointer to the item that is + * next up in the queue. + */ +extern void * QueuePop(Queue *); + +/** + * Retrieve a pointer to the item that is next up in the queue without + * actually discarding it, such that the next call to + * .Fn QueuePeek + * or + * .Fn QueuePop + * will return the same pointer. + */ +extern void * QueuePeek(Queue *); + +/** + * Determine whether or not the queue is full. + */ +extern int QueueFull(Queue *); + +/** + * Determine whether or not the queue is empty. + */ +extern int QueueEmpty(Queue *); + +#endif diff --git a/src/include/Rand.h b/src/include/Rand.h new file mode 100644 index 0000000..a5be3ce --- /dev/null +++ b/src/include/Rand.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_RAND_H +#define CYTOPLASM_RAND_H + +/*** + * @Nm Rand + * @Nd Thread-safe random numbers. + * @Dd February 16 2023 + * @Xr Util + * + * .Nm + * is used for generating random numbers in a thread-safe way. + * Currently, one generator state is shared across all threads, which + * means that only one thread can generate random numbers at a time. + * This state is protected with a mutex to guarantee this behavior. + * In the future, a seed pool may be maintained to allow multiple + * threads to generate random numbers at the same time. + * .Pp + * The generator state is seeded on the first call to a function that + * needs it. The seed is determined by the current timestamp, the ID + * of the process, and the thread ID. These should all be sufficiently + * random sources, so the seed should be secure enough. + * .Pp + * .Nm + * currently uses a simple Mersenne Twister algorithm to generate + * random numbers. This algorithm was chosen because it is extremely + * popular and widespread. While it is likely not cryptographically + * secure, and does suffer some unfortunate pitfalls, this algorithm + * has stood the test of time and is simple enough to implement, so + * it was chosen over the alternatives. + * .Pp + * .Nm + * does not use any random number generator functions from the C + * standard library, since these are often flawed. + */ + +#include + +/** + * Generate a single random integer between 0 and the passed value. + */ +extern int RandInt(unsigned int); + +/** + * Generate the number of integers specified by the second argument + * storing them into the buffer pointed to in the first argument. + * Ensure that each number is between 0 and the third argument. + * .Pp + * This function allows a caller to get multiple random numbers at once + * in a more efficient manner than repeatedly calling + * .Fn RandInt , + * since each call to these functions + * has to lock and unlock a mutex. It is therefore better to obtain + * multiple random numbers in one pass if multiple are needed. + */ +extern void RandIntN(int *, size_t, unsigned int); + +#endif /* CYTOPLASM_RAND_H */ diff --git a/src/include/Runtime.h b/src/include/Runtime.h new file mode 100644 index 0000000..6e13b6a --- /dev/null +++ b/src/include/Runtime.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_RUNTIME_H +#define CYTOPLASM_RUNTIME_H + +/*** + * @Nm Runtime + * @Nd Supporting functions for the Cytoplasm runtime. + * @Dd May 23 2023 + * @Xr Memory + * + * .Nm + * provides supporting functions for the Cytoplasm runtime. These + * functions are not intended to be called directly by programs, + * but are used internally. They're exposed via a header because + * the runtime stub needs to know their definitions. + */ + +#include + +/** + * Write a memory report to a file in the current directory, using + * the provided program arguments, including the program name that + * executed. This function is to be called after all memory is + * supposed to have been freed. It iterates over all remaining + * memory and generates a text file containing all of the + * recorded information about each block, including a hex dump of + * the data stored in them. + */ +extern void GenerateMemoryReport(int argc, char **argv); + +#endif /* CYTOPLASM_RUNTIME_H */ diff --git a/src/include/Sha.h b/src/include/Sha.h new file mode 100644 index 0000000..25b6247 --- /dev/null +++ b/src/include/Sha.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_SHA_H +#define CYTOPLASM_SHA_H + +/*** + * @Nm Sha + * @Nd A simple implementation of a few SHA hashing functions. + * @Dd December 19 2022 + * @Xr Memory Base64 + * + * This API defines simple functions for computing SHA hashes. + * At the moment, it only defines + * .Fn Sha256 + * and + * .Fn Sha1 , + * which compute the SHA-256 and SHA-1 hashes of the given C string, + * respectively. It is not trivial to implement SHA-512 in ANSI C + * due to the lack of a 64-bit integer type, so that hash + * function has been omitted. + */ + +/** + * This function takes a pointer to a NULL-terminated C string, and + * returns a NULL-terminated byte buffer allocated on the heap using + * the Memory API, or NULL if there was an error allocating memory. + * The returned byte buffer should be freed when it is no longer + * needed. It is important to note that the returned buffer is not + * a printable string; to get a printable string, use + * .Fn ShaToHex . + */ +extern unsigned char * Sha256(char *); + +/** + * This function takes a pointer to a NULL-terminated C string, and + * returns a NULL-terminated byte buffer allocated on the heap using + * the Memory API, or NULL if there was an error allocating memory. + * The returned byte buffer should be freed when it is no longer + * needed. It is important to note that the returned buffer is not + * a printable string; to get a printable string, use + * .Fn ShaToHex . + */ +extern unsigned char * Sha1(char *); + +/** + * Convert a SHA byte buffer into a hex string. These hex strings + * are typically what is transmitted, stored, and compared, however + * there may be times when it is necessary to work with the raw + * bytes directly, which is why the conversion to a hex string is + * a separate step. + */ +extern char * ShaToHex(unsigned char *); + +#endif /* CYTOPLASM_SHA_H */ diff --git a/src/include/Str.h b/src/include/Str.h new file mode 100644 index 0000000..714b8d8 --- /dev/null +++ b/src/include/Str.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_STR_H +#define CYTOPLASM_STR_H + +/*** + * @Nm Str + * @Nd Functions for creating and manipulating strings. + * @Dd February 15 2023 + * @Xr Memory + * + * .Nm + * provides string-related functions. It is called + * .Nm , + * not String, because some platforms (Windows) do not have + * case-sensitive filesystems, which poses a problem since + * .Pa string.h + * is a standard library header. + */ + +#include + +#include + +/** + * Convert UTF-16 into a Unicode codepoint. + */ +extern UInt32 StrUtf16Decode(UInt16, UInt16); + +/** + * Take a Unicode codepoint and encode it into a string buffer containing + * between 1 and 4 bytes. The string buffer is allocated on the heap, + * so it should be freed when it is no longer needed. + */ +extern char * StrUtf8Encode(UInt32); + +/** + * Duplicate a null-terminated string, returning a new string on the + * heap. This is useful when a function takes in a string that it needs + * to store for long amounts of time, even perhaps after the original + * string is gone. + */ +extern char * StrDuplicate(const char *); + +/** + * Extract part of a null-terminated string, returning a new string on + * the heap containing only the requested subsection. Like the + * substring functions included with most programming languages, the + * starting index is inclusive, and the ending index is exclusive. + */ +extern char * StrSubstr(const char *, size_t, size_t); + +/** + * A varargs function that takes a number of null-terminated strings + * specified by the first argument, and returns a new string that + * contains their concatenation. It works similarly to + * .Xr strcat 3 , + * but it takes care of allocating memory big enough to hold all the + * strings. Any string in the list may be NULL. If a NULL pointer is + * passed, it is treated like an empty string. + */ +extern char * StrConcat(size_t,...); + +/** + * Return a boolean value indicating whether or not the null-terminated + * string consists only of blank characters, as determined by + * .Xr isblank 3 . + */ +extern int StrBlank(const char *str); + +/** + * Generate a string of the specified length, containing random + * lowercase and uppercase letters. + */ +extern char * StrRandom(size_t); + +/** + * Convert the specified integer into a string, returning the string + * on the heap, or NULL if there was a memory allocation error. The + * returned string should be freed by the caller after it is no longer + * needed. + */ +extern char * StrInt(long); + +/** + * Converts a string into a lowercase version of it using + * .Xr tolower 3 , + * returning the lowercase version on the heap, or NULL if there was + * a memory allocation error. The returned string should be freed by + * the caller after it is no longer needed. + */ +extern char * StrLower(char *); + +/** + * Compare two strings and determine whether or not they are equal. + * This is the most common use case of strcmp() in Cytoplasm, but + * strcmp() doesn't like NULL pointers, so these have to be checked + * explicitly and can cause problems if they aren't. This function, + * on the other hand, makes NULL pointers special cases. If both + * arguments are NULL, then they are considered equal. If only one + * argument is NULL, they are considered not equal. Otherwise, if + * no arguments are NULL, a regular strcmp() takes place and this + * function returns a boolean value indicating whether or not + * strcmp() returned 0. + */ +extern int StrEquals(const char *, const char *); + +#endif /* CYTOPLASM_STR_H */ diff --git a/src/include/Stream.h b/src/include/Stream.h new file mode 100644 index 0000000..3ddacc6 --- /dev/null +++ b/src/include/Stream.h @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_STREAM_H +#define CYTOPLASM_STREAM_H + +/*** + * @Nm Stream + * @Nd An abstraction over the Io API that implements standard C I/O. + * @Dd April 29 2023 + * @Xr Io + * + * .Nm + * implements an abstraction layer over the Io API. This layer buffers + * I/O and makes it much easier to work with, mimicking the standard + * C library and offering some more convenience features. + */ + +#include + +#include + +/** + * An opaque structure analogous to C's FILE pointers. + */ +typedef struct Stream Stream; + +/** + * Create a new stream using the specified Io for underlying I/O + * operations. + */ +extern Stream * StreamIo(Io * io); + +/** + * Create a new stream using the specified POSIX file descriptor. + * This is a convenience function for calling + * .Fn IoFd + * and then passing the result into + * .Fn StreamIo . + */ +extern Stream * StreamFd(int); + +/** + * Create a new stream using the specified C FILE pointer. This is a + * convenience function for calling + * .Fn IoFile + * and then passing the result into + * .Fn StreamIo . + */ +extern Stream * StreamFile(FILE *); + +/** + * Create a new stream using the specified path and mode. This is a + * convenience function for calling + * .Xr fopen 3 + * and then passing the result into + * .Fn StreamFile . + */ +extern Stream * StreamOpen(const char *, const char *); + +/** + * Get a stream that writes to the standard output. + */ +extern Stream * StreamStdout(void); + +/** + * Get a stream that writes to the standard error. + */ +extern Stream * StreamStderr(void); + +/** + * Get a stream that reads from the standard input. + */ +extern Stream * StreamStdin(void); + +/** + * Close the stream. This flushes the buffers and closes the underlying + * Io. It is analogous to the standard + * .Xr fclose 3 + * function. + */ +extern int StreamClose(Stream *); + +/** + * Print a formatted string. This function is analogous to the standard + * .Xr vfprintf 3 + * function. + */ +extern int StreamVprintf(Stream *, const char *, va_list); + +/** + * Print a formatted string. This function is analogous to the + * standard + * .Xr fprintf 3 + * function. + */ +extern int StreamPrintf(Stream *, const char *,...); + +/** + * Get a single character from a stream. This function is analogous to + * the standard + * .Xr fgetc 3 + * function. + */ +extern int StreamGetc(Stream *); + +/** + * Push a character back onto the input stream. This function is + * analogous to the standard + * .Xr ungetc 3 + * function. + */ +extern int StreamUngetc(Stream *, int); + +/** + * Write a single character to the stream. This function is analogous + * to the standard + * .Xr fputc 3 + * function. + */ +extern int StreamPutc(Stream *, int); + +/** + * Write a null-terminated string to the stream. This function is + * analogous to the standard + * .Xr fputs 3 + * function. + */ +extern int StreamPuts(Stream *, char *); + +/** + * Read at most the specified number of characters minus 1 from the + * specified stream and store them at the memory located at the + * specified pointer. This function is analogous to the standard + * .Xr fgets 3 + * function. + */ +extern char * StreamGets(Stream *, char *, int); + +/** + * Set the file position indicator for the specified stream. This + * function is analogous to the standard + * .Xr fseeko + * function. + */ +extern off_t StreamSeek(Stream *, off_t, int); + +/** + * Test the end-of-file indicator for the given stream, returning a + * boolean value indicating whether or not it is set. This is analogous + * to the standard + * .Xr feof 3 + * function. + */ +extern int StreamEof(Stream *); + +/** + * Test the stream for an error condition, returning a boolean value + * indicating whether or not one is present. This is analogous to the + * standard + * .Xr ferror 3 + * function. + */ +extern int StreamError(Stream *); + +/** + * Clear the error condition associated with the given stream, allowing + * future reads or writes to potentially be successful. This functio + * is analogous to the standard + * .Xr clearerr 3 + * function. + */ +extern void StreamClearError(Stream *); + +/** + * Flush all buffered data using the streams underlying write function. + * This function is analogous to the standard + * .Xr fflush 3 + * function. + */ +extern int StreamFlush(Stream *); + +/** + * Read all the bytes from the first stream and write them to the + * second stream. This is analogous to + * .Fn IoCopy , + * but it uses the internal buffers of the streams. It is probably + * less efficient than doing a + * .Fn IoCopy + * instead, but it is more convenient. + */ +extern ssize_t StreamCopy(Stream *, Stream *); + +/** + * Get the file descriptor associated with the given stream, or -1 if + * the stream is not associated with any file descriptor. This function + * is analogous to the standard + * .Xr fileno 3 + * function. + */ +extern int StreamFileno(Stream *); + +#endif /* CYTOPLASM_STREAM_H */ diff --git a/src/include/Tls.h b/src/include/Tls.h new file mode 100644 index 0000000..4407ed0 --- /dev/null +++ b/src/include/Tls.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_TLS_H +#define CYTOPLASM_TLS_H + +/*** + * @Nm Tls + * @Nd Interface to platform-dependent TLS libraries. + * @Dd April 29 2023 + * @Xr Stream Io + * + * .Nm + * provides an interface to platform-dependent TLS libraries. It allows + * Cytoplasm to support any TLS library with no changes to existing + * code. Support for additional TLS libraries is added by creating a + * new compilation unit that implements all the functions here, with + * the exception of a few, which are noted. + * .Pp + * Currently, Cytoplasm has support for the following TLS libraries: + * .Bl -bullet -offset indent + * .It + * LibreSSL + * .It + * OpenSSL + * .El + */ + +#include + +#define TLS_LIBRESSL 2 +#define TLS_OPENSSL 3 + +/** + * Create a new TLS client stream using the given file descriptor and + * the given server hostname. The hostname should be used to verify + * that the server actually is who it says it is. + * .Pp + * This function does not need to be implemented by the individual + * TLS support stubs. + */ +extern Stream * TlsClientStream(int, const char *); + +/** + * Create a new TLS server stream using the given certificate and key + * file, in the format natively supported by the TLS library. + * .Pp + * This function does not need to be implemented by the individual + * TLS support stubs. + */ +extern Stream * TlsServerStream(int, const char *, const char *); + +/** + * Initialize a cookie that stores information about the given client + * connection. This cookie will be passed into the other functions + * defined by this API. + */ +extern void * TlsInitClient(int, const char *); + +/** + * Initialize a cookie that stores information about the given + * server connection. This cookie will be passed into the other + * functions defined by this API. + */ +extern void * TlsInitServer(int, const char *, const char *); + +/** + * Read from a TLS stream, decrypting it and storing the result in the + * specified buffer. This function takes the cookie, buffer, and + * number of decrypted bytes to read into it. See the documentation for + * .Fn IoRead . + */ +extern ssize_t TlsRead(void *, void *, size_t); + +/** + * Write to a TLS stream, encrypting the buffer. This function takes + * the cookie, buffer, and number of unencrypted bytes to write to + * the stream. See the documentation for + * .Fn IoWrite . + */ +extern ssize_t TlsWrite(void *, void *, size_t); + +/** + * Close the TLS stream, also freeing all memory associated with the + * cookie. + */ +extern int TlsClose(void *); + +#endif /* CYTOPLASM_TLS_H */ diff --git a/src/include/UInt64.h b/src/include/UInt64.h new file mode 100644 index 0000000..51cc30c --- /dev/null +++ b/src/include/UInt64.h @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_UINT64_H +#define CYTOPLASM_UINT64_H + +/*** + * @Nm UInt64 + * @Nd Fixed-width 64 bit integers. + * @Dd August 11, 2023 + * + * .Pp + * ANSI C89 (or C99 for that matter) provides no required mechanism + * for 64 bit integers. Nevertheless, many compilers provide them as + * extensions. However, since it is not a gaurantee, and to be fully + * standards-compliant and thus portable, a platform-agnostic interface + * is required. This header provides such an interface. If the platform + * has a 64 bit integer type, that is used, and native operations are + * performed by C preprocessor macro expansion. Otherwise, a + * compatibility layer is provided, which implements 64-bit + * arithmetic on an array of 2 32-bit numbers which are provided by + * .Xr Int 3 . + * .Pp + * Note that 64-bit emulation is certainly not as performant as using + * native 64-bit operations, so whenever possible, the native + * operations should be preferred. However, since C provides no required + * 64 bit integer on 32-bit and less platforms, this API can be used as + * a "good enough" fallback mechanism. + * .Pp + * Also note that this implementation, both in the native and + * non-native forms, makes some assumptions: + * .Bl -bullet -width Ds + * .It + * When a cast from a larger integer to a smaller integer is performed, + * the upper bits are truncated, not the lower bits. + * .It + * Negative numbers are represented in memory and in registers in two's + * compliment form. + * .El + * .Pp + * This API may provide unexpected output if these assumptions are + * false for a given platform. + * + * @ignore-typedefs + */ + +#include + +#include + +#ifndef INT64_FORCE_EMULATED + +#define BIT64_MAX 18446744073709551615UL + +#if UINT_MAX == BIT64_MAX +/* typedef signed int Int64; */ +typedef unsigned int UInt64; + +#define UINT64_NATIVE + +#elif ULONG_MAX == BIT64_MAX +/* typedef signed int Int64; */ +typedef unsigned long UInt64; + +#define UINT64_NATIVE + +#endif + +#endif /* ifndef INT64_FORCE_EMULATED */ + +#ifdef UINT64_NATIVE + +#define UInt64Create(high, low) (((UInt64) (high) << 32) | (low)) +#define UInt64Low(a) ((UInt32) ((a) & 0x00000000FFFFFFFF)) +#define UInt64High(a) ((UInt32) ((a) >> 32)) + +#define UInt64Add(a, b) ((a) + (b)) +#define UInt64Sub(a, b) ((a) - (b)) +#define UInt64Mul(a, b) ((a) * (b)) +#define UInt64Div(a, b) ((a) / (b)) +#define UInt64Rem(a, b) ((a) % (b)) + +#define UInt64Sll(a, b) ((a) << (b)) +#define UInt64Srl(a, b) ((a) >> (b)) + +#define UInt64And(a, b) ((a) & (b)) +#define UInt64Or(a, b) ((a) | (b)) +#define UInt64Xor(a, b) ((a) ^ (b)) +#define UInt64Not(a) (~(a)) + +#define UInt64Eq(a, b) ((a) == (b)) +#define UInt64Lt(a, b) ((a) < (b)) +#define UInt64Gt(a, b) ((a) > (b)) + +#define UInt64Neq(a, b) ((a) != (b)) +#define UInt64Leq(a, b) ((a) <= (b)) +#define UInt64Geq(a, b) ((a) >= (b)) + +#else + +/** + * For platforms that do not have a native integer large enough to + * store a 64 bit integer, this struct is used. i[0] contains the low + * bits of integer, and i[1] contains the high bits of the integer. + * .Pp + * This struct should not be accessed directly, because UInt64 may not + * actually be this struct, it might be an actual integer type. For + * maximum portability, only use the functions defined here to + * manipulate 64 bit integers. + */ +typedef struct +{ + UInt32 i[2]; +} UInt64; + +/** + * Create a new unsigned 64 bit integer using the given high and low + * bits. + */ +extern UInt64 UInt64Create(UInt32, UInt32); + +/** + * Add two unsigned 64 bit integers together. + */ +extern UInt64 UInt64Add(UInt64, UInt64); + +/** + * Subtract the second 64 bit integer from the first. + */ +extern UInt64 UInt64Sub(UInt64, UInt64); + +/** + * Multiply two 64 bit integers together. The non-native version of + * this function uses the Russian Peasant method of multiplication, + * which should afford more performance than a naive multiplication by + * addition, but it is still rather slow and depends on the size of + * the integers being multiplied. + */ +extern UInt64 UInt64Mul(UInt64, UInt64); + +/** + * Divide the first 64 bit integer by the second and return the + * quotient. The non-native version of this function uses naive binary + * long division, which is slow, but gauranteed to finish in constant + * time. + */ +extern UInt64 UInt64Div(UInt64, UInt64); + +/** + * Divide the first 64 bit integer by the second and return the + * remainder. The non-native version of this function uses naive binary + * long division, which is slow, but gauranteed to finish in constant + * time. + */ +extern UInt64 UInt64Rem(UInt64, UInt64); + +/** + * Perform a left logical bit shift of a 64 bit integer. The second + * parameter is how many places to shift, and is declared as a regular + * integer because anything more than 64 does not make sense. + */ +extern UInt64 UInt64Sll(UInt64, int); + +/** + * Perform a right logical bit shift of a 64 bit integer. The second + * parameter is how many places to shift, and is declared as a regular + * integer because anything more than 64 does not make sense. + */ +extern UInt64 UInt64Srl(UInt64, int); + +/** + * Perform a bitwise AND (&) of the provided 64 bit integers. + */ +extern UInt64 UInt64And(UInt64, UInt64); + +/** + * Perform a bitwise OR (|) of the provided 64 bit integers. + */ +extern UInt64 UInt64Or(UInt64, UInt64); + +/** + * Perform a bitwise XOR (^) of the provided 64 bit integers. + */ +extern UInt64 UInt64Xor(UInt64, UInt64); + +/** + * Perform a bitwise NOT (~) of the provided 64 bit integer. + */ +extern UInt64 UInt64Not(UInt64); + +/** + * Perform a comparison of the provided 64 bit integers and return a C + * boolean that is true if and only if they are equal. + */ +extern int UInt64Eq(UInt64, UInt64); + +/** + * Perform a comparison of the provided 64 bit integers and return a C + * boolean that is true if and only if the second operand is strictly + * less than the first. + */ +extern int UInt64Lt(UInt64, UInt64); + +/** + * Perform a comparison of the provided 64 bit integers and return a C + * boolean that is true if and only if the second operand is strictly + * greater than the first. + */ +extern int UInt64Gt(UInt64, UInt64); + +#define UInt64Low(a) ((a).i[0]) +#define UInt64High(a) ((a).i[1]) + +#define UInt64Neq(a, b) (!UInt64Eq(a, b)) +#define UInt64Leq(a, b) (UInt64Eq(a, b) || UInt64Lt(a, b)) +#define UInt64Geq(a, b) (UInt64Eq(a, b) || UInt64Gt(a, b)) + +#endif + +#define UINT64_STRBUF 65 /* Base 2 representation with '\0' */ + +/** + * Convert a 64 bit integer to a string in an arbitrary base + * representation specified by the second parameter, using the provided + * buffer and length specified by the third and fourth parameters. To + * guarantee that the string will fit in the buffer, allocate it of + * size UINT64_STRBUF or larger. Note that a buffer size smaller than + * UINT64_STRBUF will invoke undefined behavior. + */ +extern size_t UInt64Str(UInt64, int, char *, size_t); + +#endif /* CYTOPLASM_UINT64_H */ diff --git a/src/include/Uri.h b/src/include/Uri.h new file mode 100644 index 0000000..a47bf28 --- /dev/null +++ b/src/include/Uri.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_URI_H +#define CYTOPLASM_URI_H + +/*** + * @Nm Uri + * @Nd Parse a URI. Typically used to parse HTTP(s) URLs. + * @Dd April 29 2023 + * @Xr Http + * + * .Nm + * provides a simple mechanism for parsing URIs. This is an extremely + * basic parser that (ab)uses + * .Xr sscanf 3 + * to parse URIs, so it may not be the most reliable, but it should + * work in most cases and on reasonable URIs that aren't too long, as + * the _MAX definitions are modest. + */ + +#define URI_PROTO_MAX 8 +#define URI_HOST_MAX 128 +#define URI_PATH_MAX 256 + +/** + * The parsed URI is stored in this structure. + */ +typedef struct Uri +{ + char proto[URI_PROTO_MAX]; + char host[URI_HOST_MAX]; + char path[URI_PATH_MAX]; + unsigned short port; +} Uri; + +/** + * Parse a URI string into the Uri structure as described above, or + * return NULL if there was a parsing error. + */ +extern Uri * UriParse(const char *); + +/** + * Free the memory associated with a Uri structure returned by + * .Fn UriParse . + */ +extern void UriFree(Uri *); + +#endif /* CYTOPLASM_URI_H */ diff --git a/src/include/Util.h b/src/include/Util.h new file mode 100644 index 0000000..d6c3830 --- /dev/null +++ b/src/include/Util.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_UTIL_H +#define CYTOPLASM_UTIL_H + +/*** + * @Nm Util + * @Nd Some misc. helper functions that don't need their own headers. + * @Dd February 15 2023 + * + * This header holds a number of random functions related to strings, + * time, the filesystem, and other simple tasks that don't require a + * full separate API. For the most part, the functions here are + * entirely standalone, depending only on POSIX functions, however + * there are a few that depend explicitly on a few other APIs. Those + * are noted. + */ + +#include +#include +#include + +#include +#include + +/** + * Get the current timestamp in milliseconds since the Unix epoch. This + * uses + * .Xr gettimeofday 2 + * and time_t, and converts it to a single number, which is then + * returned to the caller. + * .Pp + * A note on the 2038 problem: as long as sizeof(time_t) >= 8, that is, + * as long as the time_t type is 64 bits or more, then everything + * should be fine. On most, if not, all, 64-bit systems, time_t is 64 + * bits. time_t is promoted to a 64-bit integer before it is converted + * to milliseconds, so there is no risk of overflue due to the + * multiplication by 1000. However, if time_t is only 32 bits, it will + * overflow before it even gets to this function, which will cause this + * function to produce unexpected results. + */ +extern UInt64 UtilServerTs(void); + +/** + * Use + * .Xr stat 2 + * to get the last modified time of the given file, or zero if there + * was an error getting the last modified time of a file. This is + * primarily useful for caching file data. + */ +extern UInt64 UtilLastModified(char *); + +/** + * This function behaves just like the system call + * .Xr mkdir 2 , + * but it creates any intermediate directories as necessary, unlike + * .Xr mkdir 2 . + */ +extern int UtilMkdir(const char *, const mode_t); + +/** + * Sleep the calling thread for the given number of milliseconds. + * POSIX does not have a very friendly way to sleep, so this wraps + * .Xr nanosleep 2 + * to make its usage much, much simpler. + */ +extern int UtilSleepMillis(UInt64); + +/** + * This function works identically to the POSIX + * .Xr getdelim 3 , + * except that it assumes pointers were allocated with the Memory API + * and it reads from a Stream instead of a file pointer. + */ +extern ssize_t UtilGetDelim(char **, size_t *, int, Stream *); + +/** + * This function is just a special case of + * .Fn UtilGetDelim + * that sets the delimiter to the newline character. + */ +extern ssize_t UtilGetLine(char **, size_t *, Stream *); + +/** + * Get a unique number associated with the current thread. + * Numbers are assigned in the order that threads call this + * function, but are guaranteed to be unique in identifying + * the thread in a more human-readable way than just casting + * the return value of + * .Fn pthread_self + * to a number. + */ +extern UInt32 UtilThreadNo(void); + +#endif /* CYTOPLASM_UTIL_H */ diff --git a/tools/int64.c b/tools/int64.c new file mode 100644 index 0000000..fb9c5cf --- /dev/null +++ b/tools/int64.c @@ -0,0 +1,145 @@ +#include + +#include + +/* AssertEquals(actual, expected) */ +int +AssertEquals(char *msg, Int64 x, Int64 y) +{ + if (!Int64Eq(x, y)) + { + Log(LOG_ERR, "%s: Expected 0x%X 0x%X, got 0x%X 0x%X", msg, + Int64High(y), Int64Low(y), + Int64High(x), Int64Low(x)); + + return 0; + } + + return 1; +} + +int +Main(void) +{ + Int64 x, y; + + Log(LOG_INFO, "sizeof(Int64) = %lu", sizeof(Int64)); + +#ifdef INT64_NATIVE + Log(LOG_INFO, "Using native 64-bit integers."); +#else + Log(LOG_INFO, "Using emulated 64-bit integers."); +#endif + + /* BSR Tests */ + + x = Int64Create(0x000000FF, 0x00000000); + + y = Int64Sra(x, 4); + AssertEquals("x >> 4", y, Int64Create(0x0000000F, 0xF0000000)); + + y = Int64Sra(x, 8); + AssertEquals("x >> 8", y, Int64Create(0x00000000, 0xFF000000)); + + y = Int64Sra(x, 36); + AssertEquals("x >> 36", y, Int64Create(0x00000000, 0x0000000F)); + + x = Int64Create(0xFF000000, 0x00000000); + + y = Int64Sra(x, 4); + AssertEquals("x >> 4", y, Int64Create(0xFFF00000, 0x00000000)); + + y = Int64Sra(x, 8); + AssertEquals("x >> 8", y, Int64Create(0xFFFF0000, 0x00000000)); + + y = Int64Sra(x, 63); + AssertEquals("x >> 63", y, Int64Create(0xFFFFFFFF, 0xFFFFFFFF)); + + /* BSL Tests */ + + x = Int64Create(0x00000000, 0xFF000000); + + y = Int64Sll(x, 4); + AssertEquals("x << 4", y, Int64Create(0x0000000F, 0xF0000000)); + + y = Int64Sll(x, 8); + AssertEquals("x << 8", y, Int64Create(0x000000FF, 0x00000000)); + + y = Int64Sll(x, 36); + AssertEquals("x << 36", y, Int64Create(0xF0000000, 0x00000000)); + + /* ADD Tests */ + + x = Int64Create(0x00000000, 0xF0000001); + y = Int64Create(0x00000000, 0x00000002); + AssertEquals("0xF0000001 + 0x00000002", Int64Add(x, y), Int64Create(0x00000000, 0xF0000003)); + + x = Int64Create(0x00000000, 0xF0000000); + y = Int64Create(0x00000000, 0x10000000); + AssertEquals("0xF0000000 + 0x10000000", Int64Add(x, y), Int64Create(0x00000001, 0x00000000)); + + x = Int64Create(0, 5); + y = Int64Neg(Int64Create(0, 10)); + AssertEquals("5 + (-10)", Int64Add(x, y), Int64Neg(Int64Create(0, 5))); + + /* SUB Tests */ + x = Int64Create(0x00000000, 0x00000005); + y = Int64Create(0x00000000, 0x00000002); + AssertEquals("0x00000005 - 0x00000002", Int64Sub(x, y), Int64Create(0x00000000, 0x00000003)); + + x = Int64Create(0x00000001, 0x00000000); + y = Int64Create(0x00000000, 0x00000001); + AssertEquals("0x00000001 0x00000000 - 0x00000001", Int64Sub(x, y), Int64Create(0x00000000, 0xFFFFFFFF)); + + x = Int64Create(0, 5); + y = Int64Create(0, 10); + AssertEquals("5 - 10", Int64Sub(x, y), Int64Neg(Int64Create(0, 5))); + + x = Int64Create(0, 5); + y = Int64Neg(Int64Create(0, 10)); + AssertEquals("5 - (-10)", Int64Sub(x, y), Int64Create(0, 15)); + + /* MUL Tests */ + x = Int64Create(0, 18); + y = Int64Create(0, 1); + AssertEquals("18 * 1", Int64Mul(x, y), Int64Create(0, 18)); + + x = Int64Create(0, 20); + y = Int64Create(0, 12); + AssertEquals("20 * 12", Int64Mul(x, y), Int64Create(0, 240)); + + x = Int64Create(0x00000000, 0x00000005); + y = Int64Create(0x00000000, 0x00000005); + AssertEquals("0x00000005 * 0x00000005", Int64Mul(x, y), Int64Create(0x00000000, 0x00000019)); + + x = Int64Create(0x00000001, 0x00000000); + y = Int64Create(0x00000000, 0x00000005); + AssertEquals("0x00000001 0x00000000 * 0x00000005", Int64Mul(x, y), Int64Create(0x00000005, 0x00000000)); + + /* DIV Tests */ + x = Int64Create(0, 12); + y = Int64Create(0, 4); + AssertEquals("12 / 4", Int64Div(x, y), Int64Create(0, 3)); + + /* MOD Tests */ + x = Int64Create(0x000000FF, 0x00000000); + y = Int64Create(0x00000000, 0x00000010); + AssertEquals("0x000000FF 0x00000000 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0)); + + x = Int64Create(0x00000000, 0xFF000000); + y = Int64Create(0x00000000, 0x00000010); + AssertEquals("0x00000000 0xFF000000 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0)); + + x = Int64Create(0xFF000000, 0x00000000); + y = Int64Create(0x00000000, 0x00000010); + AssertEquals("0xFF000000 0x00000000 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0)); + + x = Int64Create(0x00000000, 0x000000F0); + y = Int64Create(0x00000000, 0x00000010); + AssertEquals("0x00000000 0x000000F0 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0)); + + /* TODO: Add more tests for negative multiplication, division, and + * mod */ + + return 0; +} diff --git a/tools/uint64.c b/tools/uint64.c new file mode 100644 index 0000000..8e9f5f5 --- /dev/null +++ b/tools/uint64.c @@ -0,0 +1,119 @@ +#include + +#include + +/* AssertEquals(actual, expected) */ +int +AssertEquals(char *msg, UInt64 x, UInt64 y) +{ + if (!UInt64Eq(x, y)) + { + Log(LOG_ERR, "%s: Expected 0x%X 0x%X, got 0x%X 0x%X", msg, + UInt64High(y), UInt64Low(y), + UInt64High(x), UInt64Low(x)); + + return 0; + } + + return 1; +} + +int +Main(void) +{ + UInt64 x, y; + + Log(LOG_INFO, "sizeof(UInt64) = %lu", sizeof(UInt64)); + +#ifdef UINT64_NATIVE + Log(LOG_INFO, "Using native 64-bit integers."); +#else + Log(LOG_INFO, "Using emulated 64-bit integers."); +#endif + + /* BSR Tests */ + + x = UInt64Create(0x000000FF, 0x00000000); + + y = UInt64Srl(x, 4); + AssertEquals("x >> 4", y, UInt64Create(0x0000000F, 0xF0000000)); + + y = UInt64Srl(x, 8); + AssertEquals("x >> 8", y, UInt64Create(0x00000000, 0xFF000000)); + + y = UInt64Srl(x, 36); + AssertEquals("x >> 36", y, UInt64Create(0x00000000, 0x0000000F)); + + /* BSL Tests */ + + x = UInt64Create(0x00000000, 0xFF000000); + + y = UInt64Sll(x, 4); + AssertEquals("x << 4", y, UInt64Create(0x0000000F, 0xF0000000)); + + y = UInt64Sll(x, 8); + AssertEquals("x << 8", y, UInt64Create(0x000000FF, 0x00000000)); + + y = UInt64Sll(x, 36); + AssertEquals("x << 36", y, UInt64Create(0xF0000000, 0x00000000)); + + /* ADD Tests */ + + x = UInt64Create(0x00000000, 0xF0000001); + y = UInt64Create(0x00000000, 0x00000002); + AssertEquals("0xF0000001 + 0x00000002", UInt64Add(x, y), UInt64Create(0x00000000, 0xF0000003)); + + x = UInt64Create(0x00000000, 0xF0000000); + y = UInt64Create(0x00000000, 0x10000000); + AssertEquals("0xF0000000 + 0x10000000", UInt64Add(x, y), UInt64Create(0x00000001, 0x00000000)); + + /* SUB Tests */ + x = UInt64Create(0x00000000, 0x00000005); + y = UInt64Create(0x00000000, 0x00000002); + AssertEquals("0x00000005 - 0x00000002", UInt64Sub(x, y), UInt64Create(0x00000000, 0x00000003)); + + x = UInt64Create(0x00000001, 0x00000000); + y = UInt64Create(0x00000000, 0x00000001); + AssertEquals("0x00000001 0x00000000 - 0x00000001", UInt64Sub(x, y), UInt64Create(0x00000000, 0xFFFFFFFF)); + + /* MUL Tests */ + x = UInt64Create(0, 18); + y = UInt64Create(0, 1); + AssertEquals("18 * 1", UInt64Mul(x, y), UInt64Create(0, 18)); + + x = UInt64Create(0, 20); + y = UInt64Create(0, 12); + AssertEquals("20 * 12", UInt64Mul(x, y), UInt64Create(0, 240)); + + x = UInt64Create(0x00000000, 0x00000005); + y = UInt64Create(0x00000000, 0x00000005); + AssertEquals("0x00000005 * 0x00000005", UInt64Mul(x, y), UInt64Create(0x00000000, 0x00000019)); + + x = UInt64Create(0x00000001, 0x00000000); + y = UInt64Create(0x00000000, 0x00000005); + AssertEquals("0x00000001 0x00000000 * 0x00000005", UInt64Mul(x, y), UInt64Create(0x00000005, 0x00000000)); + + /* DIV Tests */ + x = UInt64Create(0, 12); + y = UInt64Create(0, 4); + AssertEquals("12 / 4", UInt64Div(x, y), UInt64Create(0, 3)); + + /* MOD Tests */ + x = UInt64Create(0x000000FF, 0x00000000); + y = UInt64Create(0x00000000, 0x00000010); + AssertEquals("0x000000FF 0x00000000 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0)); + + x = UInt64Create(0x00000000, 0xFF000000); + y = UInt64Create(0x00000000, 0x00000010); + AssertEquals("0x00000000 0xFF000000 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0)); + + x = UInt64Create(0xFF000000, 0x00000000); + y = UInt64Create(0x00000000, 0x00000010); + AssertEquals("0xFF000000 0x00000000 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0)); + + x = UInt64Create(0x00000000, 0x000000F0); + y = UInt64Create(0x00000000, 0x00000010); + AssertEquals("0x00000000 0x000000F0 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0)); + + return 0; +} From 138ea1c8e97aee040135176b75ada78cbac94c45 Mon Sep 17 00:00:00 2001 From: lda Date: Sun, 26 May 2024 22:06:56 +0200 Subject: [PATCH 02/18] [FIX] Oops. --- src/Int64.c | 399 ------------------------------------- src/UInt64.c | 265 ------------------------ src/include/Args.h | 82 -------- src/include/Array.h | 187 ----------------- src/include/Base64.h | 99 --------- src/include/Cron.h | 140 ------------- src/include/Db.h | 169 ---------------- src/include/Graph.h | 175 ---------------- src/include/HashMap.h | 185 ----------------- src/include/HeaderParser.h | 125 ------------ src/include/Http.h | 211 -------------------- src/include/HttpClient.h | 104 ---------- src/include/HttpRouter.h | 91 --------- src/include/HttpServer.h | 234 ---------------------- src/include/Int.h | 122 ------------ src/include/Int64.h | 252 ----------------------- src/include/Io.h | 222 --------------------- src/include/Json.h | 323 ------------------------------ src/include/Log.h | 196 ------------------ src/include/Memory.h | 235 ---------------------- src/include/Queue.h | 105 ---------- src/include/Rand.h | 81 -------- src/include/Runtime.h | 53 ----- src/include/Sha.h | 76 ------- src/include/Str.h | 129 ------------ src/include/Stream.h | 223 --------------------- src/include/Tls.h | 109 ---------- src/include/UInt64.h | 252 ----------------------- src/include/Uri.h | 69 ------- src/include/Util.h | 117 ----------- tools/int64.c | 145 -------------- tools/uint64.c | 119 ----------- 32 files changed, 5294 deletions(-) delete mode 100644 src/Int64.c delete mode 100644 src/UInt64.c delete mode 100644 src/include/Args.h delete mode 100644 src/include/Array.h delete mode 100644 src/include/Base64.h delete mode 100644 src/include/Cron.h delete mode 100644 src/include/Db.h delete mode 100644 src/include/Graph.h delete mode 100644 src/include/HashMap.h delete mode 100644 src/include/HeaderParser.h delete mode 100644 src/include/Http.h delete mode 100644 src/include/HttpClient.h delete mode 100644 src/include/HttpRouter.h delete mode 100644 src/include/HttpServer.h delete mode 100644 src/include/Int.h delete mode 100644 src/include/Int64.h delete mode 100644 src/include/Io.h delete mode 100644 src/include/Json.h delete mode 100644 src/include/Log.h delete mode 100644 src/include/Memory.h delete mode 100644 src/include/Queue.h delete mode 100644 src/include/Rand.h delete mode 100644 src/include/Runtime.h delete mode 100644 src/include/Sha.h delete mode 100644 src/include/Str.h delete mode 100644 src/include/Stream.h delete mode 100644 src/include/Tls.h delete mode 100644 src/include/UInt64.h delete mode 100644 src/include/Uri.h delete mode 100644 src/include/Util.h delete mode 100644 tools/int64.c delete mode 100644 tools/uint64.c diff --git a/src/Int64.c b/src/Int64.c deleted file mode 100644 index 21f07f5..0000000 --- a/src/Int64.c +++ /dev/null @@ -1,399 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 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 - -#ifdef INT64_NATIVE -#define Int64Sign(x) ((int) (((UInt64) (x)) >> 63)) -#else -#define Int64Sign(x) ((int) ((x).i[1] >> 31)) -#endif - -size_t -Int64Str(Int64 x, int base, char *out, size_t len) -{ - static const char symbols[] = "0123456789ABCDEF"; - - size_t i = len - 1; - size_t j = 0; - - int neg = Int64Sign(x); - - Int64 base64 = Int64Create(0, base); - - /* We only have symbols up to base 16 */ - if (base < 2 || base > 16) - { - return 0; - } - - /* - * This algorithm doesn't work on INT64_MIN. - * - * But it works on all other integers in the range, so we - * just scoot the range in by one for now. It's a hack and - * I'm not a huge fan of it, but this function is mostly - * used in Json, which shouldn't have a range this large - * anyway (Json is limited to -2^53 -> 2^53-1). - * - * Proper fixes are always welcome. - */ - if (Int64Eq(x, Int64Create(0x80000000, 0x00000000))) - { - x = Int64Add(x, Int64Create(0, 1)); - } -#if 0 - else if (Int64Eq(x, Int64Create(0x7FFFFFFF, 0xFFFFFFFF))) - { - x = Int64Sub(x, Int64Create(0, 1)); - } -#endif - - if (base != 2 && base != 8 && base != 16 && neg) - { - x = Int64Neg(x); - } - - do - { - Int64 mod = Int64Rem(x, base64); - Int32 low = Int64Low(mod); - - out[i] = symbols[low]; - i--; - x = Int64Div(x, base64); - } while (Int64Gt(x, Int64Create(0, 0))); - - if (base != 2 && base != 8 && base != 16) - { - /* - * Binary, octal, and hexadecimal are known to - * be bit representations. Everything else (notably - * decimal) should include the negative sign. - */ - if (neg) - { - out[i] = '-'; - i--; - } - } - - while (++i < len) - { - out[j++] = out[i]; - } - - out[j] = '\0'; - - return j; -} - -#ifndef INT64_NATIVE - -/* No native 64-bit support, add our own */ - -Int64 -Int64Create(UInt32 high, UInt32 low) -{ - Int64 x; - - x.i[0] = low; - x.i[1] = high; - - return x; -} - -Int64 -Int64Add(Int64 x, Int64 y) -{ - Int64 z = Int64Create(0, 0); - int carry; - - z.i[0] = x.i[0] + y.i[0]; - carry = z.i[0] < x.i[0]; - z.i[1] = x.i[1] + y.i[1] + carry; - - return z; -} - -Int64 -Int64Sub(Int64 x, Int64 y) -{ - return Int64Add(x, Int64Neg(y)); -} - -Int64 -Int64Mul(Int64 x, Int64 y) -{ - Int64 z = Int64Create(0, 0); - - int xneg = Int64Sign(x); - int yneg = Int64Sign(y); - - if (xneg) - { - x = Int64Neg(x); - } - - if (yneg) - { - y = Int64Neg(y); - } - - /* while (y > 0) */ - while (Int64Gt(y, Int64Create(0, 0))) - { - /* if (y & 1 != 0) */ - if (Int64Neq(Int64And(y, Int64Create(0, 1)), Int64Create(0, 0))) - { - z = Int64Add(z, x); - } - - x = Int64Sll(x, 1); - y = Int64Sra(y, 1); - } - - if (xneg != yneg) - { - z = Int64Neg(z); - } - - return z; -} - -typedef struct -{ - Int64 q; - Int64 r; -} Int64Ldiv; - -static Int64Ldiv -Int64LongDivision(Int64 n, Int64 d) -{ - Int64Ldiv o; - - int i; - - int nneg = Int64Sign(n); - int dneg = Int64Sign(d); - - o.q = Int64Create(0, 0); - o.r = Int64Create(0, 0); - - if (Int64Eq(d, Int64Create(0, 0))) - { - raise(SIGFPE); - return o; - } - - if (nneg) - { - n = Int64Neg(n); - } - - if (dneg) - { - d = Int64Neg(d); - } - - for (i = 63; i >= 0; i--) - { - Int64 bit = Int64And(Int64Sra(n, i), Int64Create(0, 1)); - - o.r = Int64Sll(o.r, 1); - o.r = Int64Or(o.r, bit); - - if (Int64Geq(o.r, d)) - { - o.r = Int64Sub(o.r, d); - o.q = Int64Or(o.q, Int64Sll(Int64Create(0, 1), i)); - } - } - - if (nneg != dneg) - { - o.r = Int64Neg(o.r); - o.q = Int64Neg(o.q); - } - - return o; -} - -Int64 -Int64Div(Int64 x, Int64 y) -{ - return Int64LongDivision(x, y).q; -} - -Int64 -Int64Rem(Int64 x, Int64 y) -{ - return Int64LongDivision(x, y).r; -} - -Int64 -Int64Sll(Int64 x, int y) -{ - Int64 z; - - if (!y) - { - return x; - } - - z = Int64Create(0, 0); - - if (y < 32) - { - z.i[1] = (x.i[0] >> (32 - y)) | (x.i[1] << y); - z.i[0] = x.i[0] << y; - } - else - { - z.i[1] = x.i[0] << (y - 32); - } - - return z; -} - -Int64 -Int64Sra(Int64 x, int y) -{ - Int64 z; - - int neg = Int64Sign(x); - - if (!y) - { - return x; - } - - z = Int64Create(0, 0); - - if (y < 32) - { - z.i[0] = (x.i[1] << (32 - y)) | (x.i[0] >> y); - z.i[1] = x.i[1] >> y; - } - else - { - z.i[0] = x.i[1] >> (y - 32); - } - - if (neg) - { - Int64 mask = Int64Create(0xFFFFFFFF, 0xFFFFFFFF); - - z = Int64Or(Int64Sll(mask, (64 - y)), z); - } - - return z; -} - -Int64 -Int64And(Int64 x, Int64 y) -{ - return Int64Create(x.i[1] & y.i[1], x.i[0] & y.i[0]); -} - -Int64 -Int64Or(Int64 x, Int64 y) -{ - return Int64Create(x.i[1] | y.i[1], x.i[0] | y.i[0]); -} - -Int64 -Int64Xor(Int64 x, Int64 y) -{ - return Int64Create(x.i[1] ^ y.i[1], x.i[0] ^ y.i[0]); -} - -Int64 -Int64Not(Int64 x) -{ - return Int64Create(~(x.i[1]), ~(x.i[0])); -} - -int -Int64Eq(Int64 x, Int64 y) -{ - return x.i[0] == y.i[0] && x.i[1] == y.i[1]; -} - -int -Int64Lt(Int64 x, Int64 y) -{ - int xneg = Int64Sign(x); - int yneg = Int64Sign(y); - - if (xneg != yneg) - { - return xneg > yneg; - } - else - { - if (xneg) - { - /* Both negative */ - return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]); - } - else - { - /* Both positive */ - return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]); - } - } -} - -int -Int64Gt(Int64 x, Int64 y) -{ - int xneg = Int64Sign(x); - int yneg = Int64Sign(y); - - if (xneg != yneg) - { - return xneg < yneg; - } - else - { - if (xneg) - { - /* Both negative */ - return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]); - } - else - { - /* Both positive */ - return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]); - } - } - -} - -#endif diff --git a/src/UInt64.c b/src/UInt64.c deleted file mode 100644 index ae8eff7..0000000 --- a/src/UInt64.c +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 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 - -size_t -UInt64Str(UInt64 x, int base, char *out, size_t len) -{ - static const char symbols[] = "0123456789ABCDEF"; - - size_t i = len - 1; - size_t j = 0; - - UInt64 base64 = UInt64Create(0, base); - - /* We only have symbols up to base 16 */ - if (base < 2 || base > 16) - { - return 0; - } - - do - { - UInt64 mod = UInt64Rem(x, base64); - UInt32 low = UInt64Low(mod); - - out[i] = symbols[low]; - i--; - x = UInt64Div(x, base64); - } while (UInt64Gt(x, UInt64Create(0, 0))); - - while (++i < len) - { - out[j++] = out[i]; - } - - out[j] = '\0'; - - return j; -} - -#ifndef UINT64_NATIVE - -/* No native 64-bit support, add our own */ - -UInt64 -UInt64Create(UInt32 high, UInt32 low) -{ - UInt64 x; - - x.i[0] = low; - x.i[1] = high; - - return x; -} - -UInt64 -UInt64Add(UInt64 x, UInt64 y) -{ - UInt64 z = UInt64Create(0, 0); - int carry; - - z.i[0] = x.i[0] + y.i[0]; - carry = z.i[0] < x.i[0]; - z.i[1] = x.i[1] + y.i[1] + carry; - - return z; -} - -UInt64 -UInt64Sub(UInt64 x, UInt64 y) -{ - UInt64 twosCompl = UInt64Add(UInt64Not(y), UInt64Create(0, 1)); - - return UInt64Add(x, twosCompl); -} - -UInt64 -UInt64Mul(UInt64 x, UInt64 y) -{ - UInt64 z = UInt64Create(0, 0); - - /* while (y > 0) */ - while (UInt64Gt(y, UInt64Create(0, 0))) - { - /* if (y & 1 != 0) */ - if (UInt64Neq(UInt64And(y, UInt64Create(0, 1)), UInt64Create(0, 0))) - { - z = UInt64Add(z, x); - } - - x = UInt64Sll(x, 1); - y = UInt64Srl(y, 1); - } - - return z; -} - -typedef struct -{ - UInt64 q; - UInt64 r; -} UInt64Ldiv; - -static UInt64Ldiv -UInt64LongDivision(UInt64 n, UInt64 d) -{ - UInt64Ldiv o; - - int i; - - o.q = UInt64Create(0, 0); - o.r = UInt64Create(0, 0); - - if (UInt64Eq(d, UInt64Create(0, 0))) - { - raise(SIGFPE); - return o; - } - - for (i = 63; i >= 0; i--) - { - UInt64 bit = UInt64And(UInt64Srl(n, i), UInt64Create(0, 1)); - - o.r = UInt64Sll(o.r, 1); - o.r = UInt64Or(o.r, bit); - - if (UInt64Geq(o.r, d)) - { - o.r = UInt64Sub(o.r, d); - o.q = UInt64Or(o.q, UInt64Sll(UInt64Create(0, 1), i)); - } - } - - return o; -} - -UInt64 -UInt64Div(UInt64 x, UInt64 y) -{ - return UInt64LongDivision(x, y).q; -} - -UInt64 -UInt64Rem(UInt64 x, UInt64 y) -{ - return UInt64LongDivision(x, y).r; -} - -UInt64 -UInt64Sll(UInt64 x, int y) -{ - UInt64 z; - - if (!y) - { - return x; - } - - z = UInt64Create(0, 0); - - if (y < 32) - { - z.i[1] = (x.i[0] >> (32 - y)) | (x.i[1] << y); - z.i[0] = x.i[0] << y; - } - else - { - z.i[1] = x.i[0] << (y - 32); - } - - return z; -} - -UInt64 -UInt64Srl(UInt64 x, int y) -{ - UInt64 z; - - if (!y) - { - return x; - } - - z = UInt64Create(0, 0); - - if (y < 32) - { - z.i[0] = (x.i[1] << (32 - y)) | (x.i[0] >> y); - z.i[1] = x.i[1] >> y; - } - else - { - z.i[0] = x.i[1] >> (y - 32); - } - - return z; -} - -UInt64 -UInt64And(UInt64 x, UInt64 y) -{ - return UInt64Create(x.i[1] & y.i[1], x.i[0] & y.i[0]); -} - -UInt64 -UInt64Or(UInt64 x, UInt64 y) -{ - return UInt64Create(x.i[1] | y.i[1], x.i[0] | y.i[0]); -} - -UInt64 -UInt64Xor(UInt64 x, UInt64 y) -{ - return UInt64Create(x.i[1] ^ y.i[1], x.i[0] ^ y.i[0]); -} - -UInt64 -UInt64Not(UInt64 x) -{ - return UInt64Create(~(x.i[1]), ~(x.i[0])); -} - -int -UInt64Eq(UInt64 x, UInt64 y) -{ - return x.i[0] == y.i[0] && x.i[1] == y.i[1]; -} - -int -UInt64Lt(UInt64 x, UInt64 y) -{ - return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]); -} - -int -UInt64Gt(UInt64 x, UInt64 y) -{ - return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]); -} - -#endif diff --git a/src/include/Args.h b/src/include/Args.h deleted file mode 100644 index 3108de3..0000000 --- a/src/include/Args.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_ARGS_H -#define CYTOPLASM_ARGS_H - -/*** - * @Nm Args - * @Nd Getopt-style argument parser that operates on arrays. - * @Dd May 12 2023 - * @Xr Array - * - * .Nm - * provides a simple argument parser in the style of - * .Xr getopt 3 . - * It exists because the runtime passes the program arguments as - * an Array, and it is often useful to parse the arguments to - * provide the standard command line interface. - */ - -#include - -/** - * All state is stored in this structure, instead of global - * variables. This makes - * .Nm - * thread-safe and easy to reset. - */ -typedef struct ArgParseState -{ - int optInd; - int optErr; - int optOpt; - char *optArg; - - int optPos; -} ArgParseState; - -/** - * Initialize the variables in the given parser state structure - * to their default values. This should be called before - * .Fn ArgParse - * is called with the parser state. It should also be called if - * .Fn ArgParse - * will be used again on a different array, or the same array all - * over again. - */ -extern void ArgParseStateInit(ArgParseState *); - -/** - * Parse command line arguments stored in the given array, using - * the given state and option string. This function behaves - * identically to the POSIX - * .Fn getopt - * function, and should be used in exactly the same way. Refer to - * your system's - * .Xr getopt 3 - * page for details. - */ -extern int ArgParse(ArgParseState *, Array *, const char *); - -#endif /* CYTOPLASM_ARGS_H */ diff --git a/src/include/Array.h b/src/include/Array.h deleted file mode 100644 index 0ba1335..0000000 --- a/src/include/Array.h +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef CYTOPLASM_ARRAY_H -#define CYTOPLASM_ARRAY_H - -/*** - * @Nm Array - * @Nd A simple dynamic array data structure. - * @Dd November 24 2022 - * @Xr HashMap Queue - * - * These functions implement a simple array data structure that is - * automatically resized as necessary when new values are added. This - * implementation does not actually store the values of the items in it; - * it only stores pointers to the data. As such, you will still have to - * manually maintain all your data. The advantage of this is that these - * functions don't have to copy data, and thus don't care how big the - * data is. Furthermore, arbitrary data can be stored in the array. - * .Pp - * This array implementation is optimized for storage space and - * appending. Deletions are expensive in that all the items of the list - * above a deletion are moved down to fill the hole where the deletion - * occurred. Insertions are also expensive in that all the elements - * above the given index must be shifted up to make room for the new - * element. - * .Pp - * Due to these design choices, this array implementation is best - * suited to linear writing, and then linear or random reading. - */ - -#include -#include - -/** - * The functions in this API operate on an array structure which is - * opaque to the caller. - */ -typedef struct Array Array; - -/** - * Allocate a new array. This function returns a pointer that can be - * used with the other functions in this API, or NULL if there was an - * error allocating memory for the array. - */ -extern Array *ArrayCreate(void); - -/** - * Deallocate an array. Note that this function does not free any of - * the values stored in the array; it is the caller's job to manage the - * memory for each item. Typically, the caller would iterate over all - * the items in the array and free them before freeing the array. - */ -extern void ArrayFree(Array *); - -/** - * Get the size, in number of elements, of the given array. - */ -extern size_t ArraySize(Array *); - -/** - * Get the element at the specified index from the specified array. - * This function will return NULL if the array is NULL, or the index - * is out of bounds. Otherwise, it will return a pointer to a value - * put into the array using - * .Fn ArrayInsert - * or - * .Fn ArraySet . - */ -extern void *ArrayGet(Array *, size_t); - -/** - * Insert the specified element at the specified index in the specified - * array. This function will shift the element currently at that index, - * and any elements after it before inserting the given element. - * .Pp - * This function returns a boolean value indicating whether or not it - * suceeded. - */ -extern int ArrayInsert(Array *, size_t, void *); - -/** - * Set the value at the specified index in the specified array to the - * specified value. This function will return the old value at that - * index, if any. - */ -extern void *ArraySet(Array *, size_t, void *); - -/** - * Append the specified element to the end of the specified array. This - * function uses - * .Fn ArrayInsert - * under the hood to insert an element at the end. It thus has the same - * return value as - * .Fn ArrayInsert . - */ -extern int ArrayAdd(Array *, void *); - -/** - * Remove the element at the specified index from the specified array. - * This function returns the element removed, if any. - */ -extern void *ArrayDelete(Array *, size_t); - -/** - * Sort the specified array using the specified sort function. The - * sort function compares two elements. It takes two void pointers as - * parameters, and returns an integer. The return value indicates to - * the sorting algorithm how the elements relate to each other. A - * return value of 0 indicates that the elements are identical. A - * return value greater than 0 indicates that the first item is - * ``bigger'' than the second item and should thus appear after it in - * the array. A return value less than 0 indicates the opposite: the - * second element should appear after the first in the array. - */ -extern void ArraySort(Array *, int (*) (void *, void *)); - -/** - * Remove all duplicates from an array by using the given comparison - * function to sort the array, then remove matching values. This - * function returns a new array with all duplicates removed. The - * original array is unaffected. Note that arrays only store pointers - * to values, usually values on the heap. Thus, it is possible to lose - * pointers to duplicate values and have them leak. - * .P - * This is a relatively expensive operation. The array must first be - * duplicated. Then it is sorted, then it is iterated from beginning - * to end to remove duplicate entires. Note that the comparison - * function is executed on each element at least twice. - */ -extern Array *ArrayUnique(Array *, int (*) (void *, void *)); - - -/** - * Reverses the order of all elements in the array into a new array on - * the heap, to be freed using - * .Fn ArrayFree . - */ -extern Array *ArrayReverse(Array *); - -/** - * If possible, reduce the amount of memory allocated to this array - * by calling - * .Fn Realloc - * on the internal structure to perfectly fit the elements in the - * array. This function is intended to be used by functions that return - * relatively read-only arrays that will be long-lived. - */ -extern int ArrayTrim(Array *); - -/** - * Convert a variadic arguments list into an Array. In most cases, the - * Array API is much easier to work with than - * .Fn va_arg - * and friends. - */ -extern Array *ArrayFromVarArgs(size_t, va_list); - -/** - * Duplicate an existing array. Note that arrays only hold pointers to - * their data, not the data itself, so the duplicated array will point - * to the same places in memory as the original array. - */ -extern Array *ArrayDuplicate(Array *); - -#endif /* CYTOPLASM_ARRAY_H */ diff --git a/src/include/Base64.h b/src/include/Base64.h deleted file mode 100644 index b36a48c..0000000 --- a/src/include/Base64.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef CYTOPLASM_BASE64_H -#define CYTOPLASM_BASE64_H - -/*** - * @Nm Base64 - * @Nd A simple base64 encoder/decoder with unpadded base64 support. - * @Dd September 30 2022 - * @Xr Sha2 - * - * This is an efficient yet simple base64 encoding and decoding API - * that supports regular base64, as well as the Matrix specification's - * extension to base64, called ``unpadded base64.'' This API provides - * the ability to convert between the two, instead of just implementing - * unpadded base64. - */ - -#include - -/** - * This function computes the amount of bytes needed to store a message - * of the specified number of bytes as base64. - */ -extern size_t - Base64EncodedSize(size_t); - -/** - * This function computes the amount of bytes needed to store a decoded - * representation of the encoded message. It takes a pointer to the - * encoded string because it must read a few bytes off the end in order - * to accurately compute the size. - */ -extern size_t - Base64DecodedSize(const char *, size_t); - -/** - * Encode the specified number of bytes from the specified buffer as - * base64. This function returns a string on the heap that should be - * freed with - * .Fn Free , - * or NULL if a memory allocation error ocurred. - */ -extern char * - Base64Encode(const char *, size_t); - -/** - * Decode the specified number of bytes from the specified buffer of - * base64. This function returns a string on the heap that should be - * freed with - * .Fn Free , - * or NULL if a memory allocation error occured. - */ -extern char * - Base64Decode(const char *, size_t); - -/** - * Remove the padding from a specified base64 string. This function - * modifies the specified string in place. It thus has no return value - * because it cannot fail. If the passed pointer is invalid, the - * behavior is undefined. - */ -extern void - Base64Unpad(char *, size_t); - -/** - * Add padding to an unpadded base64 string. This function takes a - * pointer to a pointer because it may be necessary to grow the memory - * allocated to the string. This function returns a boolean value - * indicating whether the pad operation was successful. In practice, - * this means it will only fail if a bigger string is necessary, but it - * could not be automatically allocated on the heap. - */ -extern int - Base64Pad(char **, size_t); - -#endif /* CYTOPLASM_BASE64_H */ diff --git a/src/include/Cron.h b/src/include/Cron.h deleted file mode 100644 index e78900d..0000000 --- a/src/include/Cron.h +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_CRON_H -#define CYTOPLASM_CRON_H - -/*** - * @Nm Cron - * @Nd Basic periodic job scheduler. - * @Dd December 24 2022 - * - * This is an extremely basic job scheduler. So basic, in fact, that - * it currently runs all jobs on a single thread, which means that it - * is intended for short-lived jobs. In the future, it might be - * extended to support a one-thread-per-job model, but for now, jobs - * should take into consideration the fact that they are sharing their - * thread, so they should not be long-running. - * .Pp - * .Nm - * works by ``ticking'' at an interval defined by the caller of - * .Fn CronCreate . - * At each tick, all the registered jobs are queried, and if they are - * due to run again, their function is executed. As much as possible, - * .Nm - * tries to tick at constant intervals, however it is possible that one - * or more jobs may overrun the tick duration. If this happens, - * .Nm - * ticks again immediately after all the jobs for the previous tick - * have completed. This is in an effort to compensate for lost time, - * however it is important to note that when jobs overrun the tick - * interval, the interval is pushed back by the amount that it was - * overrun. Because of this, - * .Nm - * is best suited for scheduling jobs that should happen - * ``aproximately'' every so often; it is not a real-time scheduler - * by any means. - */ - -#include - -/** - * All functions defined here operate on a structure opaque to the - * caller. - */ -typedef struct Cron Cron; - -/** - * A job function is a function that takes a void pointer and returns - * nothing. The pointer is passed when the job is scheduled, and - * is expected to remain valid until the job is no longer registered. - * The pointer is passed each time the job executes. - */ -typedef void (JobFunc) (void *); - -/** - * Create a new - * .Nm - * object that all other functions operate on. Like most of the other - * APIs in this project, it must be freed with - * .Fn CronFree - * when it is no longer needed. - * .Pp - * This function takes the tick interval in milliseconds. - */ -extern Cron * CronCreate(UInt32); - -/** - * Schedule a one-off job to be executed only at the next tick, and - * then immediately discarded. This is useful for scheduling tasks that - * only have to happen once, or very infrequently depending on - * conditions other than the current time, but don't have to happen - * immediately. The caller simply indicates that it wishes for the task - * to execute at some time in the future. How far into the future this - * practically ends up being is determined by how long it takes for - * other registered jobs to finish, and what the tick interval is. - * .Pp - * This function takes a job function and a pointer to pass to that - * function when it is executed. - */ -extern void - CronOnce(Cron *, JobFunc *, void *); - -/** - * Schedule a repetitive task to be executed at aproximately the given - * interval. As stated above, this is as fuzzy interval; depending on - * the jobs being run and the tick interval, tasks may not execute at - * exactly the scheduled time, but they will eventually execute. - * .Pp - * This function takes an interval in milliseconds, a job function, - * and a pointer to pass to that function when it is executed. - */ -extern void - CronEvery(Cron *, unsigned long, JobFunc *, void *); - -/** - * Start ticking the clock and executing registered jobs. - */ -extern void - CronStart(Cron *); - -/** - * Stop ticking the clock. Jobs that are already executing will - * continue to execute, but when they are finished, no new jobs will - * be executed until - * .Fn CronStart - * is called. - */ -extern void - CronStop(Cron *); - -/** - * Discard all job references and free all memory associated with the - * given - * .Nm Cron - * instance. - */ -extern void - CronFree(Cron *); - -#endif /* CYTOPLASM_CRON_H */ diff --git a/src/include/Db.h b/src/include/Db.h deleted file mode 100644 index 618a2e2..0000000 --- a/src/include/Db.h +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_DB_H -#define CYTOPLASM_DB_H - -/*** - * @Nm Db - * @Nd A minimal flat-file database with mutex locking and cache. - * @Dd April 27 2023 - * @Xr Json - * - * Cytoplasm operates on a flat-file database instead of a - * traditional relational database. This greatly simplifies the - * persistent storage code, and creates a relatively basic API, - * described here. - */ - -#include - -#include -#include - -/** - * All functions in this API operate on a database structure that is - * opaque to the caller. - */ -typedef struct Db Db; - -/** - * When an object is locked, a reference is returned. This reference - * is owned by the current thread, and the database is inaccessible to - * other threads until all references have been returned to the - * database. - * .Pp - * This reference is opaque, but can be manipulated by the functions - * defined here. - */ -typedef struct DbRef DbRef; - -/** - * Open a data directory. This function takes a path to open, and a - * cache size in bytes. If the cache size is 0, then caching is - * disabled and objects are loaded off the disk every time they are - * locked. Otherwise, objects are stored in the cache, and they are - * evicted in a least-recently-used manner. - */ -extern Db * DbOpen(char *, size_t); - -/** - * Close the database. This function will flush anything in the cache - * to the disk, and then close the data directory. It assumes that - * all references have been unlocked. If a reference has not been - * unlocked, undefined behavior results. - */ -extern void DbClose(Db *); - -/** - * Set the maximum cache size allowed before - * .Nm - * starts evicting old objects. If this is set to 0, everything in the - * cache is immediately evicted and caching is disabled. If the - * database was opened with a cache size of 0, setting this will - * initialize the cache, and subsequent calls to - * .Fn DbLock - * will begin caching objects. - */ -extern void DbMaxCacheSet(Db *, size_t); - -/** - * Create a new object in the database with the specified name. This - * function will fail if the object already exists in the database. It - * takes a variable number of C strings, with the exact number being - * specified by the second parameter. These C strings are used to - * generate a filesystem path at which to store the object. These paths - * ensure each object is uniquely identifiable, and provides semantic - * meaning to an object. - */ -extern DbRef * DbCreate(Db *, size_t,...); - -/** - * Lock an existing object in the database. This function will fail - * if the object does not exist. It takes a variable number of C - * strings, with the exact number being specified by the second - * parameter. These C strings are used to generate the filesystem path - * at which to load the object. These paths ensure each object is - * uniquely identifiable, and provides semantic meaning to an object. - */ -extern DbRef * DbLock(Db *, size_t,...); - -/** - * Immediately and permanently remove an object from the database. - * This function assumes the object is not locked, otherwise undefined - * behavior will result. - */ -extern int DbDelete(Db *, size_t,...); - -/** - * Unlock an object and return it back to the database. This function - * immediately syncs the object to the filesystem. The cache is a - * read cache; writes are always immediate to ensure data integrity in - * the event of a system failure. - */ -extern int DbUnlock(Db *, DbRef *); - -/** - * Check the existence of the given database object in a more efficient - * manner than attempting to lock it with - * .Fn DbLock . - * This function does not lock the object, nor does it load it into - * memory if it exists. - */ -extern int DbExists(Db *, size_t,...); - -/** - * List all of the objects at a given path. Unlike the other varargs - * functions, this one does not take a path to a specific object; it - * takes a directory to be iterated, where each path part is its own - * C string. Note that the resulting list only contains the objects - * in the specified directory, it does not list any subdirectories. - * .Pp - * The array returned is an array of C strings containing the object - * name. - */ -extern Array * DbList(Db *, size_t,...); - -/** - * Free the list returned by - * .Fn DbListFree . - */ -extern void DbListFree(Array *); - -/** - * Convert a database reference into JSON that can be manipulated. - * At this time, the database actually stores objects as JSON on the - * disk, so this function just returns an internal pointer, but in the - * future it may have to be generated by decompressing a binary blob, - * or something of that nature. - */ -extern HashMap * DbJson(DbRef *); - -/** - * Free the existing JSON associated with the given reference, and - * replace it with new JSON. This is more efficient than duplicating - * a separate object into the database reference. - */ -extern int DbJsonSet(DbRef *, HashMap *); - -#endif diff --git a/src/include/Graph.h b/src/include/Graph.h deleted file mode 100644 index 02e4e02..0000000 --- a/src/include/Graph.h +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef CYTOPLASM_GRAPH_H -#define CYTOPLASM_GRAPH_H - -/*** - * @Nm Graph - * @Nd Extremely simple graph, implemented as an adjacency matrix. - * @Dd July 15 2023 - * - * .Nm - * is a basic graph data structure originally written for a computer - * science class on data structures and algorithms, in which it - * received full credit. This is an adaptation of the original - * implementation that follows the Cytoplasm style and uses Cytoplasm - * APIs when convenient. - * .P - * .Nm - * stores data in an adjacency matrix, which means the storage - * complexity is O(N^2), where N is the number of vertices (called - * Nodes in this implementation) in the graph. However, this makes the - * algorithms fast and efficient. - * .P - * Nodes are identified by index, so the first node is 0, the second - * is 1, and so on. This data structure does not support storing - * arbitrary data as nodes; rather, the intended use case is to add - * all your node data to an Array, thus giving each node an index, - * and then manipulating the graph with that index. This allows access - * to node data in O(1) time in call cases, and is the most memory - * efficient. - * .P - * .Nm - * can be used to store a variety of types of graphs, although it is - * primarily suited to directed and weighted graphs. - */ - -#include - -/** - * The functions provided here operate on an opaque graph structure. - * This structure really just stores a matrix in a contiguous block of - * memory, as well as the number of nodes in the graph, but the - * structure is kept opaque so that it remains internally consistent. - * It also maintains the style of the Cytoplasm library. - */ -typedef struct Graph Graph; - -/** - * An Edge is really just a weight, which is easily represented by an - * integer. However, it makes sense to alias this to Edge for clarity, - * both in the documentation and in the implementation. - */ -typedef int Edge; - -/** - * A Node is really just a row or column in the matrix, which is easily - * represented by an unsigned integer. However, it makes sense to alias - * this to Node for clarity, both in the documentation and the - * implementation. - */ -typedef size_t Node; - -/** - * Create a new graph structure with the given number of vertices. - */ -extern Graph *GraphCreate(size_t); - -/** - * Create a new graph data structure with the given number of vertices - * and the given adjacency matrix. The adjacency matrix is copied - * verbatim into the graph data structure without any validation. - */ -extern Graph *GraphCreateWithEdges(size_t, Edge *); - -/** - * Free all memory associated with the given graph. Since graphs are - * just a collection of numbers, they do not depend on each other in - * any way. - */ -extern void GraphFree(Graph *); - -/** - * Get the weight of the edge connecting the node specified first to - * the node specified second. If this is a directed graph, it does not - * necessarily follow that there is an edge from the node specified - * second to the node specified first. It also does not follow that - * such an edge, if it exists, has the same weight. - * .P - * This function will return -1 if the graph is invalid or either node - * is out of bounds. It will return 0 if there is no such edge from the - * node specified first to the node specified second. - */ -extern Edge GraphEdgeGet(Graph *, Node, Node); - -/** - * Set the weight of the edge connecting the node specified first to - * the node specified second. If this is not a directed graph, this - * function will have to be called twice, the second time reversing the - * order of the nodes. To remove the edge, specify a weight of 0. - */ -extern Edge GraphEdgeSet(Graph *, Node, Node, Edge); - -/** - * Get the number of nodes in the given graph. This operation is a - * simple memory access that happens in O(1) time. - */ -extern size_t GraphCountNodes(Graph *); - -/** - * Perform a breadth-first search on the given graph, starting at the - * specified node. This function returns a list of nodes in the order - * they were touched. The size of the list is stored in the unsigned - * integer pointed to by the last argument. - * .P - * If an error occurs, NULL will be returned. Otherwise, the returned - * pointer should be freed with the Memory API when it is no longer - * needed. - */ -extern Node * GraphBreadthFirstSearch(Graph *, Node, size_t *); - -/** - * Perform a depth-first search on the given graph, starting at the - * specified node. This function returns a list of nodes in the order - * they were touched. The size of the list is stored in the unsigned - * integer pointed to by the last argument. - * .P - * If an error occurs, NULL will be returned. Otherwise the returned - * pointer should be freed with the Memory API when it is no longer - * needed. - */ -extern Node *GraphDepthFirstSearch(Graph *, Node, size_t *); - -/** - * Perform a topological sort on the given graph. This function returns - * a list of nodes in topological ordering, though note that this is - * probably not the only topological ordering that exists for the - * graph. The size of the list is stored in the unsigned integer - * pointed to by the last argument. It should always be the number of - * nodes in the graph, but is provided for consistency and convenience. - * .P - * If an error occurs, NULL will be returned. Otherwise the returned - * pointer should be freed with the Memory API when it is no longer - * needed. - */ -extern Node *GraphTopologicalSort(Graph *, size_t *); - -/** - * Transpose the given graph, returning a brand new graph that is the - * result of the transposition. - */ -extern Graph * GraphTranspose(Graph *); - -#endif /* CYTOPLASM_GRAPH_H */ diff --git a/src/include/HashMap.h b/src/include/HashMap.h deleted file mode 100644 index 1359400..0000000 --- a/src/include/HashMap.h +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef CYTOPLASM_HASHMAP_H -#define CYTOPLASM_HASHMAP_H - -/*** - * @Nm HashMap - * @Nd A simple hash map implementation. - * @Dd October 11 2022 - * @Xr Array Queue - * - * This is the public interface for Cytoplasm's hash map - * implementation. This hash map is designed to be simple, - * well-documented, and generally readable and understandable, yet also - * performant enough to be useful, because it is used extensively - * throughout the project. - * .Pp - * Fundamentally, this is an entirely generic map implementation. It - * can be used for many general purposes, but it is designed only to - * implement the features Cytoplasm needs to be functional. One - * example of a Cytoplasm-specific feature is that keys cannot be - * arbitrary data; they are NULL-terminated C strings. - */ - -#include - -#include - -/** - * These functions operate on an opaque structure, which the caller - * has no knowledge about. - */ -typedef struct HashMap HashMap; - -/** - * Create a new hash map that is ready to be used with the rest of the - * functions defined here. - */ -extern HashMap * HashMapCreate(void); - -/** - * Free the specified hash map such that it becomes invalid and any - * future use results in undefined behavior. Note that this function - * does not free the values stored in the hash map, but since it stores - * the keys internally, it will free the keys. You should use - * .Fn HashMapIterate - * to free the values stored in this map appropriately before calling - * this function. - */ -extern void HashMapFree(HashMap *); - -/** - * Control the maximum load of the hash map before it is expanded. - * When the hash map reaches the given capacity, it is grown. You - * don't want to only grow hash maps when they are full, because that - * makes them perform very poorly. The maximum load value is a - * percentage of how full the hash map is, and it should be between - * 0 and 1, where 0 means that no elements will cause the map to be - * expanded, and 1 means that the hash map must be completely full - * before it is expanded. The default maximum load on a new hash map - * is 0.75, which should be good enough for most purposes, however, - * this function exists specifically so that the maximum load can be - * fine-tuned. - */ -extern void HashMapMaxLoadSet(HashMap *, float); - -/** - * Use a custom hashing function with the given hash map. New hash - * maps have a sane hashing function that should work okay for most - * use cases, but if you have a better hashing function, it can be - * specified this way. Do not change the hash function after keys have - * been added; doing so results in undefined behavior. Only set a new - * hash function immediately after constructing a new hash map, before - * anything has been added to it. - * .Pp - * The hash function takes a pointer to a C string, and is expected - * to return a fairly unique numerical hash value which will be - * converted into an array index. - */ -extern void -HashMapFunctionSet(HashMap *, unsigned long (*) (const char *)); - -/** - * Set the given string key to the given value. Note that the key is - * copied into the hash map's own memory space, but the value is not. - * It is the caller's job to ensure that the value pointer remains - * valid for the life of the hash map, and are freed when no longer - * needed. - */ -extern void * HashMapSet(HashMap *, char *, void *); - -/** - * Retrieve the value for the given key, or return NULL if no such - * key exists in the hash map. - */ -extern void * HashMapGet(HashMap *, const char *); - -/** - * Remove a value specified by the given key from the hash map, and - * return it to the caller to deal with. This function returns NULL - * if no such key exists. - */ -extern void * HashMapDelete(HashMap *, const char *); - -/** - * Iterate over all the keys and values of a hash map. This function - * works very similarly to - * .Xr getopt 3 , - * where calls are repeatedly made in a while loop until there are no - * more items to go over. The difference is that this function does not - * rely on globals; it takes pointer pointers, and stores all - * necessary state inside the hash map itself. - * .Pp - * Note that this function is not thread-safe; two threads cannot be - * iterating over any given hash map at the same time, though they - * can each be iterating over different hash maps. - * .Pp - * This function can be tricky to use in some scenarios, as it - * continues where it left off on each call, until there are no more - * elements to go through in the hash map. If you are not iterating - * over the entire map in one go, and happen to break the loop, then - * the next time you attempt to iterate the hash map, you'll start - * somewhere in the middle, which is most likely not the intended - * behavior. Thus, it is always recommended to iterate over the entire - * hash map if you're going to use this function. - * .Pp - * Also note that the behavior of this function is undefined if - * insertions or deletions occur during the iteration. This - * functionality has not been tested, and will likely not work. - */ -extern int HashMapIterate(HashMap *, char **, void **); - -/** - * A reentrant version of - * .Fn HashMapIterate - * that allows the caller to overcome the flaws of that function by - * storing the cursor outside of the hash map structure itself. This - * allows multiple threads to iterate over the same hash map at the - * same time, and it allows the iteration to be halted midway through - * without causing any unintended side effects. - * .Pp - * The cursor should be initialized to 0 at the start of iteration. - */ -extern int -HashMapIterateReentrant(HashMap *, char **, void **, size_t *); - -/** - * Collect the string keys of a hash map and return them as an array. - * The returned array holds pointers to the strings stored in the - * hash map, so the strings should NOT be freed; it is sufficient to - * free the array itself. Likewise, once the hash map is freed, the - * array elements are invalid and the array should be freed. - */ -extern Array * HashMapKeys(HashMap *); - -/** - * Collect the values of a hash map and return them as an array. The - * returned array holds the same pointers to the values as the hash - * map. - */ -extern Array * HashMapValues(HashMap *); - -#endif /* CYTOPLASM_HASHMAP_H */ diff --git a/src/include/HeaderParser.h b/src/include/HeaderParser.h deleted file mode 100644 index ffb3d0b..0000000 --- a/src/include/HeaderParser.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_HEADERPARSER_H -#define CYTOPLASM_HEADERPARSER_H - -/*** - * @Nm HeaderParser - * @Nd Parse simple C header files. - * @Dd April 29 2023 - * - * .Nm - * is an extremely simple parser that lacks most of the functionality - * one would expect from a C code parser. It simply maps a stream - * of tokens into a few categories, parsing major ``milestones'' in - * a header, without actually understanding any of the details. - * .Pp - * This exists because it is used to generate man pages from headers. - * See - * .Xr hdoc 1 - * for example usage of this parser. - */ - -#include -#include - -#define HEADER_EXPR_MAX 4096 - -/** - * Headers are parsed as expressions. These are the expressions that - * this parser recognizes. - */ -typedef enum HeaderExprType -{ - HP_COMMENT, - HP_PREPROCESSOR_DIRECTIVE, - HP_TYPEDEF, - HP_DECLARATION, - HP_GLOBAL, - HP_UNKNOWN, - HP_SYNTAX_ERROR, - HP_PARSE_ERROR, - HP_EOF -} HeaderExprType; - -/** - * A representation of a function declaration. - */ -typedef struct HeaderDeclaration -{ - char returnType[64]; - char name[32]; /* Enforced by ANSI C */ - Array *args; -} HeaderDeclaration; - -/** - * A global variable declaration. The type must be of the same size - * as the function declaration's return type due to the way parsing - * them is implemented. - */ -typedef struct HeaderGlobal -{ - char type[64]; - char name[HEADER_EXPR_MAX - 64]; -} HeaderGlobal; - -/** - * A representation of a single header expression. Note that that state - * structure is entirely internally managed, so it should not be - * accessed or manipulated by functions outside the functions defined - * here. - * .Pp - * The type field should be used to determine which field in the data - * union is valid. - */ -typedef struct HeaderExpr -{ - HeaderExprType type; - union - { - char text[HEADER_EXPR_MAX]; - HeaderDeclaration declaration; - HeaderGlobal global; - struct - { - int lineNo; - char *msg; - } error; - } data; - - struct - { - Stream *stream; - int lineNo; - } state; -} HeaderExpr; - -/** - * Parse the next expression into the given header expression structure. - * To parse an entire C header, this function should be called in a - * loop until the type of the expression is HP_EOF. - */ -extern void HeaderParse(Stream *, HeaderExpr *); - -#endif /* CYTOPLASM_HEADERPARSER_H */ diff --git a/src/include/Http.h b/src/include/Http.h deleted file mode 100644 index d2de020..0000000 --- a/src/include/Http.h +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_HTTP_H -#define CYTOPLASM_HTTP_H - -/*** - * @Nm Http - * @Nd Encode and decode various parts of the HTTP protocol. - * @Dd March 12 2023 - * @Xr HttpClient HttpServer HashMap Queue Memory - * - * .Nm - * is a collection of utility functions and type definitions that are - * useful for dealing with HTTP. HTTP is not a complex protocol, but - * this API makes it a lot easier to work with. - * .Pp - * Note that this API doesn't target any particular HTTP version, but - * it is currently used with HTTP 1.0 clients and servers, and - * therefore may be lacking functionality added in later HTTP versions. - */ - -#include - -#include -#include - -#define HTTP_FLAG_NONE 0 -#define HTTP_FLAG_TLS (1 << 0) - -/** - * The request methods defined by the HTTP standard. These numeric - * constants should be preferred to strings when building HTTP APIs - * because they are more efficient. - */ -typedef enum HttpRequestMethod -{ - HTTP_METHOD_UNKNOWN, - HTTP_GET, - HTTP_HEAD, - HTTP_POST, - HTTP_PUT, - HTTP_DELETE, - HTTP_CONNECT, - HTTP_OPTIONS, - HTTP_TRACE, - HTTP_PATCH -} HttpRequestMethod; - -/** - * An enumeration that corresponds to the actual integer values of the - * valid HTTP response codes. - */ -typedef enum HttpStatus -{ - HTTP_STATUS_UNKNOWN = 0, - - /* Informational responses */ - HTTP_CONTINUE = 100, - HTTP_SWITCHING_PROTOCOLS = 101, - HTTP_EARLY_HINTS = 103, - - /* Successful responses */ - HTTP_OK = 200, - HTTP_CREATED = 201, - HTTP_ACCEPTED = 202, - HTTP_NON_AUTHORITATIVE_INFORMATION = 203, - HTTP_NO_CONTENT = 204, - HTTP_RESET_CONTENT = 205, - HTTP_PARTIAL_CONTENT = 206, - - /* Redirection messages */ - HTTP_MULTIPLE_CHOICES = 300, - HTTP_MOVED_PERMANENTLY = 301, - HTTP_FOUND = 302, - HTTP_SEE_OTHER = 303, - HTTP_NOT_MODIFIED = 304, - HTTP_TEMPORARY_REDIRECT = 307, - HTTP_PERMANENT_REDIRECT = 308, - - /* Client error messages */ - HTTP_BAD_REQUEST = 400, - HTTP_UNAUTHORIZED = 401, - HTTP_FORBIDDEN = 403, - HTTP_NOT_FOUND = 404, - HTTP_METHOD_NOT_ALLOWED = 405, - HTTP_NOT_ACCEPTABLE = 406, - HTTP_PROXY_AUTH_REQUIRED = 407, - HTTP_REQUEST_TIMEOUT = 408, - HTTP_CONFLICT = 409, - HTTP_GONE = 410, - HTTP_LENGTH_REQUIRED = 411, - HTTP_PRECONDITION_FAILED = 412, - HTTP_PAYLOAD_TOO_LARGE = 413, - HTTP_URI_TOO_LONG = 414, - HTTP_UNSUPPORTED_MEDIA_TYPE = 415, - HTTP_RANGE_NOT_SATISFIABLE = 416, - HTTP_EXPECTATION_FAILED = 417, - HTTP_TEAPOT = 418, - HTTP_UPGRADE_REQUIRED = 426, - HTTP_PRECONDITION_REQUIRED = 428, - HTTP_TOO_MANY_REQUESTS = 429, - HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, - HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451, - - /* Server error responses */ - HTTP_INTERNAL_SERVER_ERROR = 500, - HTTP_NOT_IMPLEMENTED = 501, - HTTP_BAD_GATEWAY = 502, - HTTP_SERVICE_UNAVAILABLE = 503, - HTTP_GATEWAY_TIMEOUT = 504, - HTTP_VERSION_NOT_SUPPORTED = 505, - HTTP_VARIANT_ALSO_NEGOTIATES = 506, - HTTP_NOT_EXTENDED = 510, - HTTP_NETWORK_AUTH_REQUIRED = 511 -} HttpStatus; - -/** - * Convert an HTTP status enumeration value into a string description - * of the status, which is to be used in server response to a client, - * or a client response to a user. For example, calling - * .Fn HttpStatusToString "HTTP_GATEWAY_TIMEOUT" - * (or - * .Fn HttpStatusToString "504" ) - * produces the string "Gateway Timeout". Note that the returned - * pointers point to static space, so their manipulation is forbidden. - */ -extern const char * HttpStatusToString(const HttpStatus); - -/** - * Convert a string into a numeric code that can be used throughout - * the code of a program in an efficient manner. See the definition - * of HttpRequestMethod. This function does case-sensitive matching, - * and does not trim or otherwise process the input string. - */ -extern HttpRequestMethod HttpRequestMethodFromString(const char *); - -/** - * Convert a numeric code as defined by HttpRequestMethod into a - * string that can be sent to a server. Note that the returned pointers - * point to static space, so their manipulation is forbidden. - */ -extern const char * HttpRequestMethodToString(const HttpRequestMethod); - -/** - * Encode a C string such that it can safely appear in a URL by - * performing the necessary percent escaping. A new string on the - * heap is returned. It should be freed with - * .Fn Free , - * defined in the - * .Xr Memory 3 - * API. - */ -extern char * HttpUrlEncode(char *); - -/** - * Decode a percent-encoded string into a C string, ignoring encoded - * null characters entirely, because those would do nothing but cause - * problems. - */ -extern char * HttpUrlDecode(char *); - -/** - * Decode an encoded parameter string in the form of - * ``key=val&key2=val2'' into a hash map whose values are C strings. - * This function properly decodes keys and values using the functions - * defined above. - */ -extern HashMap * HttpParamDecode(char *); - -/** - * Encode a hash map whose values are strings as an HTTP parameter - * string suitable for GET or POST requests. - */ -extern char * HttpParamEncode(HashMap *); - -/** - * Read HTTP headers from a stream and return a hash map whose values - * are strings. All keys are lowercased to make querying them - * consistent and not dependent on the case that was read from the - * stream. This is useful for both client and server code, since the - * headers are in the same format. This function should be used after - * parsing the HTTP status line, because it does not parse that line. - * It will stop when it encounters the first blank line, which - * indicates that the body is beginning. After this function completes, - * the body may be immediately read from the stream without any - * additional processing. - */ -extern HashMap * HttpParseHeaders(Stream *); - -#endif diff --git a/src/include/HttpClient.h b/src/include/HttpClient.h deleted file mode 100644 index f757041..0000000 --- a/src/include/HttpClient.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_HTTPCLIENT_H -#define CYTOPLASM_HTTPCLIENT_H - -/*** - * @Nm HttpClient - * @Nd Extremely simple HTTP client. - * @Dd April 29 2023 - * @Xr Http HttpServer Tls - * - * .Nm - * HttpClient - * builds on the HTTP API to provide a simple yet functional HTTP - * client. It aims at being easy to use and minimal, yet also - * efficient. - */ - -#include - -#include -#include - -/** - * A server response is represented by a client context. It is - * opaque, so the functions defined in this API should be used to - * fetch data from and manipulate it. - */ -typedef struct HttpClientContext HttpClientContext; - -/** - * Make an HTTP request. This function takes the request method, - * any flags defined in the HTTP API, the port, hostname, and path, - * all in that order. It returns NULL if there was an error making - * the request. Otherwise it returns a client context. Note that this - * function does not actually send any data, it simply makes the - * connection. Use - * .Fn HttpRequestHeader - * to add headers to the request. Then, send headers with - * .Fn HttpRequestSendHeaders . - * Finally, the request body, if any, can be written to the output - * stream, and then the request can be fully sent using - * .Fn HttpRequestSend . - */ -extern HttpClientContext * - HttpRequest(HttpRequestMethod, int, unsigned short, char *, char *); - -/** - * Set a request header to send to the server when making the - * request. - */ -extern void HttpRequestHeader(HttpClientContext *, char *, char *); - -/** - * Send the request headers to the server. This must be called before - * the request body can be written or a response body can be read. - */ -extern void HttpRequestSendHeaders(HttpClientContext *); - -/** - * Flush the request stream to the server. This function should be - * called before the response body is read. - */ -extern HttpStatus HttpRequestSend(HttpClientContext *); - -/** - * Get the headers returned by the server. - */ -extern HashMap * HttpResponseHeaders(HttpClientContext *); - -/** - * Get the stream used to write the request body and read the - * response body. - */ -extern Stream * HttpClientStream(HttpClientContext *); - -/** - * Free all memory associated with the client context. This closes the - * connection, if it was still open. - */ -extern void HttpClientContextFree(HttpClientContext *); - -#endif /* CYTOPLASM_HTTPCLIENT_H */ diff --git a/src/include/HttpRouter.h b/src/include/HttpRouter.h deleted file mode 100644 index 6816178..0000000 --- a/src/include/HttpRouter.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_HTTPROUTER_H -#define CYTOPLASM_HTTPROUTER_H - -/*** - * @Nm HttpRouter - * @Nd Simple HTTP request router with regular expression support. - * @Dd April 29 2023 - * @Xr HttpServer Http - * - * .Nm - * provides a simple mechanism for assigning functions to an HTTP - * request path. It is a simple tree data structure that parses the - * registered request paths and maps functions onto each part of the - * path. Then, requests can be easily routed to their appropriate - * handler functions. - */ - -#include - -/** - * The router structure is opaque and thus managed entirely by the - * functions defined in this API. - */ -typedef struct HttpRouter HttpRouter; - -/** - * A function written to handle an HTTP request takes an array - * consisting of the matched path parts in the order they appear in - * the path, and a pointer to caller-provided arguments, if any. - * It returns a pointer that the caller is assumed to know how to - * handle. - */ -typedef void *(HttpRouteFunc) (Array *, void *); - -/** - * Create a new empty routing tree. - */ -extern HttpRouter * HttpRouterCreate(void); - -/** - * Free all the memory associated with the given routing tree. - */ -extern void HttpRouterFree(HttpRouter *); - -/** - * Register the specified route function to be executed upon requests - * for the specified HTTP path. The path is parsed by splitting at - * each path separator. Each part of the path is a regular expression - * that matches the entire path part. A regular expression cannot - * match more than one path part. This allows for paths like - * .Pa /some/path/(.*)/parts - * to work as one would expect. - */ -extern int HttpRouterAdd(HttpRouter *, char *, HttpRouteFunc *); - -/** - * Route the specified request path using the specified routing - * tree. This function will parse the path and match it to the - * appropriate route handler function. The return value is a boolean - * value that indicates whether or not an appropriate route function - * was found. If an appropriate function was found, then the void - * pointer is passed to it as arguments that it is expected to know - * how to handle, and the pointer to a void pointer is where the - * route function's response will be placed. - */ -extern int HttpRouterRoute(HttpRouter *, char *, void *, void **); - -#endif /* CYTOPLASM_HTTPROUTER_H */ diff --git a/src/include/HttpServer.h b/src/include/HttpServer.h deleted file mode 100644 index f8e449a..0000000 --- a/src/include/HttpServer.h +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_HTTPSERVER_H -#define CYTOPLASM_HTTPSERVER_H - -/*** - * @Nm HttpServer - * @Nd Extremely simple HTTP server. - * @Dd December 13 2022 - * @Xr Http HttpClient - * - * .Nm - * builds on the - * .Xr Http 3 - * API, and provides a very simple, yet very functional API for - * creating an HTTP server. It aims at being easy to use and minimal, - * yet also efficient. It uses non-blocking I/O, is fully - * multi-threaded, and is very configurable. It can be set up in just - * two function calls and minimal supporting code. - * .Pp - * This API should be familar to those that have dealt with the HTTP - * server libraries of other programming languages, particularly Java. - * In fact, much of the terminology used in this API came from Java, - * and you'll notice that the way responses are sent and received very - * closely resembles Java. - */ - -#include - -#include - -#include -#include - -/** - * The functions on this API operate on an opaque structure. - */ -typedef struct HttpServer HttpServer; - -/** - * Each request receives a context structure. It is opaque, so the - * functions defined in this API should be used to fetch data from - * it. These functions allow the handler to figure out the context of - * the request, which includes the path requested, any parameters, - * and the headers and method used to make the request. The context - * also provides the means by which the handler responds to the - * request, allowing it to set the status code, headers, and body. - */ -typedef struct HttpServerContext HttpServerContext; - -/** - * The request handler function is executed when an HTTP request is - * received. It takes a request context, and a pointer as specified - * in the server configuration. - */ -typedef void (HttpHandler) (HttpServerContext *, void *); - -/** - * The number of arguments to - * .Fn HttpServerCreate - * has grown so large that arguments are now stuffed into a - * configuration structure, which is in turn passed to - * .Fn HttpServerCreate . - * This configuration is copied by value into the internal - * structures of the server. It is copied with very minimal - * validation, so ensure that all values are sensible. It may - * make sense to use - * .Fn memset - * to zero out everything in here before assigning values. - */ -typedef struct HttpServerConfig -{ - unsigned short port; - unsigned int threads; - unsigned int maxConnections; - - int flags; /* Http(3) flags */ - char *tlsCert; /* File path */ - char *tlsKey; /* File path */ - - HttpHandler *handler; - void *handlerArgs; -} HttpServerConfig; - -/** - * Create a new HTTP server using the specified configuration. - * This will set up all internal structures used by the server, - * and bind the socket and start listening for connections. However, - * it will not start accepting connections. - */ -extern HttpServer * HttpServerCreate(HttpServerConfig *); - -/** - * Retrieve the configuration that was used to instantiate the given - * server. Note that this configuration is not necessarily the exact - * one that was provided; even though its values are the same, it - * should be treated as an entirely separate configuration with no - * connection to the original. - */ -extern HttpServerConfig * HttpServerConfigGet(HttpServer *); - -/** - * Free the resources associated with the given HTTP server. Note that - * the server can only be freed after it has been stopped. Calling this - * function while the server is still running results in undefined - * behavior. - */ -extern void HttpServerFree(HttpServer *); - -/** - * Attempt to start the HTTP server, and return immediately with the - * status. This API is fully multi-threaded and asynchronous, so the - * caller can continue working while the HTTP server is running in a - * separate thread and managing a pool of threads to handle responses. - */ -extern int HttpServerStart(HttpServer *); - -/** - * Typically, at some point after calling - * .Fn HttpServerStart , - * the program will have no more work to do, so it will want to wait - * for the HTTP server to finish. This is accomplished via this - * function, which joins the HTTP worker thread to the calling thread, - * pausing the calling thread until the HTTP server has stopped. - */ -extern void HttpServerJoin(HttpServer *); - -/** - * Stop the HTTP server. Only the execution of this function will - * cause the proper shutdown of the HTTP server. If the main program - * is joined to the HTTP thread, then either another thread or a - * signal handler will have to stop the server using this function. - * The typical use case is to install a signal handler that executes - * this function on a global HTTP server. - */ -extern void HttpServerStop(HttpServer *); - -/** - * Get the request headers for the request represented by the given - * context. The data in the returned hash map should be treated as - * read only and should not be freed; it is managed entirely by the - * server. - */ -extern HashMap * HttpRequestHeaders(HttpServerContext *); - -/** - * Get the request method used to make the request represented by - * the given context. - */ -extern HttpRequestMethod HttpRequestMethodGet(HttpServerContext *); - -/** - * Get the request path for the request represented by the given - * context. The return value of this function should be treated as - * read-only, and should not be freed; it is managed entirely by the - * server. - */ -extern char * HttpRequestPath(HttpServerContext *); - -/** - * Retrieve the parsed GET parameters for the request represented by - * the given context. The returned hash map should be treated as - * read-only, and should not be freed; it is managed entirely by the - * server. - */ -extern HashMap * HttpRequestParams(HttpServerContext *); - -/** - * Set a response header to return to the client. The old value for - * the given header is returned, if any, otherwise NULL is returned. - */ -extern char * HttpResponseHeader(HttpServerContext *, char *, char *); - -/** - * Set the response status to return to the client. - */ -extern void HttpResponseStatus(HttpServerContext *, HttpStatus); - -/** - * Get the current response status that will be sent to the client - * making the request represented by the given context. - */ -extern HttpStatus HttpResponseStatusGet(HttpServerContext *); - -/** - * Send the response headers to the client that made the request - * represented by the specified context. This function must be called - * before the response body can be written, otherwise a malformed - * response will be sent. - */ -extern void HttpSendHeaders(HttpServerContext *); - -/** - * Get a stream that is both readable and writable. Reading from the - * stream reads the request body that the client sent, if there is one. - * Note that the rquest headers have already been read, so the stream - * is correctly positioned at the beginning of the body of the request. - * .Fn HttpSendHeaders - * must be called before the stream is written, otherwise a malformed - * HTTP response will be sent. An HTTP handler should properly set all - * the headers it itends to send, send those headers, and then write - * the response body to this stream. - * .Pp - * Note that the stream does not need to be closed by the HTTP - * handler; in fact doing so results in undefined behavior. The stream - * is managed entirely by the server itself, so it will close it when - * necessary. This allows the underlying protocol to differ: for - * instance, an HTTP/1.1 connection may stay for multiple requests and - * responses. - */ -extern Stream * HttpServerStream(HttpServerContext *); - -#endif /* CYTOPLASM_HTTPSERVER_H */ diff --git a/src/include/Int.h b/src/include/Int.h deleted file mode 100644 index 7933a69..0000000 --- a/src/include/Int.h +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_INT_H -#define CYTOPLASM_INT_H - -/*** - * @Nm Int - * @Nd Fixed-width integer types. - * @Dd April 27 2023 - * - * This header provides cross-platform, fixed-width integer types. - * Specifically, it uses preprocessor magic to define the following - * types: - * .Bl -bullet -offset indent - * .It - * Int8 and UInt8 - * .It - * Int16 and UInt16 - * .It - * Int32 and UInt32 - * .El - * .Pp - * Note that there is no 64-bit integer type, because the ANSI C - * standard makes no guarantee that such a type will exist, even - * though it does on most platforms. - * .Pp - * The reason Cytoplasm provides its own header for this is - * because ANSI C does not define fixed-width types, and while it - * should be safe to rely on C99 fixed-width types in most cases, - * there may be cases where even that is not possible. - * - * @ignore-typedefs - */ - -#include - -#define BIT32_MAX 4294967295UL -#define BIT16_MAX 65535UL -#define BIT8_MAX 255UL - -#ifndef UCHAR_MAX -#error Size of char data type is unknown. Define UCHAR_MAX. -#endif - -#ifndef USHRT_MAX -#error Size of short data type is unknown. Define USHRT_MAX. -#endif - -#ifndef UINT_MAX -#error Size of int data type is unknown. Define UINT_MAX. -#endif - -#ifndef ULONG_MAX -#error Size of long data type is unknown. Define ULONG_MAX. -#endif - -#if UCHAR_MAX == BIT8_MAX -typedef signed char Int8; -typedef unsigned char UInt8; - -#else -#error Unable to determine suitable data type for 8-bit integers. -#endif - -#if UINT_MAX == BIT16_MAX -typedef signed int Int16; -typedef unsigned int UInt16; - -#elif USHRT_MAX == BIT16_MAX -typedef signed short Int16; -typedef unsigned short UInt16; - -#elif UCHAR_MAX == BIT16_MAX -typedef signed char Int16; -typedef unsigned char UInt16; - -#else -#error Unable to determine suitable data type for 16-bit integers. -#endif - -#if ULONG_MAX == BIT32_MAX -typedef signed long Int32; -typedef unsigned long UInt32; - -#elif UINT_MAX == BIT32_MAX -typedef signed int Int32; -typedef unsigned int UInt32; - -#elif USHRT_MAX == BIT32_MAX -typedef signed short Int32; -typedef unsigned short UInt32; - -#elif UCHAR_MAX == BIT32_MAX -typedef signed char Int32; -typedef unsigned char UInt32; - -#else -#error Unable to determine suitable data type for 32-bit integers. -#endif - -#endif /* CYTOPLASM_INT_H */ diff --git a/src/include/Int64.h b/src/include/Int64.h deleted file mode 100644 index 08083c1..0000000 --- a/src/include/Int64.h +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_INT64_H -#define CYTOPLASM_INT64_H - -/*** - * @Nm Int64 - * @Nd Fixed-width 64 bit integers. - * @Dd August 11, 2023 - * - * .Pp - * ANSI C89 (or C99 for that matter) provides no required mechanism - * for 64 bit integers. Nevertheless, many compilers provide them as - * extensions. However, since it is not a gaurantee, and to be fully - * standards-compliant and thus portable, a platform-agnostic interface - * is required. This header provides such an interface. If the platform - * has a 64 bit integer type, that is used, and native operations are - * performed by C preprocessor macro expansion. Otherwise, a - * compatibility layer is provided, which implements 64-bit - * arithmetic on an array of 2 32-bit numbers which are provided by - * .Xr Int 3 . - * .Pp - * Note that 64-bit emulation is certainly not as performant as using - * native 64-bit operations, so whenever possible, the native - * operations should be preferred. However, since C provides no required - * 64 bit integer on 32-bit and less platforms, this API can be used as - * a "good enough" fallback mechanism. - * .Pp - * Also note that this implementation, both in the native and - * non-native forms, makes some assumptions: - * .Bl -bullet -width Ds - * .It - * When a cast from a larger integer to a smaller integer is performed, - * the upper bits are truncated, not the lower bits. - * .It - * Negative numbers are represented in memory and in registers in two's - * compliment form. - * .El - * .Pp - * This API may provide unexpected output if these assumptions are - * false for a given platform. - * - * @ignore-typedefs - */ - -#include -#include - -#include - -#ifndef INT64_FORCE_EMULATED - -#define BIT64_MAX 18446744073709551615UL - -#if UINT_MAX == BIT64_MAX -typedef signed int Int64; - -#define INT64_NATIVE - -#elif ULONG_MAX == BIT64_MAX -typedef signed long Int64; - -#define INT64_NATIVE - -#endif - -#endif /* ifndef INT64_FORCE_EMULATED */ - -#ifdef INT64_NATIVE - -#define Int64Create(high, low) ((Int64) (((UInt64) (high) << 32) | (low))) -#define Int64Neg(x) (-(x)) - -#define Int64Low(a) ((UInt32) (a)) -#define Int64High(a) ((UInt32) ((a) >> 32)) - -#define Int64Add(a, b) ((a) + (b)) -#define Int64Sub(a, b) ((a) - (b)) -#define Int64Mul(a, b) ((a) * (b)) -#define Int64Div(a, b) ((a) / (b)) -#define Int64Rem(a, b) ((a) % (b)) - -#define Int64Sll(a, b) ((a) << (b)) -#define Int64Sra(a, b) ((a) >> (b)) - -#define Int64And(a, b) ((a) & (b)) -#define Int64Or(a, b) ((a) | (b)) -#define Int64Xor(a, b) ((a) ^ (b)) -#define Int64Not(a) (~(a)) - -#define Int64Eq(a, b) ((a) == (b)) -#define Int64Lt(a, b) ((a) < (b)) -#define Int64Gt(a, b) ((a) > (b)) - -#define Int64Neq(a, b) ((a) != (b)) -#define Int64Leq(a, b) ((a) <= (b)) -#define Int64Geq(a, b) ((a) >= (b)) - -#else - -#define Int64Neg(x) (Int64Add(Int64Not(x), Int64Create(0, 1))) - -/** - * The internal bit representation of a signed integer is identical - * to an unsigned integer, the difference is in the algorithms and - * the way the bits are interpreted. - */ -typedef UInt64 Int64; - -/** - * Create a new signed 64 bit integer using the given high and low - * bits. - */ -extern Int64 Int64Create(UInt32, UInt32); - -/** - * Add two signed 64 bit integers together. - */ -extern Int64 Int64Add(Int64, Int64); - -/** - * Subtract the second 64 bit integer from the first. - */ -extern Int64 Int64Sub(Int64, Int64); - -/** - * Multiply two 64 bit integers together. The non-native version of - * this function uses the Russian Peasant method of multiplication, - * which should afford more performance than a naive multiplication by - * addition, but it is still rather slow and depends on the size of - * the integers being multiplied. - */ -extern Int64 Int64Mul(Int64, Int64); - -/** - * Divide the first 64 bit integer by the second and return the - * quotient. The non-native version of this function uses naive binary - * long division, which is slow, but gauranteed to finish in constant - * time. - */ -extern Int64 Int64Div(Int64, Int64); - -/** - * Divide the first 64 bit integer by the second and return the - * remainder. The non-native version of this function uses naive binary - * long division, which is slow, but gauranteed to finish in constant - * time. - */ -extern Int64 Int64Rem(Int64, Int64); - -/** - * Perform a left logical bit shift of a 64 bit integer. The second - * parameter is how many places to shift, and is declared as a regular - * integer because anything more than 64 does not make sense. - */ -extern Int64 Int64Sll(Int64, int); - -/** - * Perform a right arithmetic bit shift of a 64 bit integer. The second - * parameter is how many places to shift, and is declared as a regular - * integer because anything more than 64 does not make sense. - * .Pp - * Note that on platforms that use the native 64-bit implementation, - * this is technically implementation-defined, and may in fact be a - * logical shift instead of an arithmetic shift. Note that typically - * this operation is not performed on signed integers. - */ -extern Int64 Int64Sra(Int64, int); - -/** - * Perform a bitwise AND (&) of the provided 64 bit integers. - */ -extern Int64 Int64And(Int64, Int64); - -/** - * Perform a bitwise OR (|) of the provided 64 bit integers. - */ -extern Int64 Int64Or(Int64, Int64); - -/** - * Perform a bitwise XOR (^) of the provided 64 bit integers. - */ -extern Int64 Int64Xor(Int64, Int64); - -/** - * Perform a bitwise NOT (~) of the provided 64 bit integer. - */ -extern Int64 Int64Not(Int64); - -/** - * Perform a comparison of the provided 64 bit integers and return a C - * boolean that is true if and only if they are equal. - */ -extern int Int64Eq(Int64, Int64); - -/** - * Perform a comparison of the provided 64 bit integers and return a C - * boolean that is true if and only if the second operand is strictly - * less than the first. - */ -extern int Int64Lt(Int64, Int64); - -/** - * Perform a comparison of the provided 64 bit integers and return a C - * boolean that is true if and only if the second operand is strictly - * greater than the first. - */ -extern int Int64Gt(Int64, Int64); - -#define Int64Low(a) ((a).i[0]) -#define Int64High(a) ((a).i[1]) - -#define Int64Neq(a, b) (!Int64Eq(a, b)) -#define Int64Leq(a, b) (Int64Eq(a, b) || Int64Lt(a, b)) -#define Int64Geq(a, b) (Int64Eq(a, b) || Int64Gt(a, b)) - -#endif - -#define INT64_STRBUF 65 /* Base 2 representation with '\0' */ - -/** - * Convert a 64 bit integer to a string in an arbitrary base - * representation specified by the second parameter, using the provided - * buffer and length specified by the third and fourth parameters. To - * guarantee that the string will fit in the buffer, allocate it of - * size INT64_STRBUF or larger. Note that a buffer size smaller than - * INT64_STRBUF will invoke undefined behavior. - */ -extern size_t Int64Str(Int64, int, char *, size_t); - -#endif /* CYTOPLASM_INT64_H */ diff --git a/src/include/Io.h b/src/include/Io.h deleted file mode 100644 index 06afb77..0000000 --- a/src/include/Io.h +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_IO_H -#define CYTOPLASM_IO_H - -/*** - * @Nm Io - * @Nd Source/sink-agnostic I/O for implementing custom streams. - * @Dd April 29 2023 - * @Xr Stream Tls - * - * Many systems provide platform-specific means of implementing custom - * streams using file pointers. However, POSIX does not define a way - * of programmatically creating custom streams. - * .Nm - * therefore fills this gap in POSIX by mimicking all of the - * functionality of these platform-specific functions, but in pure - * POSIX C. It defines a number of callback funtions to be executed - * in place of the standard POSIX I/O functions, which are used to - * implement arbitrary streams that may not be to a file or socket. - * Additionally, streams can now be pipelined; the sink of one stream - * may be the source of another lower-level stream. Additionally, all - * streams, regardless of their source or sink, share the same API, so - * streams can be handled in a much more generic manner. This allows - * the HTTP client and server libraries to seemlessly support TLS and - * plain connections without having to handle each separately. - * .Pp - * .Nm - * was heavily inspired by GNU's - * .Fn fopencookie - * and BSD's - * .Fn funopen . - * It aims to combine the best of both of these functions into a single - * API that is intuitive and easy to use. - */ - -#include -#include -#include -#include - -#ifndef IO_BUFFER -#define IO_BUFFER 4096 -#endif - -/** - * An opaque structure analogous to a POSIX file descriptor. - */ -typedef struct Io Io; - -/** - * Read input from the source of a stream. This function should - * attempt to read the specified number of bytes of data from the - * given cookie into the given buffer. It should behave identically - * to the POSIX - * .Xr read 2 - * system call, except instead of using an integer descriptor as the - * first parameter, a pointer to an implementation-defined cookie - * stores any information the function needs to read from the source. - */ -typedef ssize_t (IoReadFunc) (void *, void *, size_t); - -/** - * Write output to a sink. This function should attempt to write the - * specified number of bytes of data from the given buffer into the - * stream described by the given cookie. It should behave identically - * to the POSIX - * .Xr write 2 - * system call, except instead of using an integer descriptor as the - * first parameter, a pointer to an implementation-defined cookie - * stores any information the function needs to write to the sink. - */ -typedef ssize_t (IoWriteFunc) (void *, void *, size_t); - -/** - * Repositions the offset of the stream described by the specified - * cookie. This function should behave identically to the POSIX - * .Xr lseek 2 - * system call, except instead of using an integer descriptor as the - * first parameter, a pointer to an implementation-defined cookie - * stores any information the function needs to seek the stream. - */ -typedef off_t (IoSeekFunc) (void *, off_t, int); - -/** - * Close the given stream, making future reads or writes result in - * undefined behavior. This function should also free all memory - * associated with the cookie. It should behave identically to the - * .Xr close 2 - * system call, except instead of using an integer descriptor for the - * parameter, a pointer to an implementation-defined cookie stores any - * information the function needs to close the stream. - */ -typedef int (IoCloseFunc) (void *); - -/** - * A simple mechanism for grouping together a set of stream functions, - * to be passed to - * .Fn IoCreate . - */ -typedef struct IoFunctions -{ - IoReadFunc *read; - IoWriteFunc *write; - IoSeekFunc *seek; - IoCloseFunc *close; -} IoFunctions; - -/** - * Create a new stream using the specified cookie and the specified - * I/O functions. - */ -extern Io * IoCreate(void *, IoFunctions); - -/** - * Read the specified number of bytes from the specified stream into - * the specified buffer. This calls the stream's underlying IoReadFunc, - * which should behave identically to the POSIX - * .Xr read 2 - * system call. - */ -extern ssize_t IoRead(Io *, void *, size_t); - -/** - * Write the specified number of bytes from the specified stream into - * the specified buffer. This calls the stream's underlying - * IoWriteFunc, which should behave identically to the POSIX - * .Xr write 2 - * system call. - */ -extern ssize_t IoWrite(Io *, void *, size_t); - -/** - * Seek the specified stream using the specified offset and whence - * value. This calls the stream's underlying IoSeekFunc, which should - * behave identically to the POSIX - * .Xr lseek 2 - * system call. - */ -extern off_t IoSeek(Io *, off_t, int); - -/** - * Close the specified stream. This calls the stream's underlying - * IoCloseFunc, which should behave identically to the POSIX - * .Xr close 2 - * system call. - */ -extern int IoClose(Io *); - -/** - * Print a formatted string to the given stream. This is a - * re-implementation of the standard library function - * .Xr vfprintf 3 , - * and behaves identically. - */ -extern int IoVprintf(Io *, const char *, va_list); - -/** - * Print a formatted string to the given stream. This is a - * re-implementation of the standard library function - * .Xr fprintf 3 , - * and behaves identically. - */ -extern int IoPrintf(Io *, const char *,...); - -/** - * Read all the bytes from the first stream and write them to the - * second stream. Neither stream is closed upon the completion of this - * function. This can be used for quick and convenient buffered - * copying of data from one stream into another. - */ -extern ssize_t IoCopy(Io *, Io *); - -/** - * Wrap a POSIX file descriptor to take advantage of this API. The - * common use case for this function is when a regular file descriptor - * needs to be accessed by an application that uses this API to also - * access non-POSIX streams. - */ -extern Io * IoFd(int); - -/** - * Open or create a file for reading or writing. The specified file - * name is opened for reading or writing as specified by the given - * flags and mode. This function is a simple convenience wrapper around - * the POSIX - * .Xr open 2 - * system call that passes the opened file descriptor into - * .Fn IoFd . - */ -extern Io * IoOpen(const char *, int, mode_t); - -/** - * Wrap a standard C file pointer to take advantage of this API. The - * common use case for this function is when a regular C file pointer - * needs to be accessed by an application that uses this API to also - * access custom streams. - */ -extern Io * IoFile(FILE *); - -#endif /* CYTOPLASM_IO_H */ diff --git a/src/include/Json.h b/src/include/Json.h deleted file mode 100644 index 4185260..0000000 --- a/src/include/Json.h +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef CYTOPLASM_JSON_H -#define CYTOPLASM_JSON_H - -/*** - * @Nm Json - * @Nd A fully-featured JSON API. - * @Dd March 12 2023 - * @Xr HashMap Array Stream - * - * .Nm - * is a fully-featured JSON API for C using the array and hash map - * APIs. It can parse JSON, ans serialize an in-memory structure to - * JSON. It build on the foundation of Array and HashMap because that's - * all JSON really is, just arrays and maps. - * .Nm - * also provides a structure for encapsulating an arbitrary value and - * identifying its type, making it easy for a strictly-typed language - * like C to work with loosely-typed JSON data. - * .Nm - * is very strict and tries to adhere as closely as possible to the - * proper definition of JSON. It will fail on syntax errors of any - * kind, which is fine for a Matrix homeserver that can just return - * M_BAD_JSON if anything in here fails, but this behavior may not be - * suitable for other purposes. - * .Pp - * This JSON implementation focuses primarily on serialization and - * deserialization to and from streams. It does not provide facilities - * for handling JSON strings; it only writes JSON to output streams, - * and reads them from input streams. Of course, you can use the - * POSIX - * .Xr fmemopen 3 - * and - * .Xr open_memstream 3 - * functions if you want to deal with JSON strings, but JSON is - * intended to be an exchange format. Data should be converted to JSON - * right when it is leaving the program, and converted from JSON to the - * in-memory format as soon as it is coming in. - * .Pp - * JSON objects are represented as hash maps consisting entirely of - * JsonValue structures, and arrays are represented as arrays - * consisting entirely of JsonValue structures. When generating a - * JSON object, any attempt to stuff a value into a hash map or array - * without first encoding it as a JsonValue will result in undefined - * behavior. - */ - -#include -#include -#include -#include - -#include -#include - -#define JSON_DEFAULT -1 -#define JSON_PRETTY 0 - -/** - * This opaque structure encapsulates all the possible types that can - * be stored in JSON. It is managed entirely by the functions defined - * in this API. It is important to note that strings, integers, floats, - * booleans, and the NULL value are all stored by value, but objects - * and arrays are stored by reference. That is, it doesn't store these - * itself, just pointers to them, however, the data - * .Em is - * freed when using - * .Fn JsonFree . - */ -typedef struct JsonValue JsonValue; - -/** - * These are the types that can be used to identify a JsonValue - * and act on it accordingly. - */ -typedef enum JsonType -{ - JSON_NULL, /* Maps to a C NULL */ - JSON_OBJECT, /* Maps to a HashMap of JsonValues */ - JSON_ARRAY, /* Maps to an Array of JsonValues */ - JSON_STRING, /* Maps to a null-terminated C string */ - JSON_INTEGER, /* Maps to an Int64 */ - JSON_FLOAT, /* Maps to a C double */ - JSON_BOOLEAN /* Maps to a C integer of either 0 or 1 */ -} JsonType; - -/** - * Determine the type of the specified JSON value. - */ -extern JsonType JsonValueType(JsonValue *); - -/** - * Encode a JSON object as a JSON value that can be added to another - * object, or an array. - */ -extern JsonValue * JsonValueObject(HashMap *); - -/** - * Unwrap a JSON value that represents an object. This function will - * return NULL if the value is not actually an object. - */ -extern HashMap * JsonValueAsObject(JsonValue *); - -/** - * Encode a JSON array as a JSON value that can be added to an object - * or another array. - */ -extern JsonValue * JsonValueArray(Array *); - -/** - * Unwrap a JSON value that represents an array. This function will - * return NULL if the value is not actually an array. - */ -extern Array * JsonValueAsArray(JsonValue *); - -/** - * Encode a C string as a JSON value that can be added to an object or - * an array. - */ -extern JsonValue * JsonValueString(char *); - -/** - * Unwrap a JSON value that represents a string. This function will - * return NULL if the value is not actually a string. - */ -extern char * JsonValueAsString(JsonValue *); - -/** - * Encode a number as a JSON value that can be added to an object or - * an array. - */ -extern JsonValue * JsonValueInteger(Int64); - -/** - * Unwrap a JSON value that represents a number. This function will - * return 0 if the value is not actually a number, which may be - * misleading. Check the type of the value before making assumptions - * about its value. - */ -extern Int64 JsonValueAsInteger(JsonValue *); - -/** - * Encode a floating point number as a JSON value that can be added - * to an object or an array. - */ -extern JsonValue * JsonValueFloat(double); - -/** - * Unwrap a JSON value that represents a floating point number. This - * function will return 0 if the value is not actually a floating - * point number, which may be misleading. Check the type of the value - * before making assumptions about its type. - */ -extern double JsonValueAsFloat(JsonValue *); - -/** - * Encode a C integer according to the way C treats integers in boolean - * expressions as a JSON value that can be added to an object or an - * array. - */ -extern JsonValue * JsonValueBoolean(int); - -/** - * Unwrap a JSON value that represents a boolean. This function will - * return 0 if the value is not actually a boolean, which may be - * misleading. Check the type of the value before making assumptions - * about its type. - */ -extern int JsonValueAsBoolean(JsonValue *); - -/** - * This is a special case that represents a JSON null. Because the - * Array and HashMap APIs do not accept NULL values, this function - * should be used to represent NULL in JSON. Even though a small - * amount of memory is allocated just to be a placeholder for nothing, - * this keeps the APIs clean. - */ -extern JsonValue * JsonValueNull(void); - -/** - * Free the memory being used by a JSON value. Note that this will - * recursively free all Arrays, HashMaps, and other JsonValues that are - * reachable from the given value, including any strings attached to - * this value. - */ -extern void JsonValueFree(JsonValue *); - -/** - * Recursively duplicate the given JSON value. This returns a new - * JSON value that is completely identical to the specified value, but - * in no way connected to it. - */ -extern JsonValue * JsonValueDuplicate(JsonValue *); - -/** - * Recursively duplicate the given JSON object. This returns a new - * JSON object that is completely identical to the specified object, - * but in no way connect to it. - */ -extern HashMap * JsonDuplicate(HashMap *); - -/** - * Recursively free a JSON object by iterating over all of its values - * and freeing them using - * .Fn JsonValueFree . - */ -extern void JsonFree(HashMap *); - -/** - * Encode the given string in such a way that it can be safely - * embedded in a JSON stream. This entails: - * .Bl -bullet -offset indent - * .It - * Escaping quotes, backslashes, and other special characters using - * their backslash escape. - * .It - * Encoding bytes that are not UTF-8 using escapes. - * .It - * Wrapping the entire string in double quotes. - * .El - * .Pp - * This function is only provided via the public - * .Nm - * API so that it is accessible to custom JSON encoders, such as the - * CanonicalJson encoder. This will typically be used for encoding - * object keys; to encode values, just use - * .Fn JsonEncodeValue . - * .Pp - * This function returns the number of bytes written to the stream, - * or if the stream is NULL, the number of bytes that would have - * been written. - */ -extern int JsonEncodeString(const char *, Stream *); - -/** - * Serialize a JSON value as it would appear in JSON output. This is - * a recursive function that also encodes all child values reachable - * from the given value. This function is exposed via the public - * .Nm - * API so that it is accessible to custom JSON encoders. Normal users - * that are not writing custom encoders should in most cases just use - * .Fn JsonEncode - * to encode an entire object. - * .Pp - * The third parameter is an integer that represents the indent level - * of the value to be printed, or a negative number if pretty-printing - * should be disabled and JSON should be printed as minimized as - * possible. To pretty-print a JSON object, set this to - * .Va JSON_PRETTY . - * To get minified output, set it to - * .Va JSON_DEFAULT . - * .Pp - * This function returns the number of bytes written to the stream, - * or if the stream is NULL, the number of bytes that would have - * been written. - */ -extern int JsonEncodeValue(JsonValue *, Stream *, int); - -/** - * Encode a JSON object as it would appear in JSON output, writing it - * to the given output stream. This function is recursive; it will - * serialize everything accessible from the passed object. The third - * parameter has the same behavior as described above. - * .Pp - * This function returns the number of bytes written to the stream, - * or if the stream is NULL, the number of bytes that would have - * been written. - */ -extern int JsonEncode(HashMap *, Stream *, int); - -/** - * Decode a JSON object from the given input stream and parse it into - * a hash map of JSON values. - */ -extern HashMap * JsonDecode(Stream *); - -/** - * A convenience function that allows the caller to retrieve and - * arbitrarily deep keys within a JSON object. It takes a root JSON - * object, the number of levels deep to go, and then that number of - * keys as a varargs list. All keys must have objects as values, with - * the exception of the last one, which is the value that will be - * returned. Otherwise, NULL indicates the specified path doas not - * exist. - */ -extern JsonValue * JsonGet(HashMap *, size_t,...); - -/** - * A convenience function that allows the caller to set arbitrarily - * deep keys within a JSON object. It takes a root JSON object, the - * number of levels deep to go, and then that number of keys as a - * varargs list. All keys must have object as values, with the - * exception of the last one, which is the value that will be set. - * The value currently at that key, if any, will be returned. - * This function will create any intermediate objects as necessary to - * set the proper key. - */ -extern JsonValue * JsonSet(HashMap *, JsonValue *, size_t,...); - -#endif /* CYTOPLASM_JSON_H */ diff --git a/src/include/Log.h b/src/include/Log.h deleted file mode 100644 index d0300ca..0000000 --- a/src/include/Log.h +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef CYTOPLASM_LOG_H -#define CYTOPLASM_LOG_H - -/*** - * @Nm Log - * @Nd A simple logging framework for logging to multiple destinations. - * @Dd April 27 2023 - * @Xr Stream - * - * .Nm - * is a simple C logging library that allows for colorful outputs, - * timestamps, and custom log levels. It also features the ability to - * have multiple logs open at one time, although Cytoplasm primarily - * utilizes the global log. All logs are thread safe. - */ - -#include -#include -#include - -#include - -#define LOG_FLAG_COLOR (1 << 0) -#define LOG_FLAG_SYSLOG (1 << 1) - -/** - * A log is defined as a configuration that describes the properties - * of the log. This opaque structure can be manipulated by the - * functions defined in this API. - */ -typedef struct LogConfig LogConfig; - -/** - * Create a new log configuration with sane defaults that can be used - * immediately with the logging functions. - */ -extern LogConfig * LogConfigCreate(void); - -/** - * Get the global log configuration, creating a new one with - * .Fn LogConfigCreate - * if necessary. - */ -extern LogConfig * LogConfigGlobal(void); - -/** - * Free the given log configuration. Note that this does not close the - * underlying stream associated with the log, if there is one. Also - * note that to avoid memory leaks, the global log configuration must - * also be freed, but it cannot be used after it is freed. - */ -extern void LogConfigFree(LogConfig *); - -/** - * Set the current log level on the specified log configuration. - * This indicates that only messages at or above this level should be - * logged; all others are silently discarded. The passed log level - * should be one of the log levels defined by - * .Xr syslog 3 . - * Refer to that page for a complete list of acceptable log levels, - * and note that passing an invalid log level will result in undefined - * behavior. - */ -extern void LogConfigLevelSet(LogConfig *, int); - -/** - * Cause the log output to be indented two more spaces than it was - * previously. This can be helpful when generating stack traces or - * other hierarchical output. This is a simple convenience wrapper - * around - * .Fn LogConfigIndentSet . - */ -extern void LogConfigIndent(LogConfig *); - -/** - * Cause the log output to be indented two less spaces than it was - * previously. This is a simple convenience wrapper around - * .Fn LogConfigIndentSet . - */ -extern void LogConfigUnindent(LogConfig *); - -/** - * Indent future log output using the specified config by some - * arbitrary amount. - */ -extern void LogConfigIndentSet(LogConfig *, size_t); - -/** - * Set the file stream that logging output should be written to. This - * defaults to standard output, but it can be set to standard error, - * or any other arbitrary stream. Passing a NULL value for the stream - * pointer sets the log output to the standard output. Note that the - * output stream is only used if - * .Va LOG_FLAG_SYSLOG - * is not set. - */ -extern void LogConfigOutputSet(LogConfig *, Stream *); - -/** - * Set a number of boolean options on a log configuration. This - * function uses bitwise operators, so multiple options can be set with - * a single function call using bitwise OR operators. The flags are - * defined as preprocessor macros, and are as follows: - * .Bl -tag -width Ds - * .It LOG_FLAG_COLOR - * When set, enable color-coded output on TTYs. Note that colors are - * implemented as ANSI escape sequences, and are not written to file - * streams that are not actually connected to a TTY, to prevent those - * sequences from being written to a file. - * .Xr isatty 3 - * is checked before writing any terminal sequences. - * .It LOG_FLAG_SYSLOG - * When set, log output to the syslog using - * .Xr syslog 3 , - * instead of logging to the file set by - * .Fn LogConfigOutputSet . - * This flag always overrides the stream set by that function, - * regardless of when it was set, even if it was set after this flag - * was set. - * .El - */ -extern void LogConfigFlagSet(LogConfig *, int); - -/** - * Clear a boolean flag from the specified log format. See above for - * the list of flags. - */ -extern void LogConfigFlagClear(LogConfig *, int); - -/** - * Set a custom timestamp to be prepended to each message if the - * output is not going to the system log. Consult your system's - * documentation for - * .Xr strftime 3 . - * A value of NULL disables the timestamp output before messages. - */ -extern void LogConfigTimeStampFormatSet(LogConfig *, char *); - -/** - * This function does the actual logging of messages using a - * specified configuration. It takes the configuration, the log - * level, a format string, and then a list of arguments, all in that - * order. This function only logs messages if their level is above - * or equal to the currently configured log level, making it easy to - * turn some messages on or off. - * .Pp - * This function has the same usage as - * .Xr vprintf 3 . - * Consult that page for the list of format specifiers and their - * arguments. This function is typically not used directly, see the - * other log functions for the most common use cases. - */ -extern void Logv(LogConfig *, int, const char *, va_list); - -/** - * Log a message using - * .Fn Logv . - * with the specified configuration. This function has the same usage - * as - * .Xr printf 3 . - */ -extern void LogTo(LogConfig *, int, const char *, ...); - -/** - * Log a message to the global log using - * .Fn Logv . - * This function has the same usage as - * .Xr printf 3 . - */ -extern void Log(int, const char *, ...); - -#endif diff --git a/src/include/Memory.h b/src/include/Memory.h deleted file mode 100644 index 5d5ecec..0000000 --- a/src/include/Memory.h +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_MEMORY_H -#define CYTOPLASM_MEMORY_H - -/*** - * @Nm Memory - * @Nd Smart memory management. - * @Dd January 9 2023 - * - * .Nm - * is an API that allows for smart memory management and profiling. It - * wraps the standard library functions - * .Xr malloc 3 , - * .Xr realloc 3 , - * and - * .Xr free 3 , - * and offers identical semantics, while providing functionality that - * the standard library doesn't have, such as getting statistics on the - * total memory allocated on the heap, and getting the size of a block - * given a pointer. Additionally, thanks to preprocessor macros, the - * exact file and line number at which an allocation, re-allocation, or - * free occured can be obtained given a pointer. Finally, all the - * blocks allocated on the heap can be iterated and evaluated, and a - * callback function can be executed every time a memory operation - * occurs. - * .Pp - * In the future, this API could include a garbage collector that - * automatically frees memory it detects as being no longer in use. - * However, this feature does not yet exist. - * .Pp - * A number of macros are available, which make the - * .Nm - * API much easier to use. They are as follows: - * .Bl -bullet -offset indent - * .It - * .Fn Malloc "x" - * .It - * .Fn Realloc "x" "y" - * .It - * .Fn Free "x" - * .El - * .Pp - * These macros expand to - * .Fn MemoryAllocate , - * .Fn MemoryReallocate , - * and - * .Fn MemoryFree - * with the second and third parameters set to __FILE__ and __LINE__. - * This allows - * .Nm - * to be used exactly how the standard library functions would be - * used. In fact, the functions to which these macros expand are not - * intended to be used directly; for the best results, use these - * macros. - */ -#include - -/** - * These values are passed into the memory hook function to indicate - * the action that just happened. - */ -typedef enum MemoryAction -{ - MEMORY_ALLOCATE, - MEMORY_REALLOCATE, - MEMORY_FREE, - MEMORY_BAD_POINTER, - MEMORY_CORRUPTED -} MemoryAction; - -#define Malloc(x) MemoryAllocate(x, __FILE__, __LINE__) -#define Realloc(x, s) MemoryReallocate(x, s, __FILE__, __LINE__) -#define Free(x) MemoryFree(x, __FILE__, __LINE__) - -/** - * The memory information is opaque, but can be accessed using the - * functions defined by this API. - */ -typedef struct MemoryInfo MemoryInfo; - -/** - * Allocate the specified number of bytes on the heap. This function - * has the same semantics as - * .Xr malloc 3 , - * except that it takes the file name and line number at which the - * allocation occurred. - */ -extern void * MemoryAllocate(size_t, const char *, int); - -/** - * Change the size of the object pointed to by the given pointer - * to the given number of bytes. This function has the same semantics - * as - * .Xr realloc 3 , - * except that it takes the file name and line number at which the - * reallocation occurred. - */ -extern void * MemoryReallocate(void *, size_t, const char *, int); - -/** - * Free the memory at the given pointer. This function has the same - * semantics as - * .Xr free 3 , - * except that it takes the file name and line number at which the - * free occurred. - */ -extern void MemoryFree(void *, const char *, int); - -/** - * Get the total number of bytes that the program has allocated on the - * heap. This operation iterates over all heap allocations made with - * .Fn MemoryAllocate - * and then returns a total count, in bytes. - */ -extern size_t MemoryAllocated(void); - -/** - * Iterate over all heap allocations made with - * .Fn MemoryAllocate - * and call - * .Fn MemoryFree - * on them. This function immediately invalidates all pointers to - * blocks on the heap, and any subsequent attempt to read or write to - * data on the heap will result in undefined behavior. This is - * typically called at the end of the program, just before exit. - */ -extern void MemoryFreeAll(void); - -/** - * Fetch information about an allocation. This function takes a raw - * pointer, and if - * . Nm - * knows about the pointer, it returns a structure that can be used - * to obtain information about the block of memory that the pointer - * points to. - */ -extern MemoryInfo * MemoryInfoGet(void *); - -/** - * Get the size in bytes of the block of memory represented by the - * specified memory info structure. - */ -extern size_t MemoryInfoGetSize(MemoryInfo *); - -/** - * Get the file name in which the block of memory represented by the - * specified memory info structure was allocated. - */ -extern const char * MemoryInfoGetFile(MemoryInfo *); - -/** - * Get the line number on which the block of memory represented by the - * specified memory info structure was allocated. - */ -extern int MemoryInfoGetLine(MemoryInfo *); - -/** - * Get a pointer to the block of memory represented by the specified - * memory info structure. - */ -extern void * MemoryInfoGetPointer(MemoryInfo *); - -/** - * This function takes a pointer to a function that takes the memory - * info structure, as well as a void pointer for caller-provided - * arguments. It iterates over all the heap memory currently allocated - * at the time of calling, executing the function on each allocation. - */ -extern void MemoryIterate(void (*) (MemoryInfo *, void *), void *); - -/** - * Specify a function to be executed whenever a memory operation - * occurs. The MemoryAction argument specifies the operation that - * occurred on the block of memory represented by the memory info - * structure. The function also takes a void pointer to caller-provided - * arguments. - */ -extern void MemoryHook(void (*) (MemoryAction, MemoryInfo *, void *), void *); - -/** - * The default memory hook, which has sane behavior and is installed - * at runtime. This function does not use any memory on the heap, - * except for the MemoryInfo passed to it, which it assumes to be - * valid. Everything else happens on the stack only, to ensure that - * the hook doesn't make any memory problems worse. - */ -extern void MemoryDefaultHook(MemoryAction, MemoryInfo *, void *); - -/** - * Read over the block of memory represented by the given memory info - * structure and generate a hexadecimal and ASCII string for each - * chunk of the block. This function takes a callback function that - * takes the following parameters in order: - * .Bl -bullet -offset indent - * .It - * The current offset from the beginning of the block of memory in - * bytes. - * .It - * A null-terminated string containing the next 16 bytes of the block - * encoded as space-separated hex values. - * .It - * A null-terminated string containing the ASCII representation of the - * same 16 bytes of memory. This ASCII representation is safe to print - * to a terminal or other text device, because non-printable characters - * are encoded as a . (period). - * .It - * Caller-passed pointer. - * .El - */ -extern void -MemoryHexDump(MemoryInfo *, void (*) (size_t, char *, char *, void *), void *); - -#endif diff --git a/src/include/Queue.h b/src/include/Queue.h deleted file mode 100644 index 35821d3..0000000 --- a/src/include/Queue.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_QUEUE_H -#define CYTOPLASM_QUEUE_H - -/*** - * @Nm Queue - * @Nd A simple static queue data structure. - * @Dd November 25 2022 - * @Xr Array HashMap - * - * .Nm - * implements a simple queue data structure that is statically sized. - * This implementation does not actually store the values of the items - * in it; it only stores pointers to the data. As such, you will have - * to manually maintain data and make sure it remains valid as long as - * it is in the queue. The advantage of this is that - * .Nm - * doesn't have to copy data, and thus doesn't care how big the data - * is. Furthermore, any arbitrary data can be stored in the queue. - * .Pp - * This queue implementation operates on the heap. It is a circular - * queue, and it does not grow as it is used. Once the size is set, - * the queue never gets any bigger. - */ - -#include - -/** - * These functions operate on a queue structure that is opaque to the - * caller. - */ -typedef struct Queue Queue; - -/** - * Allocate a new queue that is able to store the specified number of - * items in it. - */ -extern Queue * QueueCreate(size_t); - -/** - * Free the memory associated with the specified queue structure. Note - * that this function does not free any of the values stored in the - * queue; it is the caller's job to manage memory for each item. - * Typically, the caller would dequeue all the items in the queue and - * deal with them before freeing the queue itself. - */ -extern void QueueFree(Queue *); - -/** - * Push an element into the queue. This function returns a boolean - * value indicating whether or not the push succeeded. Pushing items - * into the queue will fail if the queue is full. - */ -extern int QueuePush(Queue *, void *); - -/** - * Pop an element out of the queue. This function returns NULL if the - * queue is empty. Otherwise, it returns a pointer to the item that is - * next up in the queue. - */ -extern void * QueuePop(Queue *); - -/** - * Retrieve a pointer to the item that is next up in the queue without - * actually discarding it, such that the next call to - * .Fn QueuePeek - * or - * .Fn QueuePop - * will return the same pointer. - */ -extern void * QueuePeek(Queue *); - -/** - * Determine whether or not the queue is full. - */ -extern int QueueFull(Queue *); - -/** - * Determine whether or not the queue is empty. - */ -extern int QueueEmpty(Queue *); - -#endif diff --git a/src/include/Rand.h b/src/include/Rand.h deleted file mode 100644 index a5be3ce..0000000 --- a/src/include/Rand.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef CYTOPLASM_RAND_H -#define CYTOPLASM_RAND_H - -/*** - * @Nm Rand - * @Nd Thread-safe random numbers. - * @Dd February 16 2023 - * @Xr Util - * - * .Nm - * is used for generating random numbers in a thread-safe way. - * Currently, one generator state is shared across all threads, which - * means that only one thread can generate random numbers at a time. - * This state is protected with a mutex to guarantee this behavior. - * In the future, a seed pool may be maintained to allow multiple - * threads to generate random numbers at the same time. - * .Pp - * The generator state is seeded on the first call to a function that - * needs it. The seed is determined by the current timestamp, the ID - * of the process, and the thread ID. These should all be sufficiently - * random sources, so the seed should be secure enough. - * .Pp - * .Nm - * currently uses a simple Mersenne Twister algorithm to generate - * random numbers. This algorithm was chosen because it is extremely - * popular and widespread. While it is likely not cryptographically - * secure, and does suffer some unfortunate pitfalls, this algorithm - * has stood the test of time and is simple enough to implement, so - * it was chosen over the alternatives. - * .Pp - * .Nm - * does not use any random number generator functions from the C - * standard library, since these are often flawed. - */ - -#include - -/** - * Generate a single random integer between 0 and the passed value. - */ -extern int RandInt(unsigned int); - -/** - * Generate the number of integers specified by the second argument - * storing them into the buffer pointed to in the first argument. - * Ensure that each number is between 0 and the third argument. - * .Pp - * This function allows a caller to get multiple random numbers at once - * in a more efficient manner than repeatedly calling - * .Fn RandInt , - * since each call to these functions - * has to lock and unlock a mutex. It is therefore better to obtain - * multiple random numbers in one pass if multiple are needed. - */ -extern void RandIntN(int *, size_t, unsigned int); - -#endif /* CYTOPLASM_RAND_H */ diff --git a/src/include/Runtime.h b/src/include/Runtime.h deleted file mode 100644 index 6e13b6a..0000000 --- a/src/include/Runtime.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_RUNTIME_H -#define CYTOPLASM_RUNTIME_H - -/*** - * @Nm Runtime - * @Nd Supporting functions for the Cytoplasm runtime. - * @Dd May 23 2023 - * @Xr Memory - * - * .Nm - * provides supporting functions for the Cytoplasm runtime. These - * functions are not intended to be called directly by programs, - * but are used internally. They're exposed via a header because - * the runtime stub needs to know their definitions. - */ - -#include - -/** - * Write a memory report to a file in the current directory, using - * the provided program arguments, including the program name that - * executed. This function is to be called after all memory is - * supposed to have been freed. It iterates over all remaining - * memory and generates a text file containing all of the - * recorded information about each block, including a hex dump of - * the data stored in them. - */ -extern void GenerateMemoryReport(int argc, char **argv); - -#endif /* CYTOPLASM_RUNTIME_H */ diff --git a/src/include/Sha.h b/src/include/Sha.h deleted file mode 100644 index 25b6247..0000000 --- a/src/include/Sha.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef CYTOPLASM_SHA_H -#define CYTOPLASM_SHA_H - -/*** - * @Nm Sha - * @Nd A simple implementation of a few SHA hashing functions. - * @Dd December 19 2022 - * @Xr Memory Base64 - * - * This API defines simple functions for computing SHA hashes. - * At the moment, it only defines - * .Fn Sha256 - * and - * .Fn Sha1 , - * which compute the SHA-256 and SHA-1 hashes of the given C string, - * respectively. It is not trivial to implement SHA-512 in ANSI C - * due to the lack of a 64-bit integer type, so that hash - * function has been omitted. - */ - -/** - * This function takes a pointer to a NULL-terminated C string, and - * returns a NULL-terminated byte buffer allocated on the heap using - * the Memory API, or NULL if there was an error allocating memory. - * The returned byte buffer should be freed when it is no longer - * needed. It is important to note that the returned buffer is not - * a printable string; to get a printable string, use - * .Fn ShaToHex . - */ -extern unsigned char * Sha256(char *); - -/** - * This function takes a pointer to a NULL-terminated C string, and - * returns a NULL-terminated byte buffer allocated on the heap using - * the Memory API, or NULL if there was an error allocating memory. - * The returned byte buffer should be freed when it is no longer - * needed. It is important to note that the returned buffer is not - * a printable string; to get a printable string, use - * .Fn ShaToHex . - */ -extern unsigned char * Sha1(char *); - -/** - * Convert a SHA byte buffer into a hex string. These hex strings - * are typically what is transmitted, stored, and compared, however - * there may be times when it is necessary to work with the raw - * bytes directly, which is why the conversion to a hex string is - * a separate step. - */ -extern char * ShaToHex(unsigned char *); - -#endif /* CYTOPLASM_SHA_H */ diff --git a/src/include/Str.h b/src/include/Str.h deleted file mode 100644 index 714b8d8..0000000 --- a/src/include/Str.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_STR_H -#define CYTOPLASM_STR_H - -/*** - * @Nm Str - * @Nd Functions for creating and manipulating strings. - * @Dd February 15 2023 - * @Xr Memory - * - * .Nm - * provides string-related functions. It is called - * .Nm , - * not String, because some platforms (Windows) do not have - * case-sensitive filesystems, which poses a problem since - * .Pa string.h - * is a standard library header. - */ - -#include - -#include - -/** - * Convert UTF-16 into a Unicode codepoint. - */ -extern UInt32 StrUtf16Decode(UInt16, UInt16); - -/** - * Take a Unicode codepoint and encode it into a string buffer containing - * between 1 and 4 bytes. The string buffer is allocated on the heap, - * so it should be freed when it is no longer needed. - */ -extern char * StrUtf8Encode(UInt32); - -/** - * Duplicate a null-terminated string, returning a new string on the - * heap. This is useful when a function takes in a string that it needs - * to store for long amounts of time, even perhaps after the original - * string is gone. - */ -extern char * StrDuplicate(const char *); - -/** - * Extract part of a null-terminated string, returning a new string on - * the heap containing only the requested subsection. Like the - * substring functions included with most programming languages, the - * starting index is inclusive, and the ending index is exclusive. - */ -extern char * StrSubstr(const char *, size_t, size_t); - -/** - * A varargs function that takes a number of null-terminated strings - * specified by the first argument, and returns a new string that - * contains their concatenation. It works similarly to - * .Xr strcat 3 , - * but it takes care of allocating memory big enough to hold all the - * strings. Any string in the list may be NULL. If a NULL pointer is - * passed, it is treated like an empty string. - */ -extern char * StrConcat(size_t,...); - -/** - * Return a boolean value indicating whether or not the null-terminated - * string consists only of blank characters, as determined by - * .Xr isblank 3 . - */ -extern int StrBlank(const char *str); - -/** - * Generate a string of the specified length, containing random - * lowercase and uppercase letters. - */ -extern char * StrRandom(size_t); - -/** - * Convert the specified integer into a string, returning the string - * on the heap, or NULL if there was a memory allocation error. The - * returned string should be freed by the caller after it is no longer - * needed. - */ -extern char * StrInt(long); - -/** - * Converts a string into a lowercase version of it using - * .Xr tolower 3 , - * returning the lowercase version on the heap, or NULL if there was - * a memory allocation error. The returned string should be freed by - * the caller after it is no longer needed. - */ -extern char * StrLower(char *); - -/** - * Compare two strings and determine whether or not they are equal. - * This is the most common use case of strcmp() in Cytoplasm, but - * strcmp() doesn't like NULL pointers, so these have to be checked - * explicitly and can cause problems if they aren't. This function, - * on the other hand, makes NULL pointers special cases. If both - * arguments are NULL, then they are considered equal. If only one - * argument is NULL, they are considered not equal. Otherwise, if - * no arguments are NULL, a regular strcmp() takes place and this - * function returns a boolean value indicating whether or not - * strcmp() returned 0. - */ -extern int StrEquals(const char *, const char *); - -#endif /* CYTOPLASM_STR_H */ diff --git a/src/include/Stream.h b/src/include/Stream.h deleted file mode 100644 index 3ddacc6..0000000 --- a/src/include/Stream.h +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_STREAM_H -#define CYTOPLASM_STREAM_H - -/*** - * @Nm Stream - * @Nd An abstraction over the Io API that implements standard C I/O. - * @Dd April 29 2023 - * @Xr Io - * - * .Nm - * implements an abstraction layer over the Io API. This layer buffers - * I/O and makes it much easier to work with, mimicking the standard - * C library and offering some more convenience features. - */ - -#include - -#include - -/** - * An opaque structure analogous to C's FILE pointers. - */ -typedef struct Stream Stream; - -/** - * Create a new stream using the specified Io for underlying I/O - * operations. - */ -extern Stream * StreamIo(Io * io); - -/** - * Create a new stream using the specified POSIX file descriptor. - * This is a convenience function for calling - * .Fn IoFd - * and then passing the result into - * .Fn StreamIo . - */ -extern Stream * StreamFd(int); - -/** - * Create a new stream using the specified C FILE pointer. This is a - * convenience function for calling - * .Fn IoFile - * and then passing the result into - * .Fn StreamIo . - */ -extern Stream * StreamFile(FILE *); - -/** - * Create a new stream using the specified path and mode. This is a - * convenience function for calling - * .Xr fopen 3 - * and then passing the result into - * .Fn StreamFile . - */ -extern Stream * StreamOpen(const char *, const char *); - -/** - * Get a stream that writes to the standard output. - */ -extern Stream * StreamStdout(void); - -/** - * Get a stream that writes to the standard error. - */ -extern Stream * StreamStderr(void); - -/** - * Get a stream that reads from the standard input. - */ -extern Stream * StreamStdin(void); - -/** - * Close the stream. This flushes the buffers and closes the underlying - * Io. It is analogous to the standard - * .Xr fclose 3 - * function. - */ -extern int StreamClose(Stream *); - -/** - * Print a formatted string. This function is analogous to the standard - * .Xr vfprintf 3 - * function. - */ -extern int StreamVprintf(Stream *, const char *, va_list); - -/** - * Print a formatted string. This function is analogous to the - * standard - * .Xr fprintf 3 - * function. - */ -extern int StreamPrintf(Stream *, const char *,...); - -/** - * Get a single character from a stream. This function is analogous to - * the standard - * .Xr fgetc 3 - * function. - */ -extern int StreamGetc(Stream *); - -/** - * Push a character back onto the input stream. This function is - * analogous to the standard - * .Xr ungetc 3 - * function. - */ -extern int StreamUngetc(Stream *, int); - -/** - * Write a single character to the stream. This function is analogous - * to the standard - * .Xr fputc 3 - * function. - */ -extern int StreamPutc(Stream *, int); - -/** - * Write a null-terminated string to the stream. This function is - * analogous to the standard - * .Xr fputs 3 - * function. - */ -extern int StreamPuts(Stream *, char *); - -/** - * Read at most the specified number of characters minus 1 from the - * specified stream and store them at the memory located at the - * specified pointer. This function is analogous to the standard - * .Xr fgets 3 - * function. - */ -extern char * StreamGets(Stream *, char *, int); - -/** - * Set the file position indicator for the specified stream. This - * function is analogous to the standard - * .Xr fseeko - * function. - */ -extern off_t StreamSeek(Stream *, off_t, int); - -/** - * Test the end-of-file indicator for the given stream, returning a - * boolean value indicating whether or not it is set. This is analogous - * to the standard - * .Xr feof 3 - * function. - */ -extern int StreamEof(Stream *); - -/** - * Test the stream for an error condition, returning a boolean value - * indicating whether or not one is present. This is analogous to the - * standard - * .Xr ferror 3 - * function. - */ -extern int StreamError(Stream *); - -/** - * Clear the error condition associated with the given stream, allowing - * future reads or writes to potentially be successful. This functio - * is analogous to the standard - * .Xr clearerr 3 - * function. - */ -extern void StreamClearError(Stream *); - -/** - * Flush all buffered data using the streams underlying write function. - * This function is analogous to the standard - * .Xr fflush 3 - * function. - */ -extern int StreamFlush(Stream *); - -/** - * Read all the bytes from the first stream and write them to the - * second stream. This is analogous to - * .Fn IoCopy , - * but it uses the internal buffers of the streams. It is probably - * less efficient than doing a - * .Fn IoCopy - * instead, but it is more convenient. - */ -extern ssize_t StreamCopy(Stream *, Stream *); - -/** - * Get the file descriptor associated with the given stream, or -1 if - * the stream is not associated with any file descriptor. This function - * is analogous to the standard - * .Xr fileno 3 - * function. - */ -extern int StreamFileno(Stream *); - -#endif /* CYTOPLASM_STREAM_H */ diff --git a/src/include/Tls.h b/src/include/Tls.h deleted file mode 100644 index 4407ed0..0000000 --- a/src/include/Tls.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_TLS_H -#define CYTOPLASM_TLS_H - -/*** - * @Nm Tls - * @Nd Interface to platform-dependent TLS libraries. - * @Dd April 29 2023 - * @Xr Stream Io - * - * .Nm - * provides an interface to platform-dependent TLS libraries. It allows - * Cytoplasm to support any TLS library with no changes to existing - * code. Support for additional TLS libraries is added by creating a - * new compilation unit that implements all the functions here, with - * the exception of a few, which are noted. - * .Pp - * Currently, Cytoplasm has support for the following TLS libraries: - * .Bl -bullet -offset indent - * .It - * LibreSSL - * .It - * OpenSSL - * .El - */ - -#include - -#define TLS_LIBRESSL 2 -#define TLS_OPENSSL 3 - -/** - * Create a new TLS client stream using the given file descriptor and - * the given server hostname. The hostname should be used to verify - * that the server actually is who it says it is. - * .Pp - * This function does not need to be implemented by the individual - * TLS support stubs. - */ -extern Stream * TlsClientStream(int, const char *); - -/** - * Create a new TLS server stream using the given certificate and key - * file, in the format natively supported by the TLS library. - * .Pp - * This function does not need to be implemented by the individual - * TLS support stubs. - */ -extern Stream * TlsServerStream(int, const char *, const char *); - -/** - * Initialize a cookie that stores information about the given client - * connection. This cookie will be passed into the other functions - * defined by this API. - */ -extern void * TlsInitClient(int, const char *); - -/** - * Initialize a cookie that stores information about the given - * server connection. This cookie will be passed into the other - * functions defined by this API. - */ -extern void * TlsInitServer(int, const char *, const char *); - -/** - * Read from a TLS stream, decrypting it and storing the result in the - * specified buffer. This function takes the cookie, buffer, and - * number of decrypted bytes to read into it. See the documentation for - * .Fn IoRead . - */ -extern ssize_t TlsRead(void *, void *, size_t); - -/** - * Write to a TLS stream, encrypting the buffer. This function takes - * the cookie, buffer, and number of unencrypted bytes to write to - * the stream. See the documentation for - * .Fn IoWrite . - */ -extern ssize_t TlsWrite(void *, void *, size_t); - -/** - * Close the TLS stream, also freeing all memory associated with the - * cookie. - */ -extern int TlsClose(void *); - -#endif /* CYTOPLASM_TLS_H */ diff --git a/src/include/UInt64.h b/src/include/UInt64.h deleted file mode 100644 index 51cc30c..0000000 --- a/src/include/UInt64.h +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_UINT64_H -#define CYTOPLASM_UINT64_H - -/*** - * @Nm UInt64 - * @Nd Fixed-width 64 bit integers. - * @Dd August 11, 2023 - * - * .Pp - * ANSI C89 (or C99 for that matter) provides no required mechanism - * for 64 bit integers. Nevertheless, many compilers provide them as - * extensions. However, since it is not a gaurantee, and to be fully - * standards-compliant and thus portable, a platform-agnostic interface - * is required. This header provides such an interface. If the platform - * has a 64 bit integer type, that is used, and native operations are - * performed by C preprocessor macro expansion. Otherwise, a - * compatibility layer is provided, which implements 64-bit - * arithmetic on an array of 2 32-bit numbers which are provided by - * .Xr Int 3 . - * .Pp - * Note that 64-bit emulation is certainly not as performant as using - * native 64-bit operations, so whenever possible, the native - * operations should be preferred. However, since C provides no required - * 64 bit integer on 32-bit and less platforms, this API can be used as - * a "good enough" fallback mechanism. - * .Pp - * Also note that this implementation, both in the native and - * non-native forms, makes some assumptions: - * .Bl -bullet -width Ds - * .It - * When a cast from a larger integer to a smaller integer is performed, - * the upper bits are truncated, not the lower bits. - * .It - * Negative numbers are represented in memory and in registers in two's - * compliment form. - * .El - * .Pp - * This API may provide unexpected output if these assumptions are - * false for a given platform. - * - * @ignore-typedefs - */ - -#include - -#include - -#ifndef INT64_FORCE_EMULATED - -#define BIT64_MAX 18446744073709551615UL - -#if UINT_MAX == BIT64_MAX -/* typedef signed int Int64; */ -typedef unsigned int UInt64; - -#define UINT64_NATIVE - -#elif ULONG_MAX == BIT64_MAX -/* typedef signed int Int64; */ -typedef unsigned long UInt64; - -#define UINT64_NATIVE - -#endif - -#endif /* ifndef INT64_FORCE_EMULATED */ - -#ifdef UINT64_NATIVE - -#define UInt64Create(high, low) (((UInt64) (high) << 32) | (low)) -#define UInt64Low(a) ((UInt32) ((a) & 0x00000000FFFFFFFF)) -#define UInt64High(a) ((UInt32) ((a) >> 32)) - -#define UInt64Add(a, b) ((a) + (b)) -#define UInt64Sub(a, b) ((a) - (b)) -#define UInt64Mul(a, b) ((a) * (b)) -#define UInt64Div(a, b) ((a) / (b)) -#define UInt64Rem(a, b) ((a) % (b)) - -#define UInt64Sll(a, b) ((a) << (b)) -#define UInt64Srl(a, b) ((a) >> (b)) - -#define UInt64And(a, b) ((a) & (b)) -#define UInt64Or(a, b) ((a) | (b)) -#define UInt64Xor(a, b) ((a) ^ (b)) -#define UInt64Not(a) (~(a)) - -#define UInt64Eq(a, b) ((a) == (b)) -#define UInt64Lt(a, b) ((a) < (b)) -#define UInt64Gt(a, b) ((a) > (b)) - -#define UInt64Neq(a, b) ((a) != (b)) -#define UInt64Leq(a, b) ((a) <= (b)) -#define UInt64Geq(a, b) ((a) >= (b)) - -#else - -/** - * For platforms that do not have a native integer large enough to - * store a 64 bit integer, this struct is used. i[0] contains the low - * bits of integer, and i[1] contains the high bits of the integer. - * .Pp - * This struct should not be accessed directly, because UInt64 may not - * actually be this struct, it might be an actual integer type. For - * maximum portability, only use the functions defined here to - * manipulate 64 bit integers. - */ -typedef struct -{ - UInt32 i[2]; -} UInt64; - -/** - * Create a new unsigned 64 bit integer using the given high and low - * bits. - */ -extern UInt64 UInt64Create(UInt32, UInt32); - -/** - * Add two unsigned 64 bit integers together. - */ -extern UInt64 UInt64Add(UInt64, UInt64); - -/** - * Subtract the second 64 bit integer from the first. - */ -extern UInt64 UInt64Sub(UInt64, UInt64); - -/** - * Multiply two 64 bit integers together. The non-native version of - * this function uses the Russian Peasant method of multiplication, - * which should afford more performance than a naive multiplication by - * addition, but it is still rather slow and depends on the size of - * the integers being multiplied. - */ -extern UInt64 UInt64Mul(UInt64, UInt64); - -/** - * Divide the first 64 bit integer by the second and return the - * quotient. The non-native version of this function uses naive binary - * long division, which is slow, but gauranteed to finish in constant - * time. - */ -extern UInt64 UInt64Div(UInt64, UInt64); - -/** - * Divide the first 64 bit integer by the second and return the - * remainder. The non-native version of this function uses naive binary - * long division, which is slow, but gauranteed to finish in constant - * time. - */ -extern UInt64 UInt64Rem(UInt64, UInt64); - -/** - * Perform a left logical bit shift of a 64 bit integer. The second - * parameter is how many places to shift, and is declared as a regular - * integer because anything more than 64 does not make sense. - */ -extern UInt64 UInt64Sll(UInt64, int); - -/** - * Perform a right logical bit shift of a 64 bit integer. The second - * parameter is how many places to shift, and is declared as a regular - * integer because anything more than 64 does not make sense. - */ -extern UInt64 UInt64Srl(UInt64, int); - -/** - * Perform a bitwise AND (&) of the provided 64 bit integers. - */ -extern UInt64 UInt64And(UInt64, UInt64); - -/** - * Perform a bitwise OR (|) of the provided 64 bit integers. - */ -extern UInt64 UInt64Or(UInt64, UInt64); - -/** - * Perform a bitwise XOR (^) of the provided 64 bit integers. - */ -extern UInt64 UInt64Xor(UInt64, UInt64); - -/** - * Perform a bitwise NOT (~) of the provided 64 bit integer. - */ -extern UInt64 UInt64Not(UInt64); - -/** - * Perform a comparison of the provided 64 bit integers and return a C - * boolean that is true if and only if they are equal. - */ -extern int UInt64Eq(UInt64, UInt64); - -/** - * Perform a comparison of the provided 64 bit integers and return a C - * boolean that is true if and only if the second operand is strictly - * less than the first. - */ -extern int UInt64Lt(UInt64, UInt64); - -/** - * Perform a comparison of the provided 64 bit integers and return a C - * boolean that is true if and only if the second operand is strictly - * greater than the first. - */ -extern int UInt64Gt(UInt64, UInt64); - -#define UInt64Low(a) ((a).i[0]) -#define UInt64High(a) ((a).i[1]) - -#define UInt64Neq(a, b) (!UInt64Eq(a, b)) -#define UInt64Leq(a, b) (UInt64Eq(a, b) || UInt64Lt(a, b)) -#define UInt64Geq(a, b) (UInt64Eq(a, b) || UInt64Gt(a, b)) - -#endif - -#define UINT64_STRBUF 65 /* Base 2 representation with '\0' */ - -/** - * Convert a 64 bit integer to a string in an arbitrary base - * representation specified by the second parameter, using the provided - * buffer and length specified by the third and fourth parameters. To - * guarantee that the string will fit in the buffer, allocate it of - * size UINT64_STRBUF or larger. Note that a buffer size smaller than - * UINT64_STRBUF will invoke undefined behavior. - */ -extern size_t UInt64Str(UInt64, int, char *, size_t); - -#endif /* CYTOPLASM_UINT64_H */ diff --git a/src/include/Uri.h b/src/include/Uri.h deleted file mode 100644 index a47bf28..0000000 --- a/src/include/Uri.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef CYTOPLASM_URI_H -#define CYTOPLASM_URI_H - -/*** - * @Nm Uri - * @Nd Parse a URI. Typically used to parse HTTP(s) URLs. - * @Dd April 29 2023 - * @Xr Http - * - * .Nm - * provides a simple mechanism for parsing URIs. This is an extremely - * basic parser that (ab)uses - * .Xr sscanf 3 - * to parse URIs, so it may not be the most reliable, but it should - * work in most cases and on reasonable URIs that aren't too long, as - * the _MAX definitions are modest. - */ - -#define URI_PROTO_MAX 8 -#define URI_HOST_MAX 128 -#define URI_PATH_MAX 256 - -/** - * The parsed URI is stored in this structure. - */ -typedef struct Uri -{ - char proto[URI_PROTO_MAX]; - char host[URI_HOST_MAX]; - char path[URI_PATH_MAX]; - unsigned short port; -} Uri; - -/** - * Parse a URI string into the Uri structure as described above, or - * return NULL if there was a parsing error. - */ -extern Uri * UriParse(const char *); - -/** - * Free the memory associated with a Uri structure returned by - * .Fn UriParse . - */ -extern void UriFree(Uri *); - -#endif /* CYTOPLASM_URI_H */ diff --git a/src/include/Util.h b/src/include/Util.h deleted file mode 100644 index d6c3830..0000000 --- a/src/include/Util.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> - * - * 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 portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef CYTOPLASM_UTIL_H -#define CYTOPLASM_UTIL_H - -/*** - * @Nm Util - * @Nd Some misc. helper functions that don't need their own headers. - * @Dd February 15 2023 - * - * This header holds a number of random functions related to strings, - * time, the filesystem, and other simple tasks that don't require a - * full separate API. For the most part, the functions here are - * entirely standalone, depending only on POSIX functions, however - * there are a few that depend explicitly on a few other APIs. Those - * are noted. - */ - -#include -#include -#include - -#include -#include - -/** - * Get the current timestamp in milliseconds since the Unix epoch. This - * uses - * .Xr gettimeofday 2 - * and time_t, and converts it to a single number, which is then - * returned to the caller. - * .Pp - * A note on the 2038 problem: as long as sizeof(time_t) >= 8, that is, - * as long as the time_t type is 64 bits or more, then everything - * should be fine. On most, if not, all, 64-bit systems, time_t is 64 - * bits. time_t is promoted to a 64-bit integer before it is converted - * to milliseconds, so there is no risk of overflue due to the - * multiplication by 1000. However, if time_t is only 32 bits, it will - * overflow before it even gets to this function, which will cause this - * function to produce unexpected results. - */ -extern UInt64 UtilServerTs(void); - -/** - * Use - * .Xr stat 2 - * to get the last modified time of the given file, or zero if there - * was an error getting the last modified time of a file. This is - * primarily useful for caching file data. - */ -extern UInt64 UtilLastModified(char *); - -/** - * This function behaves just like the system call - * .Xr mkdir 2 , - * but it creates any intermediate directories as necessary, unlike - * .Xr mkdir 2 . - */ -extern int UtilMkdir(const char *, const mode_t); - -/** - * Sleep the calling thread for the given number of milliseconds. - * POSIX does not have a very friendly way to sleep, so this wraps - * .Xr nanosleep 2 - * to make its usage much, much simpler. - */ -extern int UtilSleepMillis(UInt64); - -/** - * This function works identically to the POSIX - * .Xr getdelim 3 , - * except that it assumes pointers were allocated with the Memory API - * and it reads from a Stream instead of a file pointer. - */ -extern ssize_t UtilGetDelim(char **, size_t *, int, Stream *); - -/** - * This function is just a special case of - * .Fn UtilGetDelim - * that sets the delimiter to the newline character. - */ -extern ssize_t UtilGetLine(char **, size_t *, Stream *); - -/** - * Get a unique number associated with the current thread. - * Numbers are assigned in the order that threads call this - * function, but are guaranteed to be unique in identifying - * the thread in a more human-readable way than just casting - * the return value of - * .Fn pthread_self - * to a number. - */ -extern UInt32 UtilThreadNo(void); - -#endif /* CYTOPLASM_UTIL_H */ diff --git a/tools/int64.c b/tools/int64.c deleted file mode 100644 index fb9c5cf..0000000 --- a/tools/int64.c +++ /dev/null @@ -1,145 +0,0 @@ -#include - -#include - -/* AssertEquals(actual, expected) */ -int -AssertEquals(char *msg, Int64 x, Int64 y) -{ - if (!Int64Eq(x, y)) - { - Log(LOG_ERR, "%s: Expected 0x%X 0x%X, got 0x%X 0x%X", msg, - Int64High(y), Int64Low(y), - Int64High(x), Int64Low(x)); - - return 0; - } - - return 1; -} - -int -Main(void) -{ - Int64 x, y; - - Log(LOG_INFO, "sizeof(Int64) = %lu", sizeof(Int64)); - -#ifdef INT64_NATIVE - Log(LOG_INFO, "Using native 64-bit integers."); -#else - Log(LOG_INFO, "Using emulated 64-bit integers."); -#endif - - /* BSR Tests */ - - x = Int64Create(0x000000FF, 0x00000000); - - y = Int64Sra(x, 4); - AssertEquals("x >> 4", y, Int64Create(0x0000000F, 0xF0000000)); - - y = Int64Sra(x, 8); - AssertEquals("x >> 8", y, Int64Create(0x00000000, 0xFF000000)); - - y = Int64Sra(x, 36); - AssertEquals("x >> 36", y, Int64Create(0x00000000, 0x0000000F)); - - x = Int64Create(0xFF000000, 0x00000000); - - y = Int64Sra(x, 4); - AssertEquals("x >> 4", y, Int64Create(0xFFF00000, 0x00000000)); - - y = Int64Sra(x, 8); - AssertEquals("x >> 8", y, Int64Create(0xFFFF0000, 0x00000000)); - - y = Int64Sra(x, 63); - AssertEquals("x >> 63", y, Int64Create(0xFFFFFFFF, 0xFFFFFFFF)); - - /* BSL Tests */ - - x = Int64Create(0x00000000, 0xFF000000); - - y = Int64Sll(x, 4); - AssertEquals("x << 4", y, Int64Create(0x0000000F, 0xF0000000)); - - y = Int64Sll(x, 8); - AssertEquals("x << 8", y, Int64Create(0x000000FF, 0x00000000)); - - y = Int64Sll(x, 36); - AssertEquals("x << 36", y, Int64Create(0xF0000000, 0x00000000)); - - /* ADD Tests */ - - x = Int64Create(0x00000000, 0xF0000001); - y = Int64Create(0x00000000, 0x00000002); - AssertEquals("0xF0000001 + 0x00000002", Int64Add(x, y), Int64Create(0x00000000, 0xF0000003)); - - x = Int64Create(0x00000000, 0xF0000000); - y = Int64Create(0x00000000, 0x10000000); - AssertEquals("0xF0000000 + 0x10000000", Int64Add(x, y), Int64Create(0x00000001, 0x00000000)); - - x = Int64Create(0, 5); - y = Int64Neg(Int64Create(0, 10)); - AssertEquals("5 + (-10)", Int64Add(x, y), Int64Neg(Int64Create(0, 5))); - - /* SUB Tests */ - x = Int64Create(0x00000000, 0x00000005); - y = Int64Create(0x00000000, 0x00000002); - AssertEquals("0x00000005 - 0x00000002", Int64Sub(x, y), Int64Create(0x00000000, 0x00000003)); - - x = Int64Create(0x00000001, 0x00000000); - y = Int64Create(0x00000000, 0x00000001); - AssertEquals("0x00000001 0x00000000 - 0x00000001", Int64Sub(x, y), Int64Create(0x00000000, 0xFFFFFFFF)); - - x = Int64Create(0, 5); - y = Int64Create(0, 10); - AssertEquals("5 - 10", Int64Sub(x, y), Int64Neg(Int64Create(0, 5))); - - x = Int64Create(0, 5); - y = Int64Neg(Int64Create(0, 10)); - AssertEquals("5 - (-10)", Int64Sub(x, y), Int64Create(0, 15)); - - /* MUL Tests */ - x = Int64Create(0, 18); - y = Int64Create(0, 1); - AssertEquals("18 * 1", Int64Mul(x, y), Int64Create(0, 18)); - - x = Int64Create(0, 20); - y = Int64Create(0, 12); - AssertEquals("20 * 12", Int64Mul(x, y), Int64Create(0, 240)); - - x = Int64Create(0x00000000, 0x00000005); - y = Int64Create(0x00000000, 0x00000005); - AssertEquals("0x00000005 * 0x00000005", Int64Mul(x, y), Int64Create(0x00000000, 0x00000019)); - - x = Int64Create(0x00000001, 0x00000000); - y = Int64Create(0x00000000, 0x00000005); - AssertEquals("0x00000001 0x00000000 * 0x00000005", Int64Mul(x, y), Int64Create(0x00000005, 0x00000000)); - - /* DIV Tests */ - x = Int64Create(0, 12); - y = Int64Create(0, 4); - AssertEquals("12 / 4", Int64Div(x, y), Int64Create(0, 3)); - - /* MOD Tests */ - x = Int64Create(0x000000FF, 0x00000000); - y = Int64Create(0x00000000, 0x00000010); - AssertEquals("0x000000FF 0x00000000 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0)); - - x = Int64Create(0x00000000, 0xFF000000); - y = Int64Create(0x00000000, 0x00000010); - AssertEquals("0x00000000 0xFF000000 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0)); - - x = Int64Create(0xFF000000, 0x00000000); - y = Int64Create(0x00000000, 0x00000010); - AssertEquals("0xFF000000 0x00000000 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0)); - - x = Int64Create(0x00000000, 0x000000F0); - y = Int64Create(0x00000000, 0x00000010); - AssertEquals("0x00000000 0x000000F0 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0)); - - /* TODO: Add more tests for negative multiplication, division, and - * mod */ - - return 0; -} diff --git a/tools/uint64.c b/tools/uint64.c deleted file mode 100644 index 8e9f5f5..0000000 --- a/tools/uint64.c +++ /dev/null @@ -1,119 +0,0 @@ -#include - -#include - -/* AssertEquals(actual, expected) */ -int -AssertEquals(char *msg, UInt64 x, UInt64 y) -{ - if (!UInt64Eq(x, y)) - { - Log(LOG_ERR, "%s: Expected 0x%X 0x%X, got 0x%X 0x%X", msg, - UInt64High(y), UInt64Low(y), - UInt64High(x), UInt64Low(x)); - - return 0; - } - - return 1; -} - -int -Main(void) -{ - UInt64 x, y; - - Log(LOG_INFO, "sizeof(UInt64) = %lu", sizeof(UInt64)); - -#ifdef UINT64_NATIVE - Log(LOG_INFO, "Using native 64-bit integers."); -#else - Log(LOG_INFO, "Using emulated 64-bit integers."); -#endif - - /* BSR Tests */ - - x = UInt64Create(0x000000FF, 0x00000000); - - y = UInt64Srl(x, 4); - AssertEquals("x >> 4", y, UInt64Create(0x0000000F, 0xF0000000)); - - y = UInt64Srl(x, 8); - AssertEquals("x >> 8", y, UInt64Create(0x00000000, 0xFF000000)); - - y = UInt64Srl(x, 36); - AssertEquals("x >> 36", y, UInt64Create(0x00000000, 0x0000000F)); - - /* BSL Tests */ - - x = UInt64Create(0x00000000, 0xFF000000); - - y = UInt64Sll(x, 4); - AssertEquals("x << 4", y, UInt64Create(0x0000000F, 0xF0000000)); - - y = UInt64Sll(x, 8); - AssertEquals("x << 8", y, UInt64Create(0x000000FF, 0x00000000)); - - y = UInt64Sll(x, 36); - AssertEquals("x << 36", y, UInt64Create(0xF0000000, 0x00000000)); - - /* ADD Tests */ - - x = UInt64Create(0x00000000, 0xF0000001); - y = UInt64Create(0x00000000, 0x00000002); - AssertEquals("0xF0000001 + 0x00000002", UInt64Add(x, y), UInt64Create(0x00000000, 0xF0000003)); - - x = UInt64Create(0x00000000, 0xF0000000); - y = UInt64Create(0x00000000, 0x10000000); - AssertEquals("0xF0000000 + 0x10000000", UInt64Add(x, y), UInt64Create(0x00000001, 0x00000000)); - - /* SUB Tests */ - x = UInt64Create(0x00000000, 0x00000005); - y = UInt64Create(0x00000000, 0x00000002); - AssertEquals("0x00000005 - 0x00000002", UInt64Sub(x, y), UInt64Create(0x00000000, 0x00000003)); - - x = UInt64Create(0x00000001, 0x00000000); - y = UInt64Create(0x00000000, 0x00000001); - AssertEquals("0x00000001 0x00000000 - 0x00000001", UInt64Sub(x, y), UInt64Create(0x00000000, 0xFFFFFFFF)); - - /* MUL Tests */ - x = UInt64Create(0, 18); - y = UInt64Create(0, 1); - AssertEquals("18 * 1", UInt64Mul(x, y), UInt64Create(0, 18)); - - x = UInt64Create(0, 20); - y = UInt64Create(0, 12); - AssertEquals("20 * 12", UInt64Mul(x, y), UInt64Create(0, 240)); - - x = UInt64Create(0x00000000, 0x00000005); - y = UInt64Create(0x00000000, 0x00000005); - AssertEquals("0x00000005 * 0x00000005", UInt64Mul(x, y), UInt64Create(0x00000000, 0x00000019)); - - x = UInt64Create(0x00000001, 0x00000000); - y = UInt64Create(0x00000000, 0x00000005); - AssertEquals("0x00000001 0x00000000 * 0x00000005", UInt64Mul(x, y), UInt64Create(0x00000005, 0x00000000)); - - /* DIV Tests */ - x = UInt64Create(0, 12); - y = UInt64Create(0, 4); - AssertEquals("12 / 4", UInt64Div(x, y), UInt64Create(0, 3)); - - /* MOD Tests */ - x = UInt64Create(0x000000FF, 0x00000000); - y = UInt64Create(0x00000000, 0x00000010); - AssertEquals("0x000000FF 0x00000000 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0)); - - x = UInt64Create(0x00000000, 0xFF000000); - y = UInt64Create(0x00000000, 0x00000010); - AssertEquals("0x00000000 0xFF000000 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0)); - - x = UInt64Create(0xFF000000, 0x00000000); - y = UInt64Create(0x00000000, 0x00000010); - AssertEquals("0xFF000000 0x00000000 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0)); - - x = UInt64Create(0x00000000, 0x000000F0); - y = UInt64Create(0x00000000, 0x00000010); - AssertEquals("0x00000000 0x000000F0 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0)); - - return 0; -} From 007b8f6d43ecc35cd4ec8777c117ab3659a38305 Mon Sep 17 00:00:00 2001 From: lda Date: Mon, 3 Jun 2024 16:18:29 +0200 Subject: [PATCH 03/18] =?UTF-8?q?[MOD/WIP]=20Blazing-fast=20memory=20alloc?= =?UTF-8?q?ator=20=F0=9F=9A=80=F0=9F=9A=80=F0=9F=9A=80=F0=9F=9A=80?= =?UTF-8?q?=F0=9F=9A=80=F0=9F=9A=80=F0=9F=9A=80=F0=9F=9A=80=F0=9F=9A=80?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This still however removes `MemoryIterate' from allocations, because it's a real performance pickle. From my tests parsing large sync replies, this commit is near instant(pv reports 23MiB/s on a 2MB sync). Still need a good compromise along MemoryIterate(like maybe find out a clever way to only make it run on a small subset, or maybe just randomly run it from time to time, or maybe just roll without it except on some soft of debug mode????) --- src/Memory.c | 199 +++++++++++++++++---------------------------------- 1 file changed, 64 insertions(+), 135 deletions(-) diff --git a/src/Memory.c b/src/Memory.c index 4083143..1ce9fd5 100644 --- a/src/Memory.c +++ b/src/Memory.c @@ -42,14 +42,20 @@ struct MemoryInfo { + uint64_t magic; + size_t size; const char *file; int line; void *pointer; + + MemoryInfo *prev; + MemoryInfo *next; }; #define MEM_BOUND_TYPE uint32_t #define MEM_BOUND 0xDEADBEEF +#define MEM_MAGIC 0x4E69746F72697961 #define MEM_BOUND_LOWER(p) *((MEM_BOUND_TYPE *) p) #define MEM_BOUND_UPPER(p, x) *(((MEM_BOUND_TYPE *) (((uint8_t *) p) + x)) + 1) @@ -59,10 +65,10 @@ static pthread_mutex_t lock; static void (*hook) (MemoryAction, MemoryInfo *, void *) = MemoryDefaultHook; static void *hookArgs = NULL; -static MemoryInfo **allocations = NULL; -static size_t allocationsSize = 0; static size_t allocationsLen = 0; +static MemoryInfo *allocationTail = NULL; + int MemoryRuntimeInit(void) { @@ -91,70 +97,18 @@ MemoryRuntimeDestroy(void) return pthread_mutex_destroy(&lock) == 0; } -static size_t -MemoryHash(void *p) -{ - return (((size_t) p) >> 2 * 7) % allocationsSize; -} - static int MemoryInsert(MemoryInfo * a) { - size_t hash; - - if (!allocations) + if (allocationTail) { - allocationsSize = MEMORY_TABLE_CHUNK; - allocations = calloc(allocationsSize, sizeof(void *)); - if (!allocations) - { - return 0; - } + allocationTail->next = a; } + a->next = NULL; + a->prev = allocationTail; + a->magic = MEM_MAGIC; - /* If the next insertion would cause the table to be at least 3/4 - * full, re-allocate and re-hash. */ - if ((allocationsLen + 1) >= ((allocationsSize * 3) >> 2)) - { - size_t i; - size_t tmpAllocationsSize = allocationsSize; - MemoryInfo **tmpAllocations; - - allocationsSize += MEMORY_TABLE_CHUNK; - tmpAllocations = calloc(allocationsSize, sizeof(void *)); - - if (!tmpAllocations) - { - return 0; - } - - for (i = 0; i < tmpAllocationsSize; i++) - { - if (allocations[i]) - { - hash = MemoryHash(allocations[i]->pointer); - - while (tmpAllocations[hash]) - { - hash = (hash + 1) % allocationsSize; - } - - tmpAllocations[hash] = allocations[i]; - } - } - - free(allocations); - allocations = tmpAllocations; - } - - hash = MemoryHash(a->pointer); - - while (allocations[hash]) - { - hash = (hash + 1) % allocationsSize; - } - - allocations[hash] = a; + allocationTail = a; allocationsLen++; return 1; @@ -163,23 +117,24 @@ MemoryInsert(MemoryInfo * a) static void MemoryDelete(MemoryInfo * a) { - size_t hash = MemoryHash(a->pointer); - size_t count = 0; + MemoryInfo *aPrev = a->prev; + MemoryInfo *aNext = a->next; - while (count <= allocationsSize) + if (aPrev) { - if (allocations[hash] && allocations[hash] == a) - { - allocations[hash] = NULL; - allocationsLen--; - return; - } - else - { - hash = (hash + 1) % allocationsSize; - count++; - } + aPrev->next = aNext; } + if (aNext) + { + aNext->prev = aPrev; + } + + if (a == allocationTail) + { + allocationTail = aPrev; + } + + a->magic = ~MEM_MAGIC; } static int @@ -203,24 +158,18 @@ MemoryAllocate(size_t size, const char *file, int line) void *p; MemoryInfo *a; - MemoryIterate(NULL, NULL); + //MemoryIterate(NULL, NULL); pthread_mutex_lock(&lock); - a = malloc(sizeof(MemoryInfo)); + a = malloc(sizeof(MemoryInfo) + MEM_SIZE_ACTUAL(size)); if (!a) { pthread_mutex_unlock(&lock); return NULL; } - p = malloc(MEM_SIZE_ACTUAL(size)); - if (!p) - { - free(a); - pthread_mutex_unlock(&lock); - return NULL; - } + p = a + 1; memset(p, 0, MEM_SIZE_ACTUAL(size)); MEM_BOUND_LOWER(p) = MEM_BOUND; @@ -234,7 +183,6 @@ MemoryAllocate(size_t size, const char *file, int line) if (!MemoryInsert(a)) { free(a); - free(p); pthread_mutex_unlock(&lock); return NULL; } @@ -254,7 +202,7 @@ MemoryReallocate(void *p, size_t size, const char *file, int line) MemoryInfo *a; void *new = NULL; - MemoryIterate(NULL, NULL); + //MemoryIterate(NULL, NULL); if (!p) { @@ -265,15 +213,17 @@ MemoryReallocate(void *p, size_t size, const char *file, int line) if (a) { pthread_mutex_lock(&lock); - new = realloc(a->pointer, MEM_SIZE_ACTUAL(size)); + MemoryDelete(a); + new = realloc(a, sizeof(MemoryInfo) + MEM_SIZE_ACTUAL(size)); if (new) { - MemoryDelete(a); + a = new; a->size = MEM_SIZE_ACTUAL(size); a->file = file; a->line = line; + a->magic = MEM_MAGIC; - a->pointer = new; + a->pointer = a + 1; MemoryInsert(a); MEM_BOUND_LOWER(a->pointer) = MEM_BOUND; @@ -283,7 +233,7 @@ MemoryReallocate(void *p, size_t size, const char *file, int line) { hook(MEMORY_REALLOCATE, a, hookArgs); } - + } pthread_mutex_unlock(&lock); } @@ -301,7 +251,7 @@ MemoryReallocate(void *p, size_t size, const char *file, int line) } } - return ((MEM_BOUND_TYPE *) new) + 1; + return ((MEM_BOUND_TYPE *) a->pointer) + 1; } void @@ -309,7 +259,7 @@ MemoryFree(void *p, const char *file, int line) { MemoryInfo *a; - MemoryIterate(NULL, NULL); + //MemoryIterate(NULL, NULL); if (!p) { @@ -327,7 +277,6 @@ MemoryFree(void *p, const char *file, int line) hook(MEMORY_FREE, a, hookArgs); } MemoryDelete(a); - free(a->pointer); free(a); pthread_mutex_unlock(&lock); @@ -350,17 +299,15 @@ MemoryFree(void *p, const char *file, int line) size_t MemoryAllocated(void) { - size_t i; size_t total = 0; + MemoryInfo *cur; pthread_mutex_lock(&lock); - for (i = 0; i < allocationsSize; i++) + /* TODO */ + for (cur = allocationTail; cur; cur = cur->prev) { - if (allocations[i]) - { - total += MemoryInfoGetSize(allocations[i]); - } + total += MemoryInfoGetSize(cur); } pthread_mutex_unlock(&lock); @@ -371,55 +318,40 @@ MemoryAllocated(void) void MemoryFreeAll(void) { - size_t i; + MemoryInfo *cur; + MemoryInfo *prev; pthread_mutex_lock(&lock); - for (i = 0; i < allocationsSize; i++) + /* TODO */ + for (cur = allocationTail; cur; cur = prev) { - if (allocations[i]) - { - free(allocations[i]->pointer); - free(allocations[i]); - } + prev = cur->prev; + free(cur); } - free(allocations); - allocations = NULL; - allocationsSize = 0; allocationsLen = 0; pthread_mutex_unlock(&lock); } MemoryInfo * -MemoryInfoGet(void *p) +MemoryInfoGet(void *po) { - size_t hash, count; + void *p = po; pthread_mutex_lock(&lock); - p = ((MEM_BOUND_TYPE *) p) - 1; - hash = MemoryHash(p); + p = ((MEM_BOUND_TYPE *) po) - 1; + p = ((MemoryInfo *) p) - 1; - count = 0; - while (count <= allocationsSize) + if (((MemoryInfo *)p)->magic != MEM_MAGIC) { - if (!allocations[hash] || allocations[hash]->pointer != p) - { - hash = (hash + 1) % allocationsSize; - count++; - } - else - { - MemoryCheck(allocations[hash]); - pthread_mutex_unlock(&lock); - return allocations[hash]; - } + p = NULL; } pthread_mutex_unlock(&lock); - return NULL; + return p; } size_t @@ -467,21 +399,18 @@ MemoryInfoGetPointer(MemoryInfo * a) } void - MemoryIterate(void (*iterFunc) (MemoryInfo *, void *), void *args) +MemoryIterate(void (*iterFunc) (MemoryInfo *, void *), void *args) { - size_t i; + MemoryInfo *cur; pthread_mutex_lock(&lock); - for (i = 0; i < allocationsSize; i++) + for (cur = allocationTail; cur; cur = cur->prev) { - if (allocations[i]) + MemoryCheck(cur); + if (iterFunc) { - MemoryCheck(allocations[i]); - if (iterFunc) - { - iterFunc(allocations[i], args); - } + iterFunc(cur, args); } } From 8b2bdbe2209a4993bc496e77e96721f78cb08b43 Mon Sep 17 00:00:00 2001 From: LDA Date: Sat, 15 Jun 2024 13:48:39 +0200 Subject: [PATCH 04/18] [FIX] Actually do proper stringification. It just kept bothering me. --- include/Cytoplasm/Cytoplasm.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/Cytoplasm/Cytoplasm.h b/include/Cytoplasm/Cytoplasm.h index 0119e06..a0d6af9 100644 --- a/include/Cytoplasm/Cytoplasm.h +++ b/include/Cytoplasm/Cytoplasm.h @@ -33,7 +33,8 @@ #define CYTOPLASM_VERSION_BETA 0 #define CYTOPLASM_VERSION_STABLE (!CYTOPLASM_VERSION_ALPHA && !CYTOPLASM_VERSION_BETA) -#define STRINGIFY(x) #x +#define XSTRINGIFY(x) #x +#define STRINGIFY(x) XSTRINGIFY(x) /*** * @Nm Cytoplasm From bec672c92c10c4167ed3a66866416ffa52886353 Mon Sep 17 00:00:00 2001 From: LDA Date: Wed, 19 Jun 2024 17:51:13 +0200 Subject: [PATCH 05/18] [MOD] Use a different constant --- src/Memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Memory.c b/src/Memory.c index 1ce9fd5..2cb6d67 100644 --- a/src/Memory.c +++ b/src/Memory.c @@ -55,7 +55,7 @@ struct MemoryInfo #define MEM_BOUND_TYPE uint32_t #define MEM_BOUND 0xDEADBEEF -#define MEM_MAGIC 0x4E69746F72697961 +#define MEM_MAGIC 0xDEADBEEFDEADBEEF #define MEM_BOUND_LOWER(p) *((MEM_BOUND_TYPE *) p) #define MEM_BOUND_UPPER(p, x) *(((MEM_BOUND_TYPE *) (((uint8_t *) p) + x)) + 1) From 87d9421f1110425150fcf5693c412d29788942a1 Mon Sep 17 00:00:00 2001 From: LDA Date: Wed, 7 Aug 2024 12:48:33 +0200 Subject: [PATCH 06/18] [ADD/WIP] Start adding a lmdb flag to configure Start of my work to get out LMDB support. I want to make it optional, as some environments just can't use LMDB(due to mapped RAM limits, or places where mmap is unavailable (a rather cursed platform!)). Next up: Start separating Db to allow multiple subimplementations instead of being expressly for nested-dir JSON ops. --- configure | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 1afdd91..c0bf8b6 100755 --- a/configure +++ b/configure @@ -24,10 +24,10 @@ SCRIPT_ARGS="--prefix=/usr/local --lib-name=Cytoplasm" # Set SSL flags depending on the platform. case "$(uname)" in OpenBSD) - SCRIPT_ARGS="${SCRIPT_ARGS} --with-libressl" + SCRIPT_ARGS="${SCRIPT_ARGS} --with-libressl --disable-lmdb" ;; *) - SCRIPT_ARGS="${SCRIPT_ARGS} --with-openssl" + SCRIPT_ARGS="${SCRIPT_ARGS} --with-openssl --disable-lmdb" ;; esac @@ -80,6 +80,14 @@ for arg in $SCRIPT_ARGS; do TLS_IMPL="" TLS_LIBS="" ;; + --with-lmdb) + EDB_IMPL="EDB_LMDB" + EDB_LIBS="-llmdb" + ;; + --disable-lmdb) + EDB_IMPL="" + EDB_LIBS="" + ;; --prefix=*) PREFIX=$(echo "$arg" | cut -d '=' -f 2-) ;; @@ -104,6 +112,11 @@ if [ -n "$TLS_IMPL" ]; then LIBS="${LIBS} ${TLS_LIBS}" fi +if [ -n "$EDB_IMPL" ]; then + CFLAGS="${CFLAGS} -DEDB_IMPL=${EDB_IMPL}" + LIBS="${LIBS} ${EDB_LIBS}" +fi + CFLAGS="${CFLAGS} '-DLIB_NAME=\"${LIB_NAME}\"' ${DEBUG}" LDFLAGS="${LIBS} ${LDFLAGS}" From b87979e9a2ed1a54a96dab0807548d8a430fdb7a Mon Sep 17 00:00:00 2001 From: LDA Date: Wed, 7 Aug 2024 20:45:53 +0200 Subject: [PATCH 07/18] [MOD/WIP] Start separating the main DB API Not everything is available as of now, I'm still working on it, but it builds and seems to work, and its 9PM, so that's worthapush. --- configure | 4 +- include/Cytoplasm/Db.h | 9 + src/Db.c | 977 ----------------------------------------- src/Db/Db.c | 576 ++++++++++++++++++++++++ src/Db/Flat.c | 274 ++++++++++++ src/Db/Internal.h | 98 +++++ src/Db/LMDB.c | 39 ++ 7 files changed, 998 insertions(+), 979 deletions(-) delete mode 100644 src/Db.c create mode 100644 src/Db/Db.c create mode 100644 src/Db/Flat.c create mode 100644 src/Db/Internal.h create mode 100644 src/Db/LMDB.c diff --git a/configure b/configure index c0bf8b6..0018605 100755 --- a/configure +++ b/configure @@ -15,7 +15,7 @@ TOOLS="tools" # Default compiler flags. These must be supported by all POSIX C compilers. # "Fancy" compilers that have additional options must be detected and set below. -CFLAGS="-O1 -D_DEFAULT_SOURCE -I${INCLUDE}" +CFLAGS="-O1 -D_DEFAULT_SOURCE -I${INCLUDE} -I${SRC}"; LIBS="-lm -lpthread" # Default args for all platforms. @@ -153,7 +153,7 @@ print_obj() { get_deps() { src="$1" - ${CC} -I${INCLUDE} -E "$src" \ + ${CC} -I${SRC} -I${INCLUDE} -E "$src" \ | grep '^#' \ | awk '{print $3}' \ | cut -d '"' -f 2 \ diff --git a/include/Cytoplasm/Db.h b/include/Cytoplasm/Db.h index f4c41d2..604ace7 100644 --- a/include/Cytoplasm/Db.h +++ b/include/Cytoplasm/Db.h @@ -68,6 +68,15 @@ typedef struct DbRef DbRef; */ extern Db * DbOpen(char *, size_t); +/** + * Open a LMDB data directory. This function is similar to + * .Fn DbOpen , + * but works with a LMDB-based backend, with the second argument + * being the maximum filesize. This function fails with NULL if ever + * called without LMDB enabled at compiletime. + */ +extern Db * DbOpenLMDB(char *, size_t); + /** * Close the database. This function will flush anything in the cache * to the disk, and then close the data directory. It assumes that diff --git a/src/Db.c b/src/Db.c deleted file mode 100644 index 4128a89..0000000 --- a/src/Db.c +++ /dev/null @@ -1,977 +0,0 @@ -/* - * Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> - * - * 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 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 -#include - -#include -#include -#include - -#include -#include - -struct Db -{ - char *dir; - pthread_mutex_t lock; - - size_t cacheSize; - size_t maxCache; - HashMap *cache; - - /* - * The cache uses a double linked list (see DbRef - * below) to know which objects are most and least - * recently used. The following diagram helps me - * know what way all the pointers go, because it - * can get very confusing sometimes. For example, - * there's nothing stopping "next" from pointing to - * least recent, and "prev" from pointing to most - * recent, so hopefully this clarifies the pointer - * terminology used when dealing with the linked - * list: - * - * mostRecent leastRecent - * | prev prev | prev - * +---+ ---> +---+ ---> +---+ ---> NULL - * |ref| |ref| |ref| - * NULL <--- +---+ <--- +---+ <--- +---+ - * next next next - */ - DbRef *mostRecent; - DbRef *leastRecent; -}; - -struct DbRef -{ - HashMap *json; - - uint64_t ts; - size_t size; - - Array *name; - - DbRef *prev; - DbRef *next; - - int fd; - Stream *stream; -}; - -static void -StringArrayFree(Array * arr) -{ - size_t i; - - for (i = 0; i < ArraySize(arr); i++) - { - Free(ArrayGet(arr, i)); - } - - ArrayFree(arr); -} - -static ssize_t DbComputeSize(HashMap *); - -static ssize_t -DbComputeSizeOfValue(JsonValue * val) -{ - MemoryInfo *a; - ssize_t total = 0; - - size_t i; - - union - { - char *str; - Array *arr; - } u; - - if (!val) - { - return -1; - } - - a = MemoryInfoGet(val); - if (a) - { - total += MemoryInfoGetSize(a); - } - - switch (JsonValueType(val)) - { - case JSON_OBJECT: - total += DbComputeSize(JsonValueAsObject(val)); - break; - case JSON_ARRAY: - u.arr = JsonValueAsArray(val); - a = MemoryInfoGet(u.arr); - - if (a) - { - total += MemoryInfoGetSize(a); - } - - for (i = 0; i < ArraySize(u.arr); i++) - { - total += DbComputeSizeOfValue(ArrayGet(u.arr, i)); - } - break; - case JSON_STRING: - u.str = JsonValueAsString(val); - a = MemoryInfoGet(u.str); - if (a) - { - total += MemoryInfoGetSize(a); - } - break; - case JSON_NULL: - case JSON_INTEGER: - case JSON_FLOAT: - case JSON_BOOLEAN: - default: - /* These don't use any extra heap space */ - break; - } - return total; -} - -static ssize_t -DbComputeSize(HashMap * json) -{ - char *key; - JsonValue *val; - MemoryInfo *a; - size_t total; - - if (!json) - { - return -1; - } - - total = 0; - - a = MemoryInfoGet(json); - if (a) - { - total += MemoryInfoGetSize(a); - } - - while (HashMapIterate(json, &key, (void **) &val)) - { - a = MemoryInfoGet(key); - if (a) - { - total += MemoryInfoGetSize(a); - } - - total += DbComputeSizeOfValue(val); - } - - return total; -} - -static char * -DbHashKey(Array * args) -{ - size_t i; - char *str = NULL; - - for (i = 0; i < ArraySize(args); i++) - { - char *tmp = StrConcat(2, str, ArrayGet(args, i)); - - Free(str); - str = tmp; - } - - return str; -} - -static char -DbSanitiseChar(char input) -{ - switch (input) - { - case '/': - return '_'; - case '.': - return '-'; - } - return input; -} - -static char * -DbDirName(Db * db, Array * args, size_t strip) -{ - size_t i, j; - char *str = StrConcat(2, db->dir, "/"); - - for (i = 0; i < ArraySize(args) - strip; i++) - { - char *tmp; - char *sanitise = StrDuplicate(ArrayGet(args, i)); - for (j = 0; j < strlen(sanitise); j++) - { - sanitise[j] = DbSanitiseChar(sanitise[j]); - } - - tmp = StrConcat(3, str, sanitise, "/"); - - Free(str); - Free(sanitise); - - str = tmp; - } - - return str; -} - -static char * -DbFileName(Db * db, Array * args) -{ - size_t i; - char *str = StrConcat(2, db->dir, "/"); - - for (i = 0; i < ArraySize(args); i++) - { - char *tmp; - char *arg = StrDuplicate(ArrayGet(args, i)); - size_t j = 0; - - /* Sanitize name to prevent directory traversal attacks */ - while (arg[j]) - { - arg[j] = DbSanitiseChar(arg[j]); - j++; - } - - tmp = StrConcat(3, str, arg, - (i < ArraySize(args) - 1) ? "/" : ".json"); - - Free(arg); - Free(str); - - str = tmp; - } - - return str; -} - -static void -DbCacheEvict(Db * db) -{ - DbRef *ref = db->leastRecent; - DbRef *tmp; - - while (ref && db->cacheSize > db->maxCache) - { - char *hash; - - JsonFree(ref->json); - - hash = DbHashKey(ref->name); - HashMapDelete(db->cache, hash); - Free(hash); - - StringArrayFree(ref->name); - - db->cacheSize -= ref->size; - - if (ref->next) - { - ref->next->prev = ref->prev; - } - else - { - db->mostRecent = ref->prev; - } - - if (ref->prev) - { - ref->prev->next = ref->next; - } - else - { - db->leastRecent = ref->next; - } - - tmp = ref->next; - Free(ref); - - ref = tmp; - } -} - -Db * -DbOpen(char *dir, size_t cache) -{ - Db *db; - pthread_mutexattr_t attr; - - if (!dir) - { - return NULL; - } - - db = Malloc(sizeof(Db)); - if (!db) - { - return NULL; - } - - db->dir = dir; - db->maxCache = cache; - - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(&db->lock, &attr); - pthread_mutexattr_destroy(&attr); - - db->mostRecent = NULL; - db->leastRecent = NULL; - db->cacheSize = 0; - - if (db->maxCache) - { - db->cache = HashMapCreate(); - if (!db->cache) - { - return NULL; - } - } - else - { - db->cache = NULL; - } - - return db; -} - -void -DbMaxCacheSet(Db * db, size_t cache) -{ - if (!db) - { - return; - } - - pthread_mutex_lock(&db->lock); - - db->maxCache = cache; - if (db->maxCache && !db->cache) - { - db->cache = HashMapCreate(); - db->cacheSize = 0; - } - - DbCacheEvict(db); - - pthread_mutex_unlock(&db->lock); -} - -void -DbClose(Db * db) -{ - if (!db) - { - return; - } - - pthread_mutex_lock(&db->lock); - - DbMaxCacheSet(db, 0); - DbCacheEvict(db); - HashMapFree(db->cache); - - pthread_mutex_unlock(&db->lock); - pthread_mutex_destroy(&db->lock); - - Free(db); -} - -static DbRef * -DbLockFromArr(Db * db, Array * args) -{ - char *file; - char *hash; - DbRef *ref; - struct flock lock; - - int fd; - Stream *stream; - - if (!db || !args) - { - return NULL; - } - - ref = NULL; - hash = NULL; - - pthread_mutex_lock(&db->lock); - - /* Check if the item is in the cache */ - hash = DbHashKey(args); - ref = HashMapGet(db->cache, hash); - file = DbFileName(db, args); - - fd = open(file, O_RDWR); - if (fd == -1) - { - if (ref) - { - HashMapDelete(db->cache, hash); - JsonFree(ref->json); - StringArrayFree(ref->name); - - db->cacheSize -= ref->size; - - if (ref->next) - { - ref->next->prev = ref->prev; - } - else - { - db->mostRecent = ref->prev; - } - - if (ref->prev) - { - ref->prev->next = ref->next; - } - else - { - db->leastRecent = ref->next; - } - - if (!db->leastRecent) - { - db->leastRecent = db->mostRecent; - } - - Free(ref); - } - ref = NULL; - goto finish; - } - - stream = StreamFd(fd); - - lock.l_start = 0; - lock.l_len = 0; - lock.l_type = F_WRLCK; - lock.l_whence = SEEK_SET; - - /* Lock the file on the disk */ - if (fcntl(fd, F_SETLK, &lock) < 0) - { - StreamClose(stream); - ref = NULL; - goto finish; - } - - if (ref) /* In cache */ - { - uint64_t diskTs = UtilLastModified(file); - - ref->fd = fd; - ref->stream = stream; - - if (diskTs > ref->ts) - { - /* File was modified on disk since it was cached */ - HashMap *json = JsonDecode(ref->stream); - - if (!json) - { - StreamClose(ref->stream); - ref = NULL; - goto finish; - } - - JsonFree(ref->json); - ref->json = json; - ref->ts = diskTs; - ref->size = DbComputeSize(ref->json); - } - - /* Float this ref to mostRecent */ - if (ref->next) - { - ref->next->prev = ref->prev; - - if (!ref->prev) - { - db->leastRecent = ref->next; - } - else - { - ref->prev->next = ref->next; - } - - ref->prev = db->mostRecent; - ref->next = NULL; - if (db->mostRecent) - { - db->mostRecent->next = ref; - } - db->mostRecent = ref; - } - - /* If there is no least recent, this is the only thing in the - * cache, so it is also least recent. */ - if (!db->leastRecent) - { - db->leastRecent = ref; - } - - /* The file on disk may be larger than what we have in memory, - * which may require items in cache to be evicted. */ - DbCacheEvict(db); - } - else - { - Array *name; - size_t i; - - /* Not in cache; load from disk */ - - ref = Malloc(sizeof(DbRef)); - if (!ref) - { - StreamClose(stream); - goto finish; - } - - ref->json = JsonDecode(stream); - - if (!ref->json) - { - Free(ref); - StreamClose(stream); - ref = NULL; - goto finish; - } - - ref->fd = fd; - ref->stream = stream; - - name = ArrayCreate(); - for (i = 0; i < ArraySize(args); i++) - { - ArrayAdd(name, StrDuplicate(ArrayGet(args, i))); - } - ref->name = name; - - if (db->cache) - { - ref->ts = UtilTsMillis(); - ref->size = DbComputeSize(ref->json); - HashMapSet(db->cache, hash, ref); - db->cacheSize += ref->size; - - ref->next = NULL; - ref->prev = db->mostRecent; - if (db->mostRecent) - { - db->mostRecent->next = ref; - } - db->mostRecent = ref; - - if (!db->leastRecent) - { - db->leastRecent = ref; - } - - /* Adding this item to the cache may case it to grow too - * large, requiring some items to be evicted */ - DbCacheEvict(db); - } - } - -finish: - if (!ref) - { - pthread_mutex_unlock(&db->lock); - } - - Free(file); - Free(hash); - - return ref; -} - -DbRef * -DbCreate(Db * db, size_t nArgs,...) -{ - Stream *fp; - char *file; - char *dir; - va_list ap; - Array *args; - DbRef *ret; - - if (!db) - { - return NULL; - } - - va_start(ap, nArgs); - args = ArrayFromVarArgs(nArgs, ap); - va_end(ap); - - if (!args) - { - return NULL; - } - - pthread_mutex_lock(&db->lock); - - file = DbFileName(db, args); - - if (UtilLastModified(file)) - { - Free(file); - ArrayFree(args); - pthread_mutex_unlock(&db->lock); - return NULL; - } - - dir = DbDirName(db, args, 1); - if (UtilMkdir(dir, 0750) < 0) - { - Free(file); - ArrayFree(args); - Free(dir); - pthread_mutex_unlock(&db->lock); - return NULL; - } - - Free(dir); - - fp = StreamOpen(file, "w"); - Free(file); - if (!fp) - { - ArrayFree(args); - pthread_mutex_unlock(&db->lock); - return NULL; - } - - StreamPuts(fp, "{}"); - StreamClose(fp); - - /* DbLockFromArr() will lock again for us */ - pthread_mutex_unlock(&db->lock); - - ret = DbLockFromArr(db, args); - - ArrayFree(args); - - return ret; -} - -bool -DbDelete(Db * db, size_t nArgs,...) -{ - va_list ap; - Array *args; - char *file; - char *hash; - bool ret = true; - DbRef *ref; - - if (!db) - { - return false; - } - - va_start(ap, nArgs); - args = ArrayFromVarArgs(nArgs, ap); - va_end(ap); - - pthread_mutex_lock(&db->lock); - - hash = DbHashKey(args); - file = DbFileName(db, args); - - ref = HashMapGet(db->cache, hash); - if (ref) - { - HashMapDelete(db->cache, hash); - JsonFree(ref->json); - StringArrayFree(ref->name); - - db->cacheSize -= ref->size; - - if (ref->next) - { - ref->next->prev = ref->prev; - } - else - { - db->mostRecent = ref->prev; - } - - if (ref->prev) - { - ref->prev->next = ref->next; - } - else - { - db->leastRecent = ref->next; - } - - if (!db->leastRecent) - { - db->leastRecent = db->mostRecent; - } - - Free(ref); - } - - Free(hash); - - if (UtilLastModified(file)) - { - ret = (remove(file) == 0); - } - - pthread_mutex_unlock(&db->lock); - - ArrayFree(args); - Free(file); - return ret; -} - -DbRef * -DbLock(Db * db, size_t nArgs,...) -{ - va_list ap; - Array *args; - DbRef *ret; - - va_start(ap, nArgs); - args = ArrayFromVarArgs(nArgs, ap); - va_end(ap); - - if (!args) - { - return NULL; - } - - ret = DbLockFromArr(db, args); - - ArrayFree(args); - - return ret; -} - -bool -DbUnlock(Db * db, DbRef * ref) -{ - bool destroy; - - if (!db || !ref) - { - return false; - } - - lseek(ref->fd, 0L, SEEK_SET); - if (ftruncate(ref->fd, 0) < 0) - { - pthread_mutex_unlock(&db->lock); - Log(LOG_ERR, "Failed to truncate file on disk."); - Log(LOG_ERR, "Error on fd %d: %s", ref->fd, strerror(errno)); - return false; - } - - JsonEncode(ref->json, ref->stream, JSON_DEFAULT); - StreamClose(ref->stream); - - if (db->cache) - { - char *key = DbHashKey(ref->name); - - if (HashMapGet(db->cache, key)) - { - db->cacheSize -= ref->size; - ref->size = DbComputeSize(ref->json); - db->cacheSize += ref->size; - - /* If this ref has grown significantly since we last - * computed its size, it may have filled the cache and - * require some items to be evicted. */ - DbCacheEvict(db); - - destroy = false; - } - else - { - destroy = true; - } - - Free(key); - } - else - { - destroy = true; - } - - if (destroy) - { - JsonFree(ref->json); - StringArrayFree(ref->name); - - Free(ref); - } - - pthread_mutex_unlock(&db->lock); - return true; -} - -bool -DbExists(Db * db, size_t nArgs,...) -{ - va_list ap; - Array *args; - char *file; - bool ret; - - va_start(ap, nArgs); - args = ArrayFromVarArgs(nArgs, ap); - va_end(ap); - - if (!args) - { - return false; - } - - pthread_mutex_lock(&db->lock); - - file = DbFileName(db, args); - ret = (UtilLastModified(file) != 0); - - pthread_mutex_unlock(&db->lock); - - Free(file); - ArrayFree(args); - - return ret; -} - -Array * -DbList(Db * db, size_t nArgs,...) -{ - Array *result; - Array *path; - DIR *files; - struct dirent *file; - char *dir; - va_list ap; - - if (!db || !nArgs) - { - return NULL; - } - - result = ArrayCreate(); - if (!result) - { - return NULL; - } - - va_start(ap, nArgs); - path = ArrayFromVarArgs(nArgs, ap); - dir = DbDirName(db, path, 0); - - pthread_mutex_lock(&db->lock); - - files = opendir(dir); - if (!files) - { - ArrayFree(path); - ArrayFree(result); - Free(dir); - pthread_mutex_unlock(&db->lock); - return NULL; - } - while ((file = readdir(files))) - { - size_t namlen = strlen(file->d_name); - - if (namlen > 5) - { - int nameOffset = namlen - 5; - - if (StrEquals(file->d_name + nameOffset, ".json")) - { - file->d_name[nameOffset] = '\0'; - ArrayAdd(result, StrDuplicate(file->d_name)); - } - } - } - closedir(files); - - ArrayFree(path); - Free(dir); - pthread_mutex_unlock(&db->lock); - - return result; -} - -void -DbListFree(Array * arr) -{ - StringArrayFree(arr); -} - -HashMap * -DbJson(DbRef * ref) -{ - return ref ? ref->json : NULL; -} - -bool -DbJsonSet(DbRef * ref, HashMap * json) -{ - if (!ref || !json) - { - return false; - } - - JsonFree(ref->json); - ref->json = JsonDuplicate(json); - return true; -} diff --git a/src/Db/Db.c b/src/Db/Db.c new file mode 100644 index 0000000..59c1bf8 --- /dev/null +++ b/src/Db/Db.c @@ -0,0 +1,576 @@ +/* + * Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> + * + * 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 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 +#include + +#include +#include +#include + +#include +#include + +#include "Db/Internal.h" + +void +StringArrayFree(Array * arr) +{ + size_t i; + + for (i = 0; i < ArraySize(arr); i++) + { + Free(ArrayGet(arr, i)); + } + + ArrayFree(arr); +} + +static ssize_t DbComputeSize(HashMap *); + +static ssize_t +DbComputeSizeOfValue(JsonValue * val) +{ + MemoryInfo *a; + ssize_t total = 0; + + size_t i; + + union + { + char *str; + Array *arr; + } u; + + if (!val) + { + return -1; + } + + a = MemoryInfoGet(val); + if (a) + { + total += MemoryInfoGetSize(a); + } + + switch (JsonValueType(val)) + { + case JSON_OBJECT: + total += DbComputeSize(JsonValueAsObject(val)); + break; + case JSON_ARRAY: + u.arr = JsonValueAsArray(val); + a = MemoryInfoGet(u.arr); + + if (a) + { + total += MemoryInfoGetSize(a); + } + + for (i = 0; i < ArraySize(u.arr); i++) + { + total += DbComputeSizeOfValue(ArrayGet(u.arr, i)); + } + break; + case JSON_STRING: + u.str = JsonValueAsString(val); + a = MemoryInfoGet(u.str); + if (a) + { + total += MemoryInfoGetSize(a); + } + break; + case JSON_NULL: + case JSON_INTEGER: + case JSON_FLOAT: + case JSON_BOOLEAN: + default: + /* These don't use any extra heap space */ + break; + } + return total; +} + +static ssize_t +DbComputeSize(HashMap * json) +{ + char *key; + JsonValue *val; + MemoryInfo *a; + size_t total; + + if (!json) + { + return -1; + } + + total = 0; + + a = MemoryInfoGet(json); + if (a) + { + total += MemoryInfoGetSize(a); + } + + while (HashMapIterate(json, &key, (void **) &val)) + { + a = MemoryInfoGet(key); + if (a) + { + total += MemoryInfoGetSize(a); + } + + total += DbComputeSizeOfValue(val); + } + + return total; +} + +static char * +DbHashKey(Array * args) +{ + size_t i; + char *str = NULL; + + for (i = 0; i < ArraySize(args); i++) + { + char *tmp = StrConcat(2, str, ArrayGet(args, i)); + + Free(str); + str = tmp; + } + + return str; +} + +static void +DbCacheEvict(Db * db) +{ + DbRef *ref = db->leastRecent; + DbRef *tmp; + + while (ref && db->cacheSize > db->maxCache) + { + char *hash; + + JsonFree(ref->json); + + hash = DbHashKey(ref->name); + HashMapDelete(db->cache, hash); + Free(hash); + + StringArrayFree(ref->name); + + db->cacheSize -= ref->size; + + if (ref->next) + { + ref->next->prev = ref->prev; + } + else + { + db->mostRecent = ref->prev; + } + + if (ref->prev) + { + ref->prev->next = ref->next; + } + else + { + db->leastRecent = ref->next; + } + + tmp = ref->next; + Free(ref); + + ref = tmp; + } +} + +void +DbMaxCacheSet(Db * db, size_t cache) +{ + if (!db) + { + return; + } + + pthread_mutex_lock(&db->lock); + + db->maxCache = cache; + if (db->maxCache && !db->cache) + { + db->cache = HashMapCreate(); + db->cacheSize = 0; + } + + DbCacheEvict(db); + + pthread_mutex_unlock(&db->lock); +} + +void +DbClose(Db * db) +{ + if (!db) + { + return; + } + + pthread_mutex_lock(&db->lock); + if (db->close) + { + db->close(db); + } + DbMaxCacheSet(db, 0); + DbCacheEvict(db); + HashMapFree(db->cache); + + pthread_mutex_unlock(&db->lock); + pthread_mutex_destroy(&db->lock); + + Free(db); +} + +DbRef * +DbCreate(Db * db, size_t nArgs,...) +{ + va_list ap; + Array *args; + DbRef *ret; + + if (!db || !db->create) + { + return NULL; + } + + va_start(ap, nArgs); + args = ArrayFromVarArgs(nArgs, ap); + va_end(ap); + + if (!args) + { + return NULL; + } + + ret = db->create(db, args); + ArrayFree(args); + + return ret; +} + +bool +DbDelete(Db * db, size_t nArgs,...) +{ + (void) nArgs; + (void) db; + /* TODO */ + /*va_list ap; + Array *args; + char *file; + char *hash; + bool ret = true; + DbRef *ref; + + if (!db) + { + return false; + } + + va_start(ap, nArgs); + args = ArrayFromVarArgs(nArgs, ap); + va_end(ap); + + pthread_mutex_lock(&db->lock); + + hash = DbHashKey(args); + file = DbFileName(db, args); + + ref = HashMapGet(db->cache, hash); + if (ref) + { + HashMapDelete(db->cache, hash); + JsonFree(ref->json); + StringArrayFree(ref->name); + + db->cacheSize -= ref->size; + + if (ref->next) + { + ref->next->prev = ref->prev; + } + else + { + db->mostRecent = ref->prev; + } + + if (ref->prev) + { + ref->prev->next = ref->next; + } + else + { + db->leastRecent = ref->next; + } + + if (!db->leastRecent) + { + db->leastRecent = db->mostRecent; + } + + Free(ref); + } + + Free(hash); + + if (UtilLastModified(file)) + { + ret = (remove(file) == 0); + } + + pthread_mutex_unlock(&db->lock); + + ArrayFree(args); + Free(file); + return ret;*/ + return false; +} + +DbRef * +DbLock(Db * db, size_t nArgs,...) +{ + va_list ap; + Array *args; + DbRef *ret; + + va_start(ap, nArgs); + args = ArrayFromVarArgs(nArgs, ap); + va_end(ap); + + if (!args || !db->lockFunc) + { + return NULL; + } + + ret = db->lockFunc(db, args); + + ArrayFree(args); + + return ret; +} + +bool +DbUnlock(Db * db, DbRef * ref) +{ + if (!db || !ref || !db->unlock) + { + return false; + } + + return db->unlock(db, ref); +} + +bool +DbExists(Db * db, size_t nArgs,...) +{ + (void) nArgs; + (void) db; + return false; + /*va_list ap; + Array *args; + char *file; + bool ret; + + va_start(ap, nArgs); + args = ArrayFromVarArgs(nArgs, ap); + va_end(ap); + + if (!args) + { + return false; + } + + pthread_mutex_lock(&db->lock); + + file = DbFileName(db, args); + ret = (UtilLastModified(file) != 0); + + pthread_mutex_unlock(&db->lock); + + Free(file); + ArrayFree(args); + + return ret;*/ +} + +Array * +DbList(Db * db, size_t nArgs,...) +{ + (void) db; + (void) nArgs; + /* TODO */ + /*Array *result; + Array *path; + DIR *files; + struct dirent *file; + char *dir; + va_list ap; + + if (!db || !nArgs) + { + return NULL; + } + + result = ArrayCreate(); + if (!result) + { + return NULL; + } + + va_start(ap, nArgs); + path = ArrayFromVarArgs(nArgs, ap); + dir = DbDirName(db, path, 0); + + pthread_mutex_lock(&db->lock); + + files = opendir(dir); + if (!files) + { + ArrayFree(path); + ArrayFree(result); + Free(dir); + pthread_mutex_unlock(&db->lock); + return NULL; + } + while ((file = readdir(files))) + { + size_t namlen = strlen(file->d_name); + + if (namlen > 5) + { + int nameOffset = namlen - 5; + + if (StrEquals(file->d_name + nameOffset, ".json")) + { + file->d_name[nameOffset] = '\0'; + ArrayAdd(result, StrDuplicate(file->d_name)); + } + } + } + closedir(files); + + ArrayFree(path); + Free(dir); + pthread_mutex_unlock(&db->lock); + + return result;*/ + return NULL; +} + +void +DbListFree(Array * arr) +{ + StringArrayFree(arr); +} + +HashMap * +DbJson(DbRef * ref) +{ + return ref ? ref->json : NULL; +} + +bool +DbJsonSet(DbRef * ref, HashMap * json) +{ + if (!ref || !json) + { + return false; + } + + JsonFree(ref->json); + ref->json = JsonDuplicate(json); + return true; +} +void +DbInit(Db *db) +{ + pthread_mutexattr_t attr; + if (!db) + { + return; + } + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&db->lock, &attr); + pthread_mutexattr_destroy(&attr); + + db->mostRecent = NULL; + db->leastRecent = NULL; + db->cacheSize = 0; + + if (db->maxCache) + { + db->cache = HashMapCreate(); + } + else + { + db->cache = NULL; + } +} +void +DbRefInit(Db *db, DbRef *ref) +{ + if (!db || !ref) + { + return; + } + ref->prev = db->mostRecent; + ref->next = NULL; + ref->json = NULL; + ref->name = NULL; + + /* As default values to be overwritten by impls */ + ref->ts = UtilTsMillis(); + ref->size = 0; + + if (db->mostRecent) + { + db->mostRecent->next = ref; + } + if (!db->leastRecent) + { + db->leastRecent = ref; + } + db->mostRecent = ref; +} diff --git a/src/Db/Flat.c b/src/Db/Flat.c new file mode 100644 index 0000000..96c6977 --- /dev/null +++ b/src/Db/Flat.c @@ -0,0 +1,274 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include "Db/Internal.h" + +typedef struct FlatDb { + Db base; + char *dir; + /* Theres not much to do here. */ +} FlatDb; +typedef struct FlatDbRef { + DbRef base; + + Stream *stream; + int fd; +} FlatDbRef; + +static char +DbSanitiseChar(char input) +{ + switch (input) + { + case '/': + return '_'; + case '.': + return '-'; + } + return input; +} + +static char * +DbDirName(FlatDb * db, Array * args, size_t strip) +{ + size_t i, j; + char *str = StrConcat(2, db->dir, "/"); + + for (i = 0; i < ArraySize(args) - strip; i++) + { + char *tmp; + char *sanitise = StrDuplicate(ArrayGet(args, i)); + for (j = 0; j < strlen(sanitise); j++) + { + sanitise[j] = DbSanitiseChar(sanitise[j]); + } + + tmp = StrConcat(3, str, sanitise, "/"); + + Free(str); + Free(sanitise); + + str = tmp; + } + + return str; +} +static char * +DbFileName(FlatDb * db, Array * args) +{ + size_t i; + char *str = StrConcat(2, db->dir, "/"); + + for (i = 0; i < ArraySize(args); i++) + { + char *tmp; + char *arg = StrDuplicate(ArrayGet(args, i)); + size_t j = 0; + + /* Sanitize name to prevent directory traversal attacks */ + while (arg[j]) + { + arg[j] = DbSanitiseChar(arg[j]); + j++; + } + + tmp = StrConcat(3, str, arg, + (i < ArraySize(args) - 1) ? "/" : ".json"); + + Free(arg); + Free(str); + + str = tmp; + } + + return str; +} + +static DbRef * +FlatLock(Db *d, Array *dir) +{ + FlatDb *db = (FlatDb *) d; + FlatDbRef *ref = NULL; + size_t i; + char *path; + if (!d || !dir) + { + return NULL; + } + + pthread_mutex_lock(&d->lock); + path = DbFileName(db, dir); + /* TODO: Caching */ + { + int fd = open(path, O_RDWR); + Stream *stream; + struct flock lock; + if (fd == -1) + { + ref = NULL; + goto end; + } + + stream = StreamFd(fd); + + lock.l_start = 0; + lock.l_len = 0; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + + if (fcntl(fd, F_SETLK, &lock) < 0) + { + StreamClose(stream); + ref = NULL; + goto end; + } + + ref = Malloc(sizeof(*ref)); + DbRefInit(d, &(ref->base)); + ref->fd = fd; + ref->stream = stream; + ref->base.ts = UtilLastModified(path); + ref->base.json = JsonDecode(stream); + if (!ref->base.json) + { + Free(ref); + StreamClose(stream); + ref = NULL; + goto end; + } + + + ref->base.name = ArrayCreate(); + for (i = 0; i < ArraySize(dir); i++) + { + ArrayAdd( + ref->base.name, + StrDuplicate(ArrayGet(dir, i)) + ); + } + } +end: + Free(path); + if (!ref) + { + pthread_mutex_unlock(&d->lock); + } + return (DbRef *) ref; +} + +static bool +FlatUnlock(Db *d, DbRef *r) +{ + FlatDbRef *ref = (FlatDbRef *) r; + + if (!d || !r) + { + return false; + } + + lseek(ref->fd, 0L, SEEK_SET); + if (ftruncate(ref->fd, 0) < 0) + { + pthread_mutex_unlock(&d->lock); + Log(LOG_ERR, "Failed to truncate file on disk."); + Log(LOG_ERR, "Error on fd %d: %s", ref->fd, strerror(errno)); + return false; + } + + JsonEncode(ref->base.json, ref->stream, JSON_DEFAULT); + StreamClose(ref->stream); + + JsonFree(ref->base.json); + StringArrayFree(ref->base.name); + Free(ref); + + pthread_mutex_unlock(&d->lock); + return true; +} +static DbRef * +FlatCreate(Db *d, Array *dir) +{ + FlatDb *db = (FlatDb *) d; + char *path, *dirPath; + Stream *fp; + DbRef *ret; + + if (!d || !dir) + { + return NULL; + } + pthread_mutex_lock(&d->lock); + + path = DbFileName(db, dir); + if (UtilLastModified(path)) + { + Free(path); + pthread_mutex_unlock(&d->lock); + return NULL; + } + + dirPath = DbDirName(db, dir, 1); + if (UtilMkdir(dirPath, 0750) < 0) + { + Free(path); + Free(dirPath); + pthread_mutex_unlock(&d->lock); + return NULL; + } + Free(dirPath); + + fp = StreamOpen(path, "w"); + Free(path); + if (!fp) + { + pthread_mutex_unlock(&d->lock); + return NULL; + } + + StreamPuts(fp, "{}"); + StreamClose(fp); + + /* FlatLock() will lock again for us */ + pthread_mutex_unlock(&d->lock); + + ret = FlatLock(d, dir); + + return ret; +} + +Db * +DbOpen(char *dir, size_t cache) +{ + FlatDb *db; + if (!dir) + { + return NULL; + } + db = Malloc(sizeof(*db)); + DbInit(&(db->base)); + db->dir = dir; + db->base.cacheSize = cache; + + db->base.lockFunc = FlatLock; + db->base.unlock = FlatUnlock; + db->base.create = FlatCreate; + db->base.close = NULL; + + return (Db *) db; +} diff --git a/src/Db/Internal.h b/src/Db/Internal.h new file mode 100644 index 0000000..3219ed2 --- /dev/null +++ b/src/Db/Internal.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_DB_INTERNAL_H +#define CYTOPLASM_DB_INTERNAL_H + +#include +#include + +#include + +/* TODO: Define the base structures to define a database internally. + * All "implementations" shall have them as a first element, so that + * basic, general functions can work on them properly. */ + +/* The base structure of a database */ +struct Db +{ + pthread_mutex_t lock; + + size_t cacheSize; + size_t maxCache; + HashMap *cache; + + /* + * The cache uses a double linked list (see DbRef + * below) to know which objects are most and least + * recently used. The following diagram helps me + * know what way all the pointers go, because it + * can get very confusing sometimes. For example, + * there's nothing stopping "next" from pointing to + * least recent, and "prev" from pointing to most + * recent, so hopefully this clarifies the pointer + * terminology used when dealing with the linked + * list: + * + * mostRecent leastRecent + * | prev prev | prev + * +---+ ---> +---+ ---> +---+ ---> NULL + * |ref| |ref| |ref| + * NULL <--- +---+ <--- +---+ <--- +---+ + * next next next + */ + DbRef *mostRecent; + DbRef *leastRecent; + + /* TODO: Functions for implementation-specific operations + * (opening a ref, closing a db, removing an entry, ...) */ + DbRef * (*lockFunc)(Db *, Array *); + DbRef * (*create)(Db *, Array *); + bool (*unlock)(Db *, DbRef *); + void (*close)(Db *); + + /* Implementation-specific constructs */ +}; + +struct DbRef +{ + HashMap *json; + + uint64_t ts; + size_t size; + + Array *name; + + DbRef *prev; + DbRef *next; + + /* TODO: Functions for implementation-specific operations */ + + /* Implementation-specific constructs */ +}; + +extern void DbInit(Db *); +extern void DbRefInit(Db *, DbRef *); +extern void StringArrayFree(Array *); + +#endif diff --git a/src/Db/LMDB.c b/src/Db/LMDB.c new file mode 100644 index 0000000..d926795 --- /dev/null +++ b/src/Db/LMDB.c @@ -0,0 +1,39 @@ +#include + +#include + +#include "Db/Internal.h" + +#ifdef EDB_LMDB + +typedef struct LMDB { + Db base; /* The base implementation required to pass */ +} LMDB; +Db * +DbOpenLMDB(char *dir, size_t size) +{ + /* TODO */ + LMDB *db; + if (!dir || !size) + { + return NULL; + } + + db = Malloc(sizeof(*db)); + DbInit(db); + + (void) size; + (void) dir; + return db; +} + +#else +Db * +DbOpenLMDB(char *dir, size_t size) +{ + /* Unimplemented function */ + (void) size; + (void) dir; + return NULL; +} +#endif From 59dbfae1aeac73f8f640ae3b50dadfb4700b0031 Mon Sep 17 00:00:00 2001 From: LDA Date: Thu, 8 Aug 2024 09:53:51 +0200 Subject: [PATCH 08/18] [MOD/WIP] Separate all DB operations Somewhat untested, but I fail to see how it could fail, right? Next up: Getting the basics of LMDB up and running. --- src/Db/Db.c | 145 ++++++++-------------------------------------- src/Db/Flat.c | 97 +++++++++++++++++++++++++++++++ src/Db/Internal.h | 6 +- 3 files changed, 127 insertions(+), 121 deletions(-) diff --git a/src/Db/Db.c b/src/Db/Db.c index 59c1bf8..4b9b94a 100644 --- a/src/Db/Db.c +++ b/src/Db/Db.c @@ -292,15 +292,9 @@ DbCreate(Db * db, size_t nArgs,...) bool DbDelete(Db * db, size_t nArgs,...) { - (void) nArgs; - (void) db; - /* TODO */ - /*va_list ap; + va_list ap; Array *args; - char *file; - char *hash; bool ret = true; - DbRef *ref; if (!db) { @@ -311,59 +305,10 @@ DbDelete(Db * db, size_t nArgs,...) args = ArrayFromVarArgs(nArgs, ap); va_end(ap); - pthread_mutex_lock(&db->lock); - - hash = DbHashKey(args); - file = DbFileName(db, args); - - ref = HashMapGet(db->cache, hash); - if (ref) - { - HashMapDelete(db->cache, hash); - JsonFree(ref->json); - StringArrayFree(ref->name); - - db->cacheSize -= ref->size; - - if (ref->next) - { - ref->next->prev = ref->prev; - } - else - { - db->mostRecent = ref->prev; - } - - if (ref->prev) - { - ref->prev->next = ref->next; - } - else - { - db->leastRecent = ref->next; - } - - if (!db->leastRecent) - { - db->leastRecent = db->mostRecent; - } - - Free(ref); - } - - Free(hash); - - if (UtilLastModified(file)) - { - ret = (remove(file) == 0); - } - - pthread_mutex_unlock(&db->lock); + ret = db->delete(db, args); ArrayFree(args); - Free(file); - return ret;*/ - return false; + return ret; } DbRef * @@ -403,13 +348,13 @@ DbUnlock(Db * db, DbRef * ref) bool DbExists(Db * db, size_t nArgs,...) { - (void) nArgs; - (void) db; - return false; - /*va_list ap; + va_list ap; Array *args; - char *file; bool ret; + if (!db || !nArgs || !db->exists) + { + return false; + } va_start(ap, nArgs); args = ArrayFromVarArgs(nArgs, ap); @@ -420,81 +365,32 @@ DbExists(Db * db, size_t nArgs,...) return false; } - pthread_mutex_lock(&db->lock); - - file = DbFileName(db, args); - ret = (UtilLastModified(file) != 0); - - pthread_mutex_unlock(&db->lock); - - Free(file); + ret = db->exists(db, args); ArrayFree(args); - return ret;*/ + return ret; } Array * DbList(Db * db, size_t nArgs,...) { - (void) db; - (void) nArgs; - /* TODO */ - /*Array *result; + Array *result; Array *path; - DIR *files; - struct dirent *file; - char *dir; va_list ap; - if (!db || !nArgs) - { - return NULL; - } - - result = ArrayCreate(); - if (!result) + if (!db || !nArgs || !db->list) { return NULL; } va_start(ap, nArgs); path = ArrayFromVarArgs(nArgs, ap); - dir = DbDirName(db, path, 0); - - pthread_mutex_lock(&db->lock); - - files = opendir(dir); - if (!files) - { - ArrayFree(path); - ArrayFree(result); - Free(dir); - pthread_mutex_unlock(&db->lock); - return NULL; - } - while ((file = readdir(files))) - { - size_t namlen = strlen(file->d_name); - - if (namlen > 5) - { - int nameOffset = namlen - 5; - - if (StrEquals(file->d_name + nameOffset, ".json")) - { - file->d_name[nameOffset] = '\0'; - ArrayAdd(result, StrDuplicate(file->d_name)); - } - } - } - closedir(files); + va_end(ap); + result = db->list(db, path); + ArrayFree(path); - Free(dir); - pthread_mutex_unlock(&db->lock); - - return result;*/ - return NULL; + return result; } void @@ -574,3 +470,12 @@ DbRefInit(Db *db, DbRef *ref) } db->mostRecent = ref; } +void +StringArrayAppend(Array *arr, char *str) +{ + if (!arr || !str) + { + return; + } + ArrayAdd(arr, StrDuplicate(str)); +} diff --git a/src/Db/Flat.c b/src/Db/Flat.c index 96c6977..4ecf19c 100644 --- a/src/Db/Flat.c +++ b/src/Db/Flat.c @@ -252,6 +252,100 @@ FlatCreate(Db *d, Array *dir) return ret; } +static bool +FlatDelete(Db *d, Array *dir) +{ + bool ret = true; + char *file; + FlatDb *db = (FlatDb *) d; + if (!d || !dir) + { + return false; + } + + pthread_mutex_lock(&d->lock); + file = DbFileName(db, dir); + + /* TODO: Unlink the entry from the linkedlist */ + if (UtilLastModified(file)) + { + ret = remove(file) == 0; + } + + Free(file); + pthread_mutex_lock(&d->lock); + return ret; +} + +static Array * +FlatList(Db *d, Array *dir) +{ + FlatDb *db = (FlatDb *) d; + struct dirent *file; + Array *ret; + DIR *files; + char *path; + + if (!d || !dir) + { + return NULL; + } + + pthread_mutex_lock(&d->lock); + + path = DbDirName(db, dir, 0); + files = opendir(path); + if (!files) + { + Free(path); + pthread_mutex_unlock(&d->lock); + return NULL; + } + + ret = ArrayCreate(); + while ((file = readdir(files))) + { + size_t namlen = strlen(file->d_name); + + if (namlen > 5) + { + int nameOffset = namlen - 5; + + if (StrEquals(file->d_name + nameOffset, ".json")) + { + file->d_name[nameOffset] = '\0'; + StringArrayAppend(ret, file->d_name); + } + } + } + closedir(files); + Free(path); + + pthread_mutex_unlock(&d->lock); + return ret; +} +static bool +FlatExists(Db *d, Array *dir) +{ + FlatDb *db = (FlatDb *) d; + char *path; + bool ret; + if (!d || !dir) + { + return NULL; + } + + pthread_mutex_lock(&d->lock); + + path = DbFileName(db, dir); + ret = UtilLastModified(path) != 0; + Free(path); + + pthread_mutex_unlock(&d->lock); + + return ret; +} + Db * DbOpen(char *dir, size_t cache) { @@ -268,6 +362,9 @@ DbOpen(char *dir, size_t cache) db->base.lockFunc = FlatLock; db->base.unlock = FlatUnlock; db->base.create = FlatCreate; + db->base.delete = FlatDelete; + db->base.exists = FlatExists; + db->base.list = FlatList; db->base.close = NULL; return (Db *) db; diff --git a/src/Db/Internal.h b/src/Db/Internal.h index 3219ed2..d6907a8 100644 --- a/src/Db/Internal.h +++ b/src/Db/Internal.h @@ -64,11 +64,14 @@ struct Db DbRef *mostRecent; DbRef *leastRecent; - /* TODO: Functions for implementation-specific operations + /* Functions for implementation-specific operations * (opening a ref, closing a db, removing an entry, ...) */ DbRef * (*lockFunc)(Db *, Array *); DbRef * (*create)(Db *, Array *); + Array * (*list)(Db *, Array *); bool (*unlock)(Db *, DbRef *); + bool (*delete)(Db *, Array *); + bool (*exists)(Db *, Array *); void (*close)(Db *); /* Implementation-specific constructs */ @@ -94,5 +97,6 @@ struct DbRef extern void DbInit(Db *); extern void DbRefInit(Db *, DbRef *); extern void StringArrayFree(Array *); +extern void StringArrayAppend(Array *, char *); #endif From 5cb51a4d5815a7e67fc448362c70d265195f81ed Mon Sep 17 00:00:00 2001 From: LDA Date: Thu, 8 Aug 2024 14:38:23 +0200 Subject: [PATCH 09/18] [ADD/WIP] Start to add LMDB operations --- src/Db/LMDB.c | 257 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 250 insertions(+), 7 deletions(-) diff --git a/src/Db/LMDB.c b/src/Db/LMDB.c index d926795..a5750b9 100644 --- a/src/Db/LMDB.c +++ b/src/Db/LMDB.c @@ -1,30 +1,270 @@ #include - +#include +#include #include #include "Db/Internal.h" -#ifdef EDB_LMDB +#include +#include +#include + +#ifdef EDB_IMPL + +#include typedef struct LMDB { Db base; /* The base implementation required to pass */ + + MDB_env *environ; } LMDB; +typedef struct LMDBRef { + DbRef base; + + /* TODO: LMDB-dependent stuff */ + MDB_txn *transaction; + MDB_dbi dbi; +} LMDBRef; + +/* Helper functions */ +static MDB_val +LMDBTranslateKey(Array *key) +{ + MDB_val ret = { 0 }; + char *data = NULL; + size_t length = 0; + size_t i; + if (!key || ArraySize(key) > 255) + { + return ret; + } + + data = Realloc(data, ++length); + data[0] = ArraySize(key); + + /* Now, let's push every item */ + for (i = 0; i < ArraySize(key); i++) + { + char *entry = ArrayGet(key, i); + size_t offset = length; + + data = Realloc(data, (length += strlen(entry) + 1)); + memcpy(data + offset, entry, strlen(entry)); + data[length - 1] = '\0'; + } + + /* We now have every key */ + ret.mv_size = length; + ret.mv_data = data; + return ret; +} +static void +LMDBKillKey(MDB_val key) +{ + if (!key.mv_data || !key.mv_size) + { + return; + } + + Free(key.mv_data); +} + +static bool +LMDBUnlock(Db *d, DbRef *r) +{ + LMDBRef *ref = (LMDBRef *) r; + LMDB *db = (LMDB *) d; + FILE *fakestream; + Stream *stream; + MDB_val key, val; + bool ret; + + if (!d || !r) + { + return false; + } + + val.mv_data = NULL; + val.mv_size = 0; + key = LMDBTranslateKey(r->name); + + fakestream = open_memstream((char **) &val.mv_data, &val.mv_size); + stream = StreamFile(fakestream); + JsonEncode(r->json, stream, JSON_DEFAULT); + StreamFlush(stream); + StreamClose(stream); + + mdb_put(ref->transaction, ref->dbi, &key, &val, 0); + + mdb_txn_commit(ref->transaction); + mdb_dbi_close(db->environ, ref->dbi); + StringArrayFree(ref->base.name); + JsonFree(ref->base.json); + Free(ref); + + LMDBKillKey(key); + if (val.mv_data) + { + free(val.mv_data); + } + pthread_mutex_unlock(&d->lock); + return ret; +} +static DbRef * +LMDBCreate(Db *d, Array *k) +{ + LMDB *db = (LMDB *) d; + MDB_txn *transaction; + LMDBRef *ret = NULL; + MDB_val key, empty_json; + MDB_dbi dbi; + int code; + if (!d || !k) + { + return NULL; + } + + pthread_mutex_lock(&d->lock); + key = LMDBTranslateKey(k); + + /* create a txn */ + if ((code = mdb_txn_begin(db->environ, NULL, 0, &transaction)) != 0) + { + /* Very bad! */ + Log(LOG_ERR, + "%s: could not begin transaction: %s", + __func__, mdb_strerror(code) + ); + pthread_mutex_unlock(&d->lock); + goto end; + } + /* apparently you need to give it a dbi */ + if ((code = mdb_dbi_open(transaction, "db", MDB_CREATE, &dbi)) != 0) + { + Log(LOG_ERR, + "%s: could not get transaction dbi: %s", + __func__, mdb_strerror(code) + ); + pthread_mutex_unlock(&d->lock); + goto end; + + } + + empty_json.mv_size = 2; + empty_json.mv_data = "{}"; + /* put data in it */ + code = mdb_put(transaction, dbi, &key, &empty_json, MDB_NOOVERWRITE); + if (code == MDB_KEYEXIST) + { + mdb_dbi_close(db->environ, dbi); + mdb_txn_abort(transaction); + goto end; + } + else if (code == MDB_MAP_FULL) + { + Log(LOG_ERR, "%s: db is full", __func__); + mdb_dbi_close(db->environ, dbi); + mdb_txn_abort(transaction); + goto end; + } + else if (code != 0) + { + Log(LOG_ERR, "%s: mdb_put failure: %s", __func__, mdb_strerror(code)); + mdb_dbi_close(db->environ, dbi); + mdb_txn_abort(transaction); + goto end; + } + + ret = Malloc(sizeof(*ret)); + DbRefInit(d, (DbRef *) ret); + /* TODO: Timestamp */ + { + size_t i; + ret->base.name = ArrayCreate(); + for (i = 0; i < ArraySize(k); i++) + { + char *ent = ArrayGet(k, i); + StringArrayAppend(ret->base.name, ent); + } + } + ret->base.json = HashMapCreate(); + ret->transaction = transaction; + ret->dbi = dbi; +end: + LMDBKillKey(key); + return (DbRef *) ret; +} + + +/* Implementation functions */ +static void +LMDBClose(Db *d) +{ + LMDB *db = (LMDB *) d; + if (!d) + { + return; + } + + mdb_env_close(db->environ); +} + Db * DbOpenLMDB(char *dir, size_t size) { /* TODO */ + MDB_env *env = NULL; + int code; LMDB *db; if (!dir || !size) { return NULL; } - db = Malloc(sizeof(*db)); - DbInit(db); + /* Try initialising LMDB */ + if ((code = mdb_env_create(&env)) != 0) + { + Log(LOG_ERR, + "%s: could not create LMDB env: %s", + __func__, mdb_strerror(code) + ); + return NULL; + } + if ((code = mdb_env_set_mapsize(env, size) != 0)) + { + Log(LOG_ERR, + "%s: could not set mapsize to %lu: %s", + __func__, (unsigned long) size, + mdb_strerror(code) + ); + mdb_env_close(env); + return NULL; + } + mdb_env_set_maxdbs(env, 4); + if ((code = mdb_env_open(env, dir, 0, 0644)) != 0) + { + Log(LOG_ERR, + "%s: could not open LMDB env: %s", + __func__, mdb_strerror(code) + ); + mdb_env_close(env); + return NULL; + } - (void) size; - (void) dir; - return db; + + db = Malloc(sizeof(*db)); + DbInit((Db *) db); + db->environ = env; + + db->base.lockFunc = NULL; + db->base.create = LMDBCreate; + db->base.unlock = LMDBUnlock; + db->base.delete = NULL; + db->base.exists = NULL; + db->base.close = LMDBClose; + db->base.list = NULL; + + return (Db *) db; } #else @@ -32,6 +272,9 @@ Db * DbOpenLMDB(char *dir, size_t size) { /* Unimplemented function */ + Log(LOG_ERR, + "LMDB support is not enabled. Please compile with --use-lmdb" + ); (void) size; (void) dir; return NULL; From 074340195571f292f9a487d98fbd8d426b3c585a Mon Sep 17 00:00:00 2001 From: LDA Date: Thu, 8 Aug 2024 16:25:09 +0200 Subject: [PATCH 10/18] [ADD/WIP] Most DB operations for LMDB Somewhat untested. I want to go on a test run soon. Next up: aargh... listing... The one thing LMDB may suck at. --- src/Db/LMDB.c | 205 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 201 insertions(+), 4 deletions(-) diff --git a/src/Db/LMDB.c b/src/Db/LMDB.c index a5750b9..1e25c30 100644 --- a/src/Db/LMDB.c +++ b/src/Db/LMDB.c @@ -68,6 +68,203 @@ LMDBKillKey(MDB_val key) Free(key.mv_data); } +static HashMap * +LMDBDecode(MDB_val val) +{ + FILE *fakefile; + Stream *fakestream; + HashMap *ret; + if (!val.mv_data || !val.mv_size) + { + return NULL; + } + + fakefile = fmemopen(val.mv_data, val.mv_size, "r"); + fakestream = StreamFile(fakefile); + ret = JsonDecode(fakestream); + StreamClose(fakestream); + + return ret; +} + +static DbRef * +LMDBLock(Db *d, Array *k) +{ + LMDB *db = (LMDB *) d; + MDB_txn *transaction; + LMDBRef *ret = NULL; + MDB_val key, empty_json; + MDB_dbi dbi; + int code; + if (!d || !k) + { + return NULL; + } + + pthread_mutex_lock(&d->lock); + key = LMDBTranslateKey(k); + + /* create a txn */ + if ((code = mdb_txn_begin(db->environ, NULL, 0, &transaction)) != 0) + { + /* Very bad! */ + Log(LOG_ERR, + "%s: could not begin transaction: %s", + __func__, mdb_strerror(code) + ); + pthread_mutex_unlock(&d->lock); + goto end; + } + /* apparently you need to give it a dbi */ + if ((code = mdb_dbi_open(transaction, "db", MDB_CREATE, &dbi)) != 0) + { + Log(LOG_ERR, + "%s: could not get transaction dbi: %s", + __func__, mdb_strerror(code) + ); + pthread_mutex_unlock(&d->lock); + goto end; + + } + + empty_json.mv_size = 0; + empty_json.mv_data = NULL; + /* get data from it */ + code = mdb_get(transaction, dbi, &key, &empty_json); + if (code == MDB_NOTFOUND) + { + Log(LOG_ERR, + "%s: mdb_get failure: %s", + __func__, mdb_strerror(code) + ); + mdb_txn_abort(transaction); + mdb_dbi_close(db->environ, dbi); + goto end; + } + else if (code != 0) + { + Log(LOG_ERR, + "%s: mdb_get failure: %s", + __func__, mdb_strerror(code) + ); + mdb_txn_abort(transaction); + mdb_dbi_close(db->environ, dbi); + goto end; + } + + ret = Malloc(sizeof(*ret)); + DbRefInit(d, (DbRef *) ret); + /* TODO: Timestamp */ + { + size_t i; + ret->base.name = ArrayCreate(); + for (i = 0; i < ArraySize(k); i++) + { + char *ent = ArrayGet(k, i); + StringArrayAppend(ret->base.name, ent); + } + } + ret->base.json = LMDBDecode(empty_json); + ret->transaction = transaction; + ret->dbi = dbi; +end: + LMDBKillKey(key); + return (DbRef *) ret; +} +static bool +LMDBExists(Db *d, Array *k) +{ + MDB_val key, empty; + LMDB *db = (LMDB *) d; + MDB_txn *transaction; + MDB_dbi dbi; + int code; + bool ret = false; + if (!d || !k) + { + return false; + } + + pthread_mutex_lock(&d->lock); + key = LMDBTranslateKey(k); + + /* create a txn */ + if ((code = mdb_txn_begin(db->environ, NULL, 0, &transaction)) != 0) + { + /* Very bad! */ + Log(LOG_ERR, + "%s: could not begin transaction: %s", + __func__, mdb_strerror(code) + ); + goto end; + } + /* apparently you need to give it a dbi */ + if ((code = mdb_dbi_open(transaction, "db", MDB_CREATE, &dbi)) != 0) + { + Log(LOG_ERR, + "%s: could not get transaction dbi: %s", + __func__, mdb_strerror(code) + ); + goto end; + + } + + ret = mdb_get(transaction, dbi, &key, &empty) == 0; + mdb_txn_abort(transaction); + mdb_dbi_close(db->environ, dbi); + +end: + LMDBKillKey(key); + pthread_mutex_unlock(&d->lock); + return ret; +} +static bool +LMDBDelete(Db *d, Array *k) +{ + MDB_val key, empty; + LMDB *db = (LMDB *) d; + MDB_txn *transaction; + MDB_dbi dbi; + int code; + bool ret = false; + if (!d || !k) + { + return false; + } + + pthread_mutex_lock(&d->lock); + key = LMDBTranslateKey(k); + + /* create a txn */ + if ((code = mdb_txn_begin(db->environ, NULL, 0, &transaction)) != 0) + { + /* Very bad! */ + Log(LOG_ERR, + "%s: could not begin transaction: %s", + __func__, mdb_strerror(code) + ); + goto end; + } + /* apparently you need to give it a dbi */ + if ((code = mdb_dbi_open(transaction, "db", MDB_CREATE, &dbi)) != 0) + { + Log(LOG_ERR, + "%s: could not get transaction dbi: %s", + __func__, mdb_strerror(code) + ); + goto end; + + } + + ret = mdb_del(transaction, dbi, &key, &empty) == 0; + mdb_txn_commit(transaction); + mdb_dbi_close(db->environ, dbi); + +end: + LMDBKillKey(key); + pthread_mutex_unlock(&d->lock); + return ret; +} static bool LMDBUnlock(Db *d, DbRef *r) @@ -256,13 +453,13 @@ DbOpenLMDB(char *dir, size_t size) DbInit((Db *) db); db->environ = env; - db->base.lockFunc = NULL; + db->base.lockFunc = LMDBLock; db->base.create = LMDBCreate; db->base.unlock = LMDBUnlock; - db->base.delete = NULL; - db->base.exists = NULL; + db->base.delete = LMDBDelete; + db->base.exists = LMDBExists; db->base.close = LMDBClose; - db->base.list = NULL; + db->base.list = NULL; /* TODO: This one is gonna be Fun. */ return (Db *) db; } From 004c53a028a5e20d13ba758abc10a66229600d00 Mon Sep 17 00:00:00 2001 From: LDA Date: Fri, 9 Aug 2024 11:44:16 +0200 Subject: [PATCH 11/18] [ADD/WIP] Listing in LMDB I need to start entering a LMDB test run. --- src/Db/LMDB.c | 134 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 2 deletions(-) diff --git a/src/Db/LMDB.c b/src/Db/LMDB.c index 1e25c30..cee841f 100644 --- a/src/Db/LMDB.c +++ b/src/Db/LMDB.c @@ -86,6 +86,53 @@ LMDBDecode(MDB_val val) return ret; } +static bool +LMDBKeyStartsWith(MDB_val key, MDB_val starts) +{ + size_t i; + if (!key.mv_size || !starts.mv_size) + { + return false; + } + if (key.mv_size < starts.mv_size) + { + return false; + } + + for (i = 0; i < starts.mv_size; i++) + { + char keyC = ((char *) key.mv_data)[i]; + char startC = ((char *) starts.mv_data)[i]; + + if (keyC != startC) + { + return false; + } + } + return true; +} +static char * +LMDBKeyHead(MDB_val key) +{ + char *end; + if (!key.mv_size || !key.mv_data) + { + return NULL; + } + /* -2 because we have a NULL byte in there */ + end = ((char *) key.mv_data) + key.mv_size - 1; + if ((void *) end < key.mv_data) + { + /* Uh oh. */ + return NULL; + } + + while ((void *) (end - 1) >= key.mv_data && *(end - 1)) + { + end--; + } + return end; +} static DbRef * LMDBLock(Db *d, Array *k) @@ -291,7 +338,7 @@ LMDBUnlock(Db *d, DbRef *r) StreamFlush(stream); StreamClose(stream); - mdb_put(ref->transaction, ref->dbi, &key, &val, 0); + ret = mdb_put(ref->transaction, ref->dbi, &key, &val, 0) == 0; mdb_txn_commit(ref->transaction); mdb_dbi_close(db->environ, ref->dbi); @@ -392,6 +439,89 @@ end: return (DbRef *) ret; } +static Array * +LMDBList(Db *d, Array *k) +{ + LMDB *db = (LMDB *) d; + MDB_val key = { 0 }; + MDB_val subKey; + MDB_val val; + Array *ret = NULL; + + MDB_cursor *cursor; + MDB_cursor_op op = MDB_SET_RANGE; + MDB_txn *txn; + MDB_dbi dbi; + int code; + + if (!d || !k) + { + return NULL; + } + + pthread_mutex_lock(&d->lock); + + if ((code = mdb_txn_begin(db->environ, NULL, 0, &txn)) != 0) + { + /* Very bad! */ + Log(LOG_ERR, + "%s: could not begin transaction: %s", + __func__, mdb_strerror(code) + ); + goto end; + } + /* apparently you need to give it a dbi */ + if ((code = mdb_dbi_open(txn, "db", MDB_CREATE, &dbi)) != 0) + { + Log(LOG_ERR, + "%s: could not get transaction dbi: %s", + __func__, mdb_strerror(code) + ); + mdb_txn_abort(txn); + goto end; + } + if ((code = mdb_cursor_open(txn, dbi, &cursor)) != 0) + { + Log(LOG_ERR, + "%s: could not get cursor: %s", + __func__, mdb_strerror(code) + ); + mdb_txn_abort(txn); + mdb_dbi_close(db->environ, dbi); + goto end; + } + + key = LMDBTranslateKey(k); + + /* Small hack to get it to list subitems */ + ((char *) key.mv_data)[0]++; + + ret = ArrayCreate(); + subKey = key; + while (mdb_cursor_get(cursor, &subKey, &val, op) == 0) + { + /* This searches by *increasing* order. The problem is that it may + * extend to unwanted points. Since the values are sorted, we can + * just exit if the subkey is incorrect. */ + char *head = LMDBKeyHead(subKey); + if (!LMDBKeyStartsWith(subKey, key)) + { + break; + } + + StringArrayAppend(ret, head); + op = MDB_NEXT; + } + + mdb_cursor_close(cursor); + mdb_txn_abort(txn); + mdb_dbi_close(db->environ, dbi); + +end: + LMDBKillKey(key); + pthread_mutex_unlock(&d->lock); + return ret; +} /* Implementation functions */ static void @@ -459,7 +589,7 @@ DbOpenLMDB(char *dir, size_t size) db->base.delete = LMDBDelete; db->base.exists = LMDBExists; db->base.close = LMDBClose; - db->base.list = NULL; /* TODO: This one is gonna be Fun. */ + db->base.list = LMDBList; /* TODO: This one is gonna be Fun. */ return (Db *) db; } From a90c66736c79a7486e575c93962c2f38406cbda3 Mon Sep 17 00:00:00 2001 From: LDA Date: Fri, 9 Aug 2024 12:03:09 +0200 Subject: [PATCH 12/18] [MOD/WIP] Disable thread-local storage for LMDB DB locks ought to be enough... --- configure | 2 +- src/Db/LMDB.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/configure b/configure index 0018605..d50d752 100755 --- a/configure +++ b/configure @@ -113,7 +113,7 @@ if [ -n "$TLS_IMPL" ]; then fi if [ -n "$EDB_IMPL" ]; then - CFLAGS="${CFLAGS} -DEDB_IMPL=${EDB_IMPL}" + CFLAGS="${CFLAGS} -D${EDB_IMPL}" LIBS="${LIBS} ${EDB_LIBS}" fi diff --git a/src/Db/LMDB.c b/src/Db/LMDB.c index cee841f..edee68f 100644 --- a/src/Db/LMDB.c +++ b/src/Db/LMDB.c @@ -9,7 +9,7 @@ #include #include -#ifdef EDB_IMPL +#ifdef EDB_LMDB #include @@ -568,7 +568,7 @@ DbOpenLMDB(char *dir, size_t size) return NULL; } mdb_env_set_maxdbs(env, 4); - if ((code = mdb_env_open(env, dir, 0, 0644)) != 0) + if ((code = mdb_env_open(env, dir, MDB_NOTLS, 0644)) != 0) { Log(LOG_ERR, "%s: could not open LMDB env: %s", @@ -589,7 +589,7 @@ DbOpenLMDB(char *dir, size_t size) db->base.delete = LMDBDelete; db->base.exists = LMDBExists; db->base.close = LMDBClose; - db->base.list = LMDBList; /* TODO: This one is gonna be Fun. */ + db->base.list = LMDBList; return (Db *) db; } From f6af2cd78235f148750c69c946a5e0090d846cc2 Mon Sep 17 00:00:00 2001 From: LDA Date: Sat, 10 Aug 2024 09:24:42 +0200 Subject: [PATCH 13/18] [FIX/WIP] Temporary fix to the database system It used to crash, my bad. --- src/Db/Db.c | 13 ++++--------- src/Db/Flat.c | 25 +++++++++++++------------ 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/Db/Db.c b/src/Db/Db.c index 4b9b94a..d5fa449 100644 --- a/src/Db/Db.c +++ b/src/Db/Db.c @@ -434,6 +434,7 @@ DbInit(Db *db) db->mostRecent = NULL; db->leastRecent = NULL; db->cacheSize = 0; + db->maxCache = 0; if (db->maxCache) { @@ -460,15 +461,9 @@ DbRefInit(Db *db, DbRef *ref) ref->ts = UtilTsMillis(); ref->size = 0; - if (db->mostRecent) - { - db->mostRecent->next = ref; - } - if (!db->leastRecent) - { - db->leastRecent = ref; - } - db->mostRecent = ref; + /* TODO: Append the ref to the cache list. + * I removed it because it broke everything and crashed all the time. + * My bad! */ } void StringArrayAppend(Array *arr, char *str) diff --git a/src/Db/Flat.c b/src/Db/Flat.c index 4ecf19c..dbe8189 100644 --- a/src/Db/Flat.c +++ b/src/Db/Flat.c @@ -106,7 +106,7 @@ FlatLock(Db *d, Array *dir) FlatDb *db = (FlatDb *) d; FlatDbRef *ref = NULL; size_t i; - char *path; + char *path = NULL; if (!d || !dir) { return NULL; @@ -126,6 +126,11 @@ FlatLock(Db *d, Array *dir) } stream = StreamFd(fd); + if (!stream) + { + ref = NULL; + goto end; + } lock.l_start = 0; lock.l_len = 0; @@ -140,11 +145,11 @@ FlatLock(Db *d, Array *dir) } ref = Malloc(sizeof(*ref)); - DbRefInit(d, &(ref->base)); - ref->fd = fd; - ref->stream = stream; + DbRefInit(d, (DbRef *) ref); ref->base.ts = UtilLastModified(path); ref->base.json = JsonDecode(stream); + ref->stream = stream; + ref->fd = fd; if (!ref->base.json) { Free(ref); @@ -157,10 +162,7 @@ FlatLock(Db *d, Array *dir) ref->base.name = ArrayCreate(); for (i = 0; i < ArraySize(dir); i++) { - ArrayAdd( - ref->base.name, - StrDuplicate(ArrayGet(dir, i)) - ); + StringArrayAppend(ref->base.name, ArrayGet(dir, i)); } } end: @@ -246,7 +248,6 @@ FlatCreate(Db *d, Array *dir) /* FlatLock() will lock again for us */ pthread_mutex_unlock(&d->lock); - ret = FlatLock(d, dir); return ret; @@ -255,7 +256,7 @@ FlatCreate(Db *d, Array *dir) static bool FlatDelete(Db *d, Array *dir) { - bool ret = true; + bool ret = false; char *file; FlatDb *db = (FlatDb *) d; if (!d || !dir) @@ -273,7 +274,7 @@ FlatDelete(Db *d, Array *dir) } Free(file); - pthread_mutex_lock(&d->lock); + pthread_mutex_unlock(&d->lock); return ret; } @@ -355,7 +356,7 @@ DbOpen(char *dir, size_t cache) return NULL; } db = Malloc(sizeof(*db)); - DbInit(&(db->base)); + DbInit((Db *) db); db->dir = dir; db->base.cacheSize = cache; From 20bb7a20ad5f102863bdcabc19abd0906e7bb919 Mon Sep 17 00:00:00 2001 From: LDA Date: Sat, 10 Aug 2024 10:33:50 +0200 Subject: [PATCH 14/18] [FIX/WIP] Fix mutex issues around LMDB Currently doing a test run on another project of mine to find out how stable it is. Next up(more long-termed): Faster JSON parsing than just plaintext! --- src/Db/LMDB.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Db/LMDB.c b/src/Db/LMDB.c index edee68f..3e35695 100644 --- a/src/Db/LMDB.c +++ b/src/Db/LMDB.c @@ -159,7 +159,6 @@ LMDBLock(Db *d, Array *k) "%s: could not begin transaction: %s", __func__, mdb_strerror(code) ); - pthread_mutex_unlock(&d->lock); goto end; } /* apparently you need to give it a dbi */ @@ -169,9 +168,7 @@ LMDBLock(Db *d, Array *k) "%s: could not get transaction dbi: %s", __func__, mdb_strerror(code) ); - pthread_mutex_unlock(&d->lock); goto end; - } empty_json.mv_size = 0; @@ -180,10 +177,7 @@ LMDBLock(Db *d, Array *k) code = mdb_get(transaction, dbi, &key, &empty_json); if (code == MDB_NOTFOUND) { - Log(LOG_ERR, - "%s: mdb_get failure: %s", - __func__, mdb_strerror(code) - ); + /* No use logging it, as that is just locking behaviour */ mdb_txn_abort(transaction); mdb_dbi_close(db->environ, dbi); goto end; @@ -215,6 +209,10 @@ LMDBLock(Db *d, Array *k) ret->transaction = transaction; ret->dbi = dbi; end: + if (!ret) + { + pthread_mutex_unlock(&d->lock); + } LMDBKillKey(key); return (DbRef *) ret; } @@ -351,7 +349,10 @@ LMDBUnlock(Db *d, DbRef *r) { free(val.mv_data); } - pthread_mutex_unlock(&d->lock); + if (ret) + { + pthread_mutex_unlock(&d->lock); + } return ret; } static DbRef * @@ -379,7 +380,6 @@ LMDBCreate(Db *d, Array *k) "%s: could not begin transaction: %s", __func__, mdb_strerror(code) ); - pthread_mutex_unlock(&d->lock); goto end; } /* apparently you need to give it a dbi */ @@ -389,7 +389,6 @@ LMDBCreate(Db *d, Array *k) "%s: could not get transaction dbi: %s", __func__, mdb_strerror(code) ); - pthread_mutex_unlock(&d->lock); goto end; } @@ -435,6 +434,10 @@ LMDBCreate(Db *d, Array *k) ret->transaction = transaction; ret->dbi = dbi; end: + if (!ret) + { + pthread_mutex_unlock(&d->lock); + } LMDBKillKey(key); return (DbRef *) ret; } From f32cdb7d899aecc9feef20262afdd14d6ab197b7 Mon Sep 17 00:00:00 2001 From: LDA Date: Sat, 10 Aug 2024 23:58:41 +0200 Subject: [PATCH 15/18] [MOD/WIP] Mark listing transactions as readonly May want to sprinkle in "hinting" on the nature of operations done the database, which could allow LMDB to deal with those far more efficiently (for example, a read-only transaction can just be done as soon as the JSON itself is parsed out, as we don't really need the former anymore!) --- src/Db/LMDB.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Db/LMDB.c b/src/Db/LMDB.c index 3e35695..ff56fba 100644 --- a/src/Db/LMDB.c +++ b/src/Db/LMDB.c @@ -464,7 +464,9 @@ LMDBList(Db *d, Array *k) pthread_mutex_lock(&d->lock); - if ((code = mdb_txn_begin(db->environ, NULL, 0, &txn)) != 0) + /* Marked as read-only, as we just don't need to write anything + * when listing */ + if ((code = mdb_txn_begin(db->environ, NULL, MDB_RDONLY, &txn)) != 0) { /* Very bad! */ Log(LOG_ERR, From 3df1e4ab7b40e992639af89505f656591a9fc753 Mon Sep 17 00:00:00 2001 From: LDA Date: Sun, 11 Aug 2024 12:46:49 +0200 Subject: [PATCH 16/18] [ADD/WIP] Start rolling in intents --- include/Cytoplasm/Db.h | 20 ++++++++++++++++ src/Db/Db.c | 25 ++++++++++++++++++- src/Db/Flat.c | 6 +++-- src/Db/Internal.h | 5 ++-- src/Db/LMDB.c | 54 ++++++++++++++++++++++++++++-------------- 5 files changed, 86 insertions(+), 24 deletions(-) diff --git a/include/Cytoplasm/Db.h b/include/Cytoplasm/Db.h index 604ace7..14a1064 100644 --- a/include/Cytoplasm/Db.h +++ b/include/Cytoplasm/Db.h @@ -48,6 +48,17 @@ */ typedef struct Db Db; +/** + * Some "hints" for the database backend for operations. + * Hints are a way for the program to declare what to except of it + * (and the program MUST adhere to these hints, but the backend + * MAY adhere). + */ +typedef enum DbHint { + DB_HINT_READONLY, /* The database reference is treated as read-only */ + DB_HINT_WRITE /* The database reference is treated as read/write */ +} DbHint; + /** * When an object is locked, a reference is returned. This reference * is owned by the current thread, and the database is inaccessible to @@ -118,6 +129,15 @@ extern DbRef * DbCreate(Db *, size_t,...); */ extern DbRef * DbLock(Db *, size_t,...); +/** + * Behaves like + * .Fn DbLock , + * but with hints on the reference itself, as + * .Fn DbLock + * itself is read/write. + */ +extern DbRef * DbLockIntent(Db *, DbHint, size_t,...); + /** * Immediately and permanently remove an object from the database. * This function assumes the object is not locked, otherwise undefined diff --git a/src/Db/Db.c b/src/Db/Db.c index d5fa449..1015bbf 100644 --- a/src/Db/Db.c +++ b/src/Db/Db.c @@ -327,7 +327,30 @@ DbLock(Db * db, size_t nArgs,...) return NULL; } - ret = db->lockFunc(db, args); + ret = db->lockFunc(db, DB_HINT_WRITE, args); + + ArrayFree(args); + + return ret; +} + +DbRef * +DbLockIntent(Db * db, DbHint hint, size_t nArgs,...) +{ + va_list ap; + Array *args; + DbRef *ret; + + va_start(ap, nArgs); + args = ArrayFromVarArgs(nArgs, ap); + va_end(ap); + + if (!args || !db->lockFunc) + { + return NULL; + } + + ret = db->lockFunc(db, hint, args); ArrayFree(args); diff --git a/src/Db/Flat.c b/src/Db/Flat.c index dbe8189..df5685c 100644 --- a/src/Db/Flat.c +++ b/src/Db/Flat.c @@ -101,7 +101,7 @@ DbFileName(FlatDb * db, Array * args) } static DbRef * -FlatLock(Db *d, Array *dir) +FlatLock(Db *d, DbHint hint, Array *dir) { FlatDb *db = (FlatDb *) d; FlatDbRef *ref = NULL; @@ -146,6 +146,8 @@ FlatLock(Db *d, Array *dir) ref = Malloc(sizeof(*ref)); DbRefInit(d, (DbRef *) ref); + /* TODO: Hints */ + ref->base.hint = hint; ref->base.ts = UtilLastModified(path); ref->base.json = JsonDecode(stream); ref->stream = stream; @@ -248,7 +250,7 @@ FlatCreate(Db *d, Array *dir) /* FlatLock() will lock again for us */ pthread_mutex_unlock(&d->lock); - ret = FlatLock(d, dir); + ret = FlatLock(d, DB_HINT_WRITE, dir); return ret; } diff --git a/src/Db/Internal.h b/src/Db/Internal.h index d6907a8..1f1014a 100644 --- a/src/Db/Internal.h +++ b/src/Db/Internal.h @@ -66,7 +66,7 @@ struct Db /* Functions for implementation-specific operations * (opening a ref, closing a db, removing an entry, ...) */ - DbRef * (*lockFunc)(Db *, Array *); + DbRef * (*lockFunc)(Db *, DbHint, Array *); DbRef * (*create)(Db *, Array *); Array * (*list)(Db *, Array *); bool (*unlock)(Db *, DbRef *); @@ -89,8 +89,7 @@ struct DbRef DbRef *prev; DbRef *next; - /* TODO: Functions for implementation-specific operations */ - + DbHint hint; /* Implementation-specific constructs */ }; diff --git a/src/Db/LMDB.c b/src/Db/LMDB.c index 3e35695..270c031 100644 --- a/src/Db/LMDB.c +++ b/src/Db/LMDB.c @@ -135,14 +135,14 @@ LMDBKeyHead(MDB_val key) } static DbRef * -LMDBLock(Db *d, Array *k) +LMDBLock(Db *d, DbHint hint, Array *k) { LMDB *db = (LMDB *) d; MDB_txn *transaction; LMDBRef *ret = NULL; MDB_val key, empty_json; MDB_dbi dbi; - int code; + int code, flags; if (!d || !k) { return NULL; @@ -152,7 +152,8 @@ LMDBLock(Db *d, Array *k) key = LMDBTranslateKey(k); /* create a txn */ - if ((code = mdb_txn_begin(db->environ, NULL, 0, &transaction)) != 0) + flags = hint == DB_HINT_READONLY ? MDB_RDONLY : 0; + if ((code = mdb_txn_begin(db->environ, NULL, flags, &transaction)) != 0) { /* Very bad! */ Log(LOG_ERR, @@ -206,10 +207,21 @@ LMDBLock(Db *d, Array *k) } } ret->base.json = LMDBDecode(empty_json); - ret->transaction = transaction; - ret->dbi = dbi; + ret->base.hint = hint; + ret->transaction = NULL; + + if (hint == DB_HINT_WRITE) + { + ret->transaction = transaction; + ret->dbi = dbi; + } + else + { + mdb_txn_abort(transaction); + mdb_dbi_close(db->environ, dbi); + } end: - if (!ret) + if (!ret || hint == DB_HINT_READONLY) { pthread_mutex_unlock(&d->lock); } @@ -319,7 +331,7 @@ LMDBUnlock(Db *d, DbRef *r) FILE *fakestream; Stream *stream; MDB_val key, val; - bool ret; + bool ret = true; if (!d || !r) { @@ -328,28 +340,31 @@ LMDBUnlock(Db *d, DbRef *r) val.mv_data = NULL; val.mv_size = 0; - key = LMDBTranslateKey(r->name); + if (ref->transaction && r->hint == DB_HINT_WRITE) + { + key = LMDBTranslateKey(r->name); - fakestream = open_memstream((char **) &val.mv_data, &val.mv_size); - stream = StreamFile(fakestream); - JsonEncode(r->json, stream, JSON_DEFAULT); - StreamFlush(stream); - StreamClose(stream); + fakestream = open_memstream((char **) &val.mv_data, &val.mv_size); + stream = StreamFile(fakestream); + JsonEncode(r->json, stream, JSON_DEFAULT); + StreamFlush(stream); + StreamClose(stream); - ret = mdb_put(ref->transaction, ref->dbi, &key, &val, 0) == 0; + ret = mdb_put(ref->transaction, ref->dbi, &key, &val, 0) == 0; - mdb_txn_commit(ref->transaction); - mdb_dbi_close(db->environ, ref->dbi); + mdb_txn_commit(ref->transaction); + mdb_dbi_close(db->environ, ref->dbi); + LMDBKillKey(key); + } StringArrayFree(ref->base.name); JsonFree(ref->base.json); Free(ref); - LMDBKillKey(key); if (val.mv_data) { free(val.mv_data); } - if (ret) + if (ret && r->hint == DB_HINT_WRITE) { pthread_mutex_unlock(&d->lock); } @@ -430,6 +445,7 @@ LMDBCreate(Db *d, Array *k) StringArrayAppend(ret->base.name, ent); } } + ret->base.hint = DB_HINT_WRITE; ret->base.json = HashMapCreate(); ret->transaction = transaction; ret->dbi = dbi; @@ -464,6 +480,8 @@ LMDBList(Db *d, Array *k) pthread_mutex_lock(&d->lock); + /* Marked as read-only, as we just don't need to write anything + * when listing */ if ((code = mdb_txn_begin(db->environ, NULL, 0, &txn)) != 0) { /* Very bad! */ From e133eebef3da9e4682bee70138880014a95960c0 Mon Sep 17 00:00:00 2001 From: LDA Date: Sun, 11 Aug 2024 15:28:21 +0200 Subject: [PATCH 17/18] [MOD/WIP] Use one shared DBI Yeah, I was stupid for not doing that earlier. --- src/Db/LMDB.c | 126 +++++++++++++++++--------------------------------- 1 file changed, 43 insertions(+), 83 deletions(-) diff --git a/src/Db/LMDB.c b/src/Db/LMDB.c index 270c031..044c439 100644 --- a/src/Db/LMDB.c +++ b/src/Db/LMDB.c @@ -17,6 +17,7 @@ typedef struct LMDB { Db base; /* The base implementation required to pass */ MDB_env *environ; + MDB_dbi dbi; } LMDB; typedef struct LMDBRef { DbRef base; @@ -140,8 +141,7 @@ LMDBLock(Db *d, DbHint hint, Array *k) LMDB *db = (LMDB *) d; MDB_txn *transaction; LMDBRef *ret = NULL; - MDB_val key, empty_json; - MDB_dbi dbi; + MDB_val key, json_val; int code, flags; if (!d || !k) { @@ -151,7 +151,9 @@ LMDBLock(Db *d, DbHint hint, Array *k) pthread_mutex_lock(&d->lock); key = LMDBTranslateKey(k); - /* create a txn */ + /* Create a transaction, honoring hints. */ + /* TODO: Do we want to create a "main" transaction that everyone inherits + * from? */ flags = hint == DB_HINT_READONLY ? MDB_RDONLY : 0; if ((code = mdb_txn_begin(db->environ, NULL, flags, &transaction)) != 0) { @@ -162,25 +164,13 @@ LMDBLock(Db *d, DbHint hint, Array *k) ); goto end; } - /* apparently you need to give it a dbi */ - if ((code = mdb_dbi_open(transaction, "db", MDB_CREATE, &dbi)) != 0) - { - Log(LOG_ERR, - "%s: could not get transaction dbi: %s", - __func__, mdb_strerror(code) - ); - goto end; - } - empty_json.mv_size = 0; - empty_json.mv_data = NULL; - /* get data from it */ - code = mdb_get(transaction, dbi, &key, &empty_json); + json_val.mv_size = 0; + json_val.mv_data = NULL; + code = mdb_get(transaction, db->dbi, &key, &json_val); if (code == MDB_NOTFOUND) { - /* No use logging it, as that is just locking behaviour */ mdb_txn_abort(transaction); - mdb_dbi_close(db->environ, dbi); goto end; } else if (code != 0) @@ -190,7 +180,6 @@ LMDBLock(Db *d, DbHint hint, Array *k) __func__, mdb_strerror(code) ); mdb_txn_abort(transaction); - mdb_dbi_close(db->environ, dbi); goto end; } @@ -206,19 +195,17 @@ LMDBLock(Db *d, DbHint hint, Array *k) StringArrayAppend(ret->base.name, ent); } } - ret->base.json = LMDBDecode(empty_json); + ret->base.json = LMDBDecode(json_val); ret->base.hint = hint; ret->transaction = NULL; if (hint == DB_HINT_WRITE) { ret->transaction = transaction; - ret->dbi = dbi; } else { mdb_txn_abort(transaction); - mdb_dbi_close(db->environ, dbi); } end: if (!ret || hint == DB_HINT_READONLY) @@ -234,7 +221,6 @@ LMDBExists(Db *d, Array *k) MDB_val key, empty; LMDB *db = (LMDB *) d; MDB_txn *transaction; - MDB_dbi dbi; int code; bool ret = false; if (!d || !k) @@ -255,21 +241,9 @@ LMDBExists(Db *d, Array *k) ); goto end; } - /* apparently you need to give it a dbi */ - if ((code = mdb_dbi_open(transaction, "db", MDB_CREATE, &dbi)) != 0) - { - Log(LOG_ERR, - "%s: could not get transaction dbi: %s", - __func__, mdb_strerror(code) - ); - goto end; - } - - ret = mdb_get(transaction, dbi, &key, &empty) == 0; + ret = mdb_get(transaction, db->dbi, &key, &empty) == 0; mdb_txn_abort(transaction); - mdb_dbi_close(db->environ, dbi); - end: LMDBKillKey(key); pthread_mutex_unlock(&d->lock); @@ -281,7 +255,6 @@ LMDBDelete(Db *d, Array *k) MDB_val key, empty; LMDB *db = (LMDB *) d; MDB_txn *transaction; - MDB_dbi dbi; int code; bool ret = false; if (!d || !k) @@ -302,21 +275,9 @@ LMDBDelete(Db *d, Array *k) ); goto end; } - /* apparently you need to give it a dbi */ - if ((code = mdb_dbi_open(transaction, "db", MDB_CREATE, &dbi)) != 0) - { - Log(LOG_ERR, - "%s: could not get transaction dbi: %s", - __func__, mdb_strerror(code) - ); - goto end; - } - - ret = mdb_del(transaction, dbi, &key, &empty) == 0; + ret = mdb_del(transaction, db->dbi, &key, &empty) == 0; mdb_txn_commit(transaction); - mdb_dbi_close(db->environ, dbi); - end: LMDBKillKey(key); pthread_mutex_unlock(&d->lock); @@ -350,10 +311,9 @@ LMDBUnlock(Db *d, DbRef *r) StreamFlush(stream); StreamClose(stream); - ret = mdb_put(ref->transaction, ref->dbi, &key, &val, 0) == 0; + ret = mdb_put(ref->transaction, db->dbi, &key, &val, 0) == 0; mdb_txn_commit(ref->transaction); - mdb_dbi_close(db->environ, ref->dbi); LMDBKillKey(key); } StringArrayFree(ref->base.name); @@ -377,7 +337,6 @@ LMDBCreate(Db *d, Array *k) MDB_txn *transaction; LMDBRef *ret = NULL; MDB_val key, empty_json; - MDB_dbi dbi; int code; if (!d || !k) { @@ -397,38 +356,25 @@ LMDBCreate(Db *d, Array *k) ); goto end; } - /* apparently you need to give it a dbi */ - if ((code = mdb_dbi_open(transaction, "db", MDB_CREATE, &dbi)) != 0) - { - Log(LOG_ERR, - "%s: could not get transaction dbi: %s", - __func__, mdb_strerror(code) - ); - goto end; - - } empty_json.mv_size = 2; empty_json.mv_data = "{}"; /* put data in it */ - code = mdb_put(transaction, dbi, &key, &empty_json, MDB_NOOVERWRITE); + code = mdb_put(transaction, db->dbi, &key, &empty_json, MDB_NOOVERWRITE); if (code == MDB_KEYEXIST) { - mdb_dbi_close(db->environ, dbi); mdb_txn_abort(transaction); goto end; } else if (code == MDB_MAP_FULL) { Log(LOG_ERR, "%s: db is full", __func__); - mdb_dbi_close(db->environ, dbi); mdb_txn_abort(transaction); goto end; } else if (code != 0) { Log(LOG_ERR, "%s: mdb_put failure: %s", __func__, mdb_strerror(code)); - mdb_dbi_close(db->environ, dbi); mdb_txn_abort(transaction); goto end; } @@ -448,7 +394,6 @@ LMDBCreate(Db *d, Array *k) ret->base.hint = DB_HINT_WRITE; ret->base.json = HashMapCreate(); ret->transaction = transaction; - ret->dbi = dbi; end: if (!ret) { @@ -470,7 +415,6 @@ LMDBList(Db *d, Array *k) MDB_cursor *cursor; MDB_cursor_op op = MDB_SET_RANGE; MDB_txn *txn; - MDB_dbi dbi; int code; if (!d || !k) @@ -482,7 +426,7 @@ LMDBList(Db *d, Array *k) /* Marked as read-only, as we just don't need to write anything * when listing */ - if ((code = mdb_txn_begin(db->environ, NULL, 0, &txn)) != 0) + if ((code = mdb_txn_begin(db->environ, NULL, MDB_RDONLY, &txn)) != 0) { /* Very bad! */ Log(LOG_ERR, @@ -491,24 +435,13 @@ LMDBList(Db *d, Array *k) ); goto end; } - /* apparently you need to give it a dbi */ - if ((code = mdb_dbi_open(txn, "db", MDB_CREATE, &dbi)) != 0) - { - Log(LOG_ERR, - "%s: could not get transaction dbi: %s", - __func__, mdb_strerror(code) - ); - mdb_txn_abort(txn); - goto end; - } - if ((code = mdb_cursor_open(txn, dbi, &cursor)) != 0) + if ((code = mdb_cursor_open(txn, db->dbi, &cursor)) != 0) { Log(LOG_ERR, "%s: could not get cursor: %s", __func__, mdb_strerror(code) ); mdb_txn_abort(txn); - mdb_dbi_close(db->environ, dbi); goto end; } @@ -536,7 +469,6 @@ LMDBList(Db *d, Array *k) mdb_cursor_close(cursor); mdb_txn_abort(txn); - mdb_dbi_close(db->environ, dbi); end: LMDBKillKey(key); @@ -554,6 +486,7 @@ LMDBClose(Db *d) return; } + mdb_dbi_close(db->environ, db->dbi); mdb_env_close(db->environ); } @@ -562,6 +495,8 @@ DbOpenLMDB(char *dir, size_t size) { /* TODO */ MDB_env *env = NULL; + MDB_txn *txn; + MDB_dbi dbi; int code; LMDB *db; if (!dir || !size) @@ -599,10 +534,35 @@ DbOpenLMDB(char *dir, size_t size) return NULL; } + /* Initialise a DBI */ + { + if ((code = mdb_txn_begin(env, NULL, 0, &txn)) != 0) + { + Log(LOG_ERR, + "%s: could not begin transaction: %s", + __func__, mdb_strerror(code) + ); + mdb_env_close(env); + return NULL; + } + if ((code = mdb_dbi_open(txn, "db", MDB_CREATE, &dbi)) != 0) + { + Log(LOG_ERR, + "%s: could not get transaction dbi: %s", + __func__, mdb_strerror(code) + ); + mdb_txn_abort(txn); + mdb_env_close(env); + return NULL; + } + mdb_txn_commit(txn); + } + db = Malloc(sizeof(*db)); DbInit((Db *) db); db->environ = env; + db->dbi = dbi; db->base.lockFunc = LMDBLock; db->base.create = LMDBCreate; From 1d0eb9d49a409448dce24cc091ee49b577eb52f3 Mon Sep 17 00:00:00 2001 From: "LevitatingBusinessMan (Rein Fernhout)" Date: Thu, 22 Aug 2024 03:49:43 +0200 Subject: [PATCH 18/18] remove use of install in Makefile not posix --- configure | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 1afdd91..e5b9284 100755 --- a/configure +++ b/configure @@ -256,8 +256,9 @@ ${TAB}done ${LIB_NAME}: ${OUT}/lib/lib${LIB_NAME}.a ${OUT}/lib/lib${LIB_NAME}.so install: ${LIB_NAME} -${TAB}install -D ${OUT}/lib/lib${LIB_NAME}.a \$(PREFIX)/lib/lib${LIB_NAME}.a -${TAB}install -D ${OUT}/lib/lib${LIB_NAME}.so \$(PREFIX)/lib/lib${LIB_NAME}.so +${TAB}mkdir -p ${OUT}/lib +${TAB}cp ${OUT}/lib/lib${LIB_NAME}.a \$(PREFIX)/lib/lib${LIB_NAME}.a +${TAB}cp ${OUT}/lib/lib${LIB_NAME}.so \$(PREFIX)/lib/lib${LIB_NAME}.so $(collect ${INCLUDE}/ '' '' \$\(PREFIX\)/include/${LIB_NAME}/ install_out) $(collect ${INCLUDE}/ .h .3 \$\(PREFIX\)/man/man3/${LIB_NAME}- install_man) $(collect ${TOOLS}/ '.c' '' \$\(PREFIX\)/bin/ install_tool)