Import new Cytoplasm library based off of code from Telodendria.

Telodendria doesn't use this library yet, but it will soon.
This commit is contained in:
Jordan Bancino 2023-05-13 17:30:09 +00:00
commit 40eac30b5c
60 changed files with 15048 additions and 0 deletions

3
.cvsignore Normal file
View file

@ -0,0 +1,3 @@
build
out
*-leaked.txt

28
.indent.pro vendored Normal file
View file

@ -0,0 +1,28 @@
-bad
-bap
-bbb
-nbc
-bl
-c36
-cd36
-ncdb
-nce
-ci8
-cli1
-d0
-di1
-ndj
-ei
-fc1
-i4
-ip
-l72
-lc72
-lp
-npcs
-psl
-sc
-nsob
-nut
-nv

24
LICENSE.txt Normal file
View file

@ -0,0 +1,24 @@
/*
* Cytoplasm (libcytoplasm)
* 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.
*/

158
README.txt Normal file
View file

@ -0,0 +1,158 @@
Cytoplasm (libcytoplasm)
========================================================================
Cytoplasm is a general-purpose C library and runtime stub for
creating high-level (particularly networked and multi-threaded) C
applications. It allows applications to take advantage of the speed,
flexibility, and simplicity of the C programming language, while
providing helpful code to perform various complex tasks. Cytoplasm
provides high-level data structures, a basic logging facility, an
HTTP client and server, and more.
Cytoplasm aims not to only do one thing well, but to do many things
good enough. The primary target of Cytoplasm is simple, yet higher
level C applications that have to perform relatively complex tasks,
but don't want to pull in a large number of dependencies.
Cytoplasm is extremely opinionated on the way programs using it are
written. It strives to create a comprehensive and tightly-integrated
programming environment, while also maintaining C programming
correctness. It doesn't do any macro magic or make C look like
anything other than C. It is written entirely in C89, and depends
only on a POSIX environment. This differentiates it from other
general-purpose libraries that often require modern compilers and
non-standard language and environment features. Cytoplasm is intended
to be extremely portable and simple, while still providing some of
the functionality expected in higher-level programming languages
in a platform-agnostic manner. In the case of TLS, Cytoplasm wraps
low-level TLS libraries to offer a single, unified interface to TLS
so that programs do not have to care about the underlying
implementation.
Cytoplasm is probably not suitable for embedded programming. It makes
liberal use of the heap, and while data structures are designed to
conserve memory where possible and practical, minimal memory usage
is not really a design goal for Cytoplasm, although Cytoplasm takes
care not to use any more memory than it absolutely needs. Cytoplasm
also wraps a few standard libraries with additional logic and
checking. While this ensures better runtime safetly, this inevitably
adds a little overhead.
Originally a part of Telodendria (https://telodendria.io), a Matrix
homeserver written in C, Cytoplasm was split off into its own project
due to the desire of some Telodendria developers to use Telodendria's
code in other projects. Cytoplasm is still a Telodendria project,
and is maintained along side of Telodendria itself, even living in
the same CVS module, but it is designed specifically to be distributed
and used totally independent of Telodendria.
The name "Cytoplasm" was chosen for a few reasons. It plays off the
precedent set up by the Matrix organization in naming projects after
the parts of a neuron. It also speaks to the function of Cytoplasm.
The cytoplasm of a cell is the supporting material. It is what gives
the cell its shape, and it facilitates the movement of materials
to the other cell parts. Likewise, Cytoplasm aims to provide a
support mechanism for C applications that have to perform complex
tasks.
Cytoplasm also starts with a C, which I think is a nice touch for C
libraries. It's also fun to say and unique enough that searching for
"libcytoplasm" should bring you to this project and not some other
one.
Building
------------------------------------------------------------------------
If your operating system or software distribution provides a pre-built
package of Cytoplasm, you should prefer to use that instead of
building it from source.
Cytoplasm aims to have zero dependencies beyond what is mandated
by POSIX. You only need the standard math and pthread libraries to
build it. TLS support can optionally be enabled by setting the
TLS_IMPL environment variable. The supported TLS implementations
are as follows:
* OpenSSL
* LibreSSL
If TLS support is not enabled, all APIs that use it should fall
back to non-TLS behavior in a sensible manner. For example, if TLS
support is not enabled, then the HTTP client API will simply return
an error if a TLS connection is requested.
Cytoplasm uses a custom build script instead of Make, for the sake
of portability. To build everything, just run the script:
$ sh make.sh
This will produce the following out/ directory:
out/
lib/
libcytoplasm.so - The Cytoplasm shared library.
libcytoplasm.a - The Cytoplasm static library.
cytoplasm.o - The Cytoplasm runtime stub.
bin/
hdoc - The documentation generator tool.
man/ - All Cytoplasm API documentation.
Usage
------------------------------------------------------------------------
Cytoplasm provides the typical .so and .a files, which can be used
to link programs with it in the usual way. However, Cytoplasm also
provides a minimal runtime environment that is intended to be used
with the library. As such, it also provides a runtime stub, which
is intended to be linked in with programs using Cytoplasm. This
stub is responsible for setting up and tearing down some Cytoplasm
APIs. While it isn't required by any means, it makes Cytoplasm a
lot easier to use for programmers by abstracting out all of the
common logic that most programs will want to use.
Here is the canonical Hello World written with Cytoplasm:
#include <Log.h>
int Main(void)
{
Log(LOG_INFO, "Hello World!");
return 0;
}
If this file is Hello.c, then you can compile it by doing something
like this:
$ cc -o hello Hello.c cytoplasm.o -lcytoplasm
This command assumes that the runtime stub resides in the current
working directory, and that libcytoplasm.so is in your library path.
If you're using the version of Cytoplasm installed by your operating
system or software distribution, consult the documentation for the
location of the runtime stub. It may be located in
/usr/local/libexec or some other similar location. If you've built
Cytoplasm from source and wish to link to it from there, you may wish
to do something like this:
$ export CYTOPLASM=/path/to/Cytoplasm/out/lib
$ cc -o hello Hello.c "${CYTOPLASM}/cytoplasm.o" \
"-L${CYTOPLASM}" -lcytoplasm
As you may have noticed, C programs using Cytoplasm's runtime stub
don't write the main() function. Instead, they use Main(). The main()
function is provided by the runtime stub. The full form of Main()
expected by the stub is as follows:
int Main(Array *args, HashMap *env);
The first argument is a Cytoplasm array of the command line
arguments, and the second is a Cytoplasm hash map of environment
variables. Most linkers will let programs omit the env argument,
or both arguments if you don't need either. The return value of
Main() is returned to the operating system.
Note that both arguments to Main may be treated like any other
array or hash map. However, do not invoke ArrayFree() or HashMapFree()
on the passed pointers, because memory is cleaned up after Main()
returns.

301
make.sh Executable file
View file

@ -0,0 +1,301 @@
#!/usr/bin/env sh
addprefix() {
prefix="$1"
shift
for thing in "$@"; do
echo "${prefix}${thing}"
done
unset prefix
unset thing
}
: "${NAME:=Cytoplasm}"
: "${LIB_NAME:=$(echo ${NAME} | tr '[A-Z]' '[a-z]')}"
: "${VERSION:=0.3.0}"
: "${CVS_TAG:=${NAME}-$(echo ${VERSION} | sed 's/\./_/g')}"
: "${SRC:=src}"
: "${TOOLS:=tools}"
: "${BUILD:=build}"
: "${OUT:=out}"
: "${STUB:=RtStub}"
: "${LICENSE:=LICENSE.txt}"
: "${CC:=cc}"
: "${AR:=ar}"
: "${DEFINES:=-D_DEFAULT_SOURCE -DLIB_NAME=\"${NAME}\" -DLIB_VERSION=\"${VERSION}\"}"
: "${INCLUDE:=${SRC}/include}"
: "${CFLAGS:=-Wall -Wextra -pedantic -std=c89 -O3 -pipe}"
: "${LD_EXTRA:=-flto -fdata-sections -ffunction-sections -s -Wl,-gc-sections}"
: "${LDFLAGS:=-lm -pthread}"
if [ -n "${TLS_IMPL}" ]; then
case "${TLS_IMPL}" in
"LIBRESSL")
TLS_LIBS="-ltls -lcrypto -lssl"
;;
"OPENSSL")
TLS_LIBS="-lcrypto -lssl"
;;
*)
echo "Unrecognized TLS implementation: ${TLS_IMPL}"
echo "Consult Tls.h for supported implementations."
echo "Note that the TLS_ prefix is omitted in TLS_IMPL."
exit 1
;;
esac
DEFINES="${DEFINES} -DTLS_IMPL=TLS_${TLS_IMPL}"
LDFLAGS="${LDFLAGS} ${TLS_LIBS}"
fi
CFLAGS="${CFLAGS} ${DEFINES} $(addprefix -I$(pwd)/ ${INCLUDE})"
LDFLAGS="${LDFLAGS} ${LD_EXTRA}"
# Check the modificiation time of a file. This is used to do
# incremental builds; we only want to rebuild files that have
# have changed.
mod_time() {
if [ -n "$1" ] && [ -f "$1" ]; then
case "$(uname)" in
Linux | CYGWIN_NT* | Haiku)
stat -c %Y "$1"
;;
*BSD | DragonFly | Minix)
stat -f %m "$1"
;;
*)
# Platform unknown, force rebuilding the whole
# project every time.
echo "0"
;;
esac
else
echo "0"
fi
}
# Substitute shell variables in a stream with their actual value
# in this shell.
setsubst() {
SED="/tmp/sed-$RANDOM.txt"
(
set | while IFS='=' read -r var val; do
val=$(echo "$val" | cut -d "'" -f 2- | rev | cut -d "'" -f 2- | rev)
echo "s|\\\${$var}|$val|g"
done
echo "s|\\\${[a-zA-Z_]*}||g"
echo "s|'''|'|g"
) >"$SED"
sed -f "$SED" $@
rm "$SED"
}
recipe_docs() {
export LD_LIBRARY_PATH="${OUT}/lib"
mkdir -p "${OUT}/man/man3"
for header in $(find ${INCLUDE} -name '*.h'); do
basename=$(basename "$header")
man=$(echo "${OUT}/man/man3/$basename" | sed -e 's/\.h$/\.3/')
if [ $(mod_time "$header") -ge $(mod_time "$man") ]; then
echo "DOC $basename"
if ! "${OUT}/bin/hdoc" -D "Os=${NAME}" -i "$header" -o "$man"; then
rm "$man"
exit 1
fi
fi
done
if which makewhatis 2>&1 > /dev/null; then
makewhatis "${OUT}/man"
fi
}
recipe_build() {
mkdir -p "${BUILD}" ${OUT}/{bin,lib}
cd "${SRC}"
echo "CC = ${CC}"
echo "CFLAGS = ${CFLAGS}"
echo "LDFLAGS = ${LDFLAGS}"
echo
do_rebuild=0
objs=""
for src in $(find . -name '*.c' | cut -d '/' -f 2-); do
obj=$(echo "${BUILD}/$src" | sed -e 's/\.c$/\.o/')
if [ $(basename "$obj" .o) != "${STUB}" ]; then
objs="$objs $obj"
fi
if [ $(mod_time "$src") -ge $(mod_time "../$obj") ]; then
echo "CC $(basename $obj)"
obj_dir=$(dirname "../$obj")
mkdir -p "$obj_dir"
if ! $CC $CFLAGS -fPIC -c -o "../$obj" "$src"; then
exit 1
fi
do_rebuild=1
fi
done
cd ..
if [ $do_rebuild -eq 1 ] || [ ! -f "${OUT}/lib/lib${LIB_NAME}.a" ]; then
echo "AR lib${LIB_NAME}.a"
if ! $AR rcs "${OUT}/lib/lib${LIB_NAME}.a" $objs; then
exit 1
fi
fi
if [ $do_rebuild -eq 1 ] || [ ! -f "${OUT}/lib/lib${LIB_NAME}.so" ]; then
echo "LD lib${LIB_NAME}.so"
if ! $CC -shared -o "${OUT}/lib/lib${LIB_NAME}.so" $objs ${LDFLAGS}; then
exit 1
fi
fi
cp "${BUILD}/${STUB}.o" "${OUT}/lib/${LIB_NAME}.o"
for src in $(find "${TOOLS}" -name '*.c'); do
out=$(basename "$src" .c)
out="${OUT}/bin/$out"
if [ $(mod_time "$src") -ge $(mod_time "$out") ] || [ $do_rebuild -eq 1 ]; then
echo "CC $(basename $out)"
mkdir -p "$(dirname $out)"
if ! $CC $CFLAGS -o "$out" "$src" "${OUT}/lib/${LIB_NAME}.o" "-L${OUT}/lib" "-l${LIB_NAME}" ${LDFLAGS}; then
exit 1
fi
fi
done
recipe_docs
}
recipe_clean() {
rm -r "${BUILD}" "${OUT}"
}
# Update copyright comments in sources and header files.
recipe_license() {
find . -name '*.[ch]' | while IFS= read -r src; do
if [ -t 1 ]; then
printf "LICENSE %s%*c\r" $(basename "$src") "16" " "
fi
srcHeader=$(grep -n -m 1 '^ \*/' "$src" | cut -d ':' -f 1)
head "-n$srcHeader" "$src" |
diff -u -p - "${LICENSE}" |
patch "$src" | grep -v "^Hmm"
done
if [ -t 1 ]; then
printf "%*c\n" "50" " "
fi
}
# Format source code files by running indent(1) on them.
recipe_format() {
find . -name '*.c' | while IFS= read -r src; do
if [ -t 1 ]; then
printf "FMT %s%*c\r" $(basename "$src") "16" " "
fi
if indent "$src"; then
rm $(basename "$src").BAK
fi
done
if [ -t 1 ]; then
printf "%*c\n" "50" " "
fi
}
# Generate a release tarball, checksum and sign it, and push it to
# the web root.
recipe_release() {
# Tag the release at this point in time.
cvs tag "$CVS_TAG"
mkdir -p "${OUT}/release"
cd "${OUT}/release"
# Generate the release tarball.
cvs export "-r$CVS_TAG" "${NAME}"
mv "${NAME}" "${NAME}-v${VERSION}"
tar -czf "${NAME}-v${VERSION}.tar.gz" "${NAME}-v${VERSION}"
rm -r "${NAME}-v${VERSION}"
# Checksum the release tarball.
sha256 "${NAME}-v${VERSION}.tar.gz" > "${NAME}-v${VERSION}.tar.gz.sha256"
# Sign the release tarball.
if [ ! -z "${SIGNIFY_SECRET}" ]; then
signify -S -s "${SIGNIFY_SECRET}" \
-m "${NAME}-v${VERSION}.tar.gz" \
-x "${NAME}-v${VERSION}.tar.gz.sig"
else
echo "Warning: SIGNIFY_SECRET not net."
echo "The built tarball will not be signed."
fi
}
recipe_patch() {
# If the user has not set their MXID, try to deduce one from
# their system.
if [ -z "$MXID" ]; then
MXID="@${USER}:$(hostname)"
fi
# If the user has not set their EDITOR, use a safe default.
# (vi should be available on any POSIX system)
if [ -z "$EDITOR" ]; then
EDITOR=vi
fi
NORMALIZED_MXID=$(echo "$MXID" | sed -e 's/@//g' -e 's/:/-/g')
PATCH_FILE="${NORMALIZED_MXID}_$(date +%s).patch"
# Generate the actual patch file
# Here we write some nice headers, and then do a cvs diff.
(
printf 'From: "%s" <%s>\n' "$DISPLAY_NAME" "$MXID"
echo "Date: $(date)"
echo "Subject: "
echo
cvs -q diff -uNp $PATCHSET | grep -v '^\? '
) >"$PATCH_FILE"
"$EDITOR" "$PATCH_FILE"
echo "$PATCH_FILE"
}
recipe_diff() {
tmp_patch="/tmp/${NAME}-$(date +%s).patch"
cvs -q diff -uNp $PATCHSET >"$tmp_patch"
if [ -z "$PAGER" ]; then
PAGER="less -F"
fi
$PAGER "$tmp_patch"
rm "$tmp_patch"
}
# Execute the user-specified recipes.
for recipe in $@; do
recipe_$recipe
done
# If no recipe was provided, run a build.
if [ -z "$1" ]; then
recipe_build
fi

118
src/Args.c Normal file
View file

@ -0,0 +1,118 @@
/*
* 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 <Args.h>
#include <Memory.h>
#include <Log.h>
#include <Str.h>
#include <ctype.h>
#include <string.h>
void
ArgParseStateInit(ArgParseState * state)
{
state->optPos = 1;
state->optErr = 1;
state->optInd = 1;
state->optOpt = 0;
state->optArg = NULL;
}
int
ArgParse(ArgParseState * state, Array * args, const char *optStr)
{
const char *arg;
arg = ArrayGet(args, state->optInd);
if (arg && StrEquals(arg, "--"))
{
state->optInd++;
return -1;
}
else if (!arg || arg[0] != '-' || !isalnum((unsigned char) arg[1]))
{
return -1;
}
else
{
const char *opt = strchr(optStr, arg[state->optPos]);
state->optOpt = arg[state->optPos];
if (!opt)
{
if (state->optErr && *optStr != ':')
{
Log(LOG_ERR, "Illegal option: %c", ArrayGet(args, 0), state->optOpt);
}
if (!arg[++state->optPos])
{
state->optInd++;
state->optPos = 1;
}
return '?';
}
else if (opt[1] == ':')
{
if (arg[state->optPos + 1])
{
state->optArg = (char *) arg + state->optPos + 1;
state->optInd++;
state->optPos = 1;
return state->optOpt;
}
else if (ArrayGet(args, state->optInd + 1))
{
state->optArg = (char *) ArrayGet(args, state->optInd + 1);
state->optInd += 2;
state->optPos = 1;
return state->optOpt;
}
else
{
if (state->optErr && *optStr != ':')
{
Log(LOG_ERR, "Option requires an argument: %c", state->optOpt);
}
if (!arg[++state->optPos])
{
state->optInd++;
state->optPos = 1;
}
return *optStr == ':' ? ':' : '?';
}
}
else
{
if (!arg[++state->optPos])
{
state->optInd++;
state->optPos = 1;
}
return state->optOpt;
}
}
}

339
src/Array.c Normal file
View file

@ -0,0 +1,339 @@
/*
* 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 <Array.h>
#ifndef ARRAY_BLOCK
#define ARRAY_BLOCK 16
#endif
#include <stddef.h>
#include <Memory.h>
struct Array
{
void **entries; /* An array of void pointers, to
* store any data */
size_t allocated; /* Elements allocated on the heap */
size_t size; /* Elements actually filled */
};
int
ArrayAdd(Array * array, void *value)
{
if (!array)
{
return 0;
}
return ArrayInsert(array, array->size, value);
}
Array *
ArrayCreate(void)
{
Array *array = Malloc(sizeof(Array));
if (!array)
{
return NULL;
}
array->size = 0;
array->allocated = ARRAY_BLOCK;
array->entries = Malloc(sizeof(void *) * ARRAY_BLOCK);
if (!array->entries)
{
Free(array);
return NULL;
}
return array;
}
void *
ArrayDelete(Array * array, size_t index)
{
size_t i;
void *element;
if (!array || array->size <= index)
{
return NULL;
}
element = array->entries[index];
for (i = index; i < array->size - 1; i++)
{
array->entries[i] = array->entries[i + 1];
}
array->size--;
return element;
}
void
ArrayFree(Array * array)
{
if (array)
{
Free(array->entries);
Free(array);
}
}
void *
ArrayGet(Array * array, size_t index)
{
if (!array)
{
return NULL;
}
if (index >= array->size)
{
return NULL;
}
return array->entries[index];
}
extern int
ArrayInsert(Array * array, size_t index, void *value)
{
size_t i;
if (!array || !value || index > array->size)
{
return 0;
}
if (array->size >= array->allocated)
{
void **tmp;
size_t newSize = array->allocated + ARRAY_BLOCK;
tmp = array->entries;
array->entries = Realloc(array->entries,
sizeof(void *) * newSize);
if (!array->entries)
{
array->entries = tmp;
return 0;
}
array->allocated = newSize;
}
for (i = array->size; i > index; i--)
{
array->entries[i] = array->entries[i - 1];
}
array->size++;
array->entries[index] = value;
return 1;
}
extern void *
ArraySet(Array * array, size_t index, void *value)
{
void *oldValue;
if (!value)
{
return ArrayDelete(array, index);
}
if (!array)
{
return NULL;
}
if (index >= array->size)
{
return NULL;
}
oldValue = array->entries[index];
array->entries[index] = value;
return oldValue;
}
size_t
ArraySize(Array * array)
{
if (!array)
{
return 0;
}
return array->size;
}
int
ArrayTrim(Array * array)
{
void **tmp;
if (!array)
{
return 0;
}
tmp = array->entries;
array->entries = Realloc(array->entries,
sizeof(void *) * array->size);
if (!array->entries)
{
array->entries = tmp;
return 0;
}
return 1;
}
static void
ArraySwap(Array * array, size_t i, size_t j)
{
void *p = array->entries[i];
array->entries[i] = array->entries[j];
array->entries[j] = p;
}
static size_t
ArrayPartition(Array * array, size_t low, size_t high, int (*compare) (void *, void *))
{
void *pivot = array->entries[high];
size_t i = low - 1;
size_t j;
for (j = low; j <= high - 1; j++)
{
if (compare(array->entries[j], pivot) < 0)
{
i++;
ArraySwap(array, i, j);
}
}
ArraySwap(array, i + 1, high);
return i + 1;
}
static void
ArrayQuickSort(Array * array, size_t low, size_t high, int (*compare) (void *, void *))
{
if (low < high)
{
size_t pi = ArrayPartition(array, low, high, compare);
ArrayQuickSort(array, low, pi - 1, compare);
ArrayQuickSort(array, pi + 1, high, compare);
}
}
void
ArraySort(Array * array, int (*compare) (void *, void *))
{
if (!array)
{
return;
}
ArrayQuickSort(array, 0, array->size, compare);
}
/* Even though the following operations could be done using only the
* public Array API defined above, I opted for low-level struct
* manipulation because it allows much more efficient copying; we only
* allocate what we for sure need upfront, and don't have to
* re-allocate during the operation. */
Array *
ArrayFromVarArgs(size_t n, va_list ap)
{
size_t i;
Array *arr = Malloc(sizeof(Array));
if (!arr)
{
return NULL;
}
arr->size = n;
arr->allocated = n;
arr->entries = Malloc(sizeof(void *) * arr->allocated);
if (!arr->entries)
{
Free(arr);
return NULL;
}
for (i = 0; i < n; i++)
{
arr->entries[i] = va_arg(ap, void *);
}
return arr;
}
Array *
ArrayDuplicate(Array * arr)
{
size_t i;
Array *arr2 = Malloc(sizeof(Array));
if (!arr2)
{
return NULL;
}
arr2->size = arr->size;
arr2->allocated = arr->size;
arr2->entries = Malloc(sizeof(void *) * arr->allocated);
if (!arr2->entries)
{
Free(arr2);
return NULL;
}
for (i = 0; i < arr2->size; i++)
{
arr2->entries[i] = arr->entries[i];
}
return arr2;
}

244
src/Base64.c Normal file
View file

@ -0,0 +1,244 @@
/*
* 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 <Base64.h>
#include <Memory.h>
static const char Base64EncodeMap[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const int Base64DecodeMap[] = {
62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58,
59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51
};
size_t
Base64EncodedSize(size_t inputSize)
{
size_t size = inputSize;
if (inputSize % 3)
{
size += 3 - (inputSize % 3);
}
size /= 3;
size *= 4;
return size;
}
size_t
Base64DecodedSize(const char *base64, size_t len)
{
size_t ret;
size_t i;
if (!base64)
{
return 0;
}
ret = len / 4 * 3;
for (i = len; i > 0; i--)
{
if (base64[i] == '=')
{
ret--;
}
else
{
break;
}
}
return ret;
}
char *
Base64Encode(const char *input, size_t len)
{
char *out;
size_t outLen;
size_t i, j, v;
if (!input || !len)
{
return NULL;
}
outLen = Base64EncodedSize(len);
out = Malloc(outLen + 1);
if (!out)
{
return NULL;
}
out[outLen] = '\0';
for (i = 0, j = 0; i < len; i += 3, j += 4)
{
v = input[i];
v = i + 1 < len ? v << 8 | input[i + 1] : v << 8;
v = i + 2 < len ? v << 8 | input[i + 2] : v << 8;
out[j] = Base64EncodeMap[(v >> 18) & 0x3F];
out[j + 1] = Base64EncodeMap[(v >> 12) & 0x3F];
if (i + 1 < len)
{
out[j + 2] = Base64EncodeMap[(v >> 6) & 0x3F];
}
else
{
out[j + 2] = '=';
}
if (i + 2 < len)
{
out[j + 3] = Base64EncodeMap[v & 0x3F];
}
else
{
out[j + 3] = '=';
}
}
return out;
}
static int
Base64IsValidChar(char c)
{
return (c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c == '+') ||
(c == '/') ||
(c == '=');
}
char *
Base64Decode(const char *input, size_t len)
{
size_t i, j;
int v;
size_t outLen;
char *out;
if (!input)
{
return NULL;
}
outLen = Base64DecodedSize(input, len);
if (len % 4)
{
/* Invalid length; must have incorrect padding */
return NULL;
}
/* Scan for invalid characters. */
for (i = 0; i < len; i++)
{
if (!Base64IsValidChar(input[i]))
{
return NULL;
}
}
out = Malloc(outLen + 1);
if (!out)
{
return NULL;
}
out[outLen] = '\0';
for (i = 0, j = 0; i < len; i += 4, j += 3)
{
v = Base64DecodeMap[input[i] - 43];
v = (v << 6) | Base64DecodeMap[input[i + 1] - 43];
v = input[i + 2] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 2] - 43];
v = input[i + 3] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 3] - 43];
out[j] = (v >> 16) & 0xFF;
if (input[i + 2] != '=')
out[j + 1] = (v >> 8) & 0xFF;
if (input[i + 3] != '=')
out[j + 2] = v & 0xFF;
}
return out;
}
extern void
Base64Unpad(char *base64, size_t length)
{
if (!base64)
{
return;
}
while (base64[length - 1] == '=')
{
length--;
}
base64[length] = '\0';
}
extern int
Base64Pad(char **base64Ptr, size_t length)
{
char *tmp;
size_t newSize;
size_t i;
if (length % 4 == 0)
{
return length; /* Success: no padding needed */
}
newSize = length + (4 - (length % 4));
tmp = Realloc(*base64Ptr, newSize + 100);;
if (!tmp)
{
return 0; /* Memory error */
}
*base64Ptr = tmp;
for (i = length; i < newSize; i++)
{
(*base64Ptr)[i] = '=';
}
(*base64Ptr)[newSize] = '\0';
return newSize;
}

249
src/Cron.c Normal file
View file

@ -0,0 +1,249 @@
/*
* 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 <Cron.h>
#include <Array.h>
#include <Memory.h>
#include <Util.h>
#include <pthread.h>
struct Cron
{
unsigned long tick;
Array *jobs;
pthread_mutex_t lock;
volatile unsigned int stop:1;
pthread_t thread;
};
typedef struct Job
{
unsigned long interval;
unsigned long lastExec;
JobFunc *func;
void *args;
} Job;
static Job *
JobCreate(long interval, JobFunc * func, void *args)
{
Job *job;
if (!func)
{
return NULL;
}
job = Malloc(sizeof(Job));
if (!job)
{
return NULL;
}
job->interval = interval;
job->lastExec = 0;
job->func = func;
job->args = args;
return job;
}
static void *
CronThread(void *args)
{
Cron *cron = args;
while (!cron->stop)
{
size_t i;
unsigned long ts; /* tick start */
unsigned long te; /* tick end */
pthread_mutex_lock(&cron->lock);
ts = UtilServerTs();
for (i = 0; i < ArraySize(cron->jobs); i++)
{
Job *job = ArrayGet(cron->jobs, i);
if (ts - job->lastExec > job->interval)
{
job->func(job->args);
job->lastExec = ts;
}
if (!job->interval)
{
ArrayDelete(cron->jobs, i);
Free(job);
}
}
te = UtilServerTs();
pthread_mutex_unlock(&cron->lock);
/* Only sleep if the jobs didn't overrun the tick */
if (cron->tick > (te - ts))
{
const unsigned long microTick = 100;
unsigned long remainingTick = cron->tick - (te - ts);
/* Only sleep for microTick ms at a time because if the job
* scheduler is supposed to stop before the tick is up, we
* don't want to be stuck in a long sleep */
while (remainingTick >= microTick && !cron->stop)
{
UtilSleepMillis(microTick);
remainingTick -= microTick;
}
if (remainingTick && !cron->stop)
{
UtilSleepMillis(remainingTick);
}
}
}
return NULL;
}
Cron *
CronCreate(unsigned long tick)
{
Cron *cron = Malloc(sizeof(Cron));
if (!cron)
{
return NULL;
}
cron->jobs = ArrayCreate();
if (!cron->jobs)
{
Free(cron);
return NULL;
}
cron->tick = tick;
cron->stop = 1;
pthread_mutex_init(&cron->lock, NULL);
return cron;
}
void
CronOnce(Cron * cron, JobFunc * func, void *args)
{
Job *job;
if (!cron || !func)
{
return;
}
job = JobCreate(0, func, args);
if (!job)
{
return;
}
pthread_mutex_lock(&cron->lock);
ArrayAdd(cron->jobs, job);
pthread_mutex_unlock(&cron->lock);
}
void
CronEvery(Cron * cron, unsigned long interval, JobFunc * func, void *args)
{
Job *job;
if (!cron || !func)
{
return;
}
job = JobCreate(interval, func, args);
if (!job)
{
return;
}
pthread_mutex_lock(&cron->lock);
ArrayAdd(cron->jobs, job);
pthread_mutex_unlock(&cron->lock);
}
void
CronStart(Cron * cron)
{
if (!cron || !cron->stop)
{
return;
}
cron->stop = 0;
pthread_create(&cron->thread, NULL, CronThread, cron);
}
void
CronStop(Cron * cron)
{
if (!cron || cron->stop)
{
return;
}
cron->stop = 1;
pthread_join(cron->thread, NULL);
}
void
CronFree(Cron * cron)
{
size_t i;
if (!cron)
{
return;
}
CronStop(cron);
pthread_mutex_lock(&cron->lock);
for (i = 0; i < ArraySize(cron->jobs); i++)
{
Free(ArrayGet(cron->jobs, i));
}
ArrayFree(cron->jobs);
pthread_mutex_unlock(&cron->lock);
pthread_mutex_destroy(&cron->lock);
Free(cron);
}

968
src/Db.c Normal file
View file

@ -0,0 +1,968 @@
/*
* 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 <Db.h>
#include <Memory.h>
#include <Json.h>
#include <Util.h>
#include <Str.h>
#include <Stream.h>
#include <Log.h>
#include <sys/types.h>
#include <dirent.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
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;
};