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;
};
struct DbRef
{
HashMap *json;
unsigned long 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 *
DbDirName(Db * db, Array * args, size_t strip)
{
size_t i;
char *str = StrConcat(2, db->dir, "/");
for (i = 0; i < ArraySize(args) - strip; i++)
{
char *tmp;
tmp = StrConcat(3, str, ArrayGet(args, i), "/");
Free(str);
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])
{
switch (arg[j])
{
case '/':
arg[j] = '_';
break;
case '.':
arg[j] = '-';
break;
default:
break;
}
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 */
{
unsigned long 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 = UtilServerTs();
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;
}
int
DbDelete(Db * db, size_t nArgs,...)
{
va_list ap;
Array *args;
char *file;
char *hash;
int ret = 1;
DbRef *ref;
if (!db)
{
return 0;
}
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;
}
int
DbUnlock(Db * db, DbRef * ref)
{
int destroy;
if (!db || !ref)
{
return 0;
}
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 0;
}
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 = 0;
}
else
{
destroy = 1;
}
Free(key);
}
else
{
destroy = 1;
}
if (destroy)
{
JsonFree(ref->json);
StringArrayFree(ref->name);
Free(ref);
}
pthread_mutex_unlock(&db->lock);
return 1;
}
int
DbExists(Db * db, size_t nArgs,...)
{
va_list ap;
Array *args;
char *file;
int ret;
va_start(ap, nArgs);
args = ArrayFromVarArgs(nArgs, ap);
va_end(ap);
if (!args)
{
return 0;
}
pthread_mutex_lock(&db->lock);
file = DbFileName(db, args);
ret = UtilLastModified(file);
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;
}
int
DbJsonSet(DbRef * ref, HashMap * json)
{
if (!ref || !json)
{
return 0;
}
JsonFree(ref->json);
ref->json = JsonDuplicate(json);
return 1;
}

401
src/HashMap.c Normal file
View file

@ -0,0 +1,401 @@
/*
* 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 <HashMap.h>
#include <Memory.h>
#include <Str.h>
#include <stddef.h>
#include <string.h>
typedef struct HashMapBucket
{
unsigned long hash;
char *key;
void *value;
} HashMapBucket;
struct HashMap
{
size_t count;
size_t capacity;
HashMapBucket **entries;
unsigned long (*hashFunc) (const char *);
float maxLoad;
size_t iterator;
};
static unsigned long
HashMapHashKey(const char *key)
{
unsigned long hash = 2166136261u;
size_t i = 0;
while (key[i])
{
hash ^= (unsigned char) key[i];
hash *= 16777619;
i++;
}
return hash;
}
static int
HashMapGrow(HashMap * map)
{
size_t oldCapacity;
size_t i;
HashMapBucket **newEntries;
if (!map)
{
return 0;
}
oldCapacity = map->capacity;
map->capacity *= 2;
newEntries = Malloc(map->capacity * sizeof(HashMapBucket *));
if (!newEntries)
{
map->capacity /= 2;
return 0;
}
memset(newEntries, 0, map->capacity * sizeof(HashMapBucket *));
for (i = 0; i < oldCapacity; i++)
{
/* If there is a value here, and it isn't a tombstone */
if (map->entries[i] && map->entries[i]->hash)
{
/* Copy it to the new entries array */
size_t index = map->entries[i]->hash % map->capacity;
for (;;)
{
if (newEntries[index])
{
if (!newEntries[index]->hash)
{
Free(newEntries[index]);
newEntries[index] = map->entries[i];
break;
}
}
else
{
newEntries[index] = map->entries[i];
break;
}
index = (index + 1) % map->capacity;
}
}
else
{
/* Either NULL or a tombstone */
Free(map->entries[i]);
}
}
Free(map->entries);
map->entries = newEntries;
return 1;
}
HashMap *
HashMapCreate(void)
{
HashMap *map = Malloc(sizeof(HashMap));
if (!map)
{
return NULL;
}
map->maxLoad = 0.75;
map->count = 0;
map->capacity = 16;
map->iterator = 0;
map->hashFunc = HashMapHashKey;
map->entries = Malloc(map->capacity * sizeof(HashMapBucket *));
if (!map->entries)
{
Free(map);
return NULL;
}
memset(map->entries, 0, map->capacity * sizeof(HashMapBucket *));
return map;
}
void *
HashMapDelete(HashMap * map, const char *key)
{
unsigned long hash;
size_t index;
if (!map || !key)
{
return NULL;
}
hash = map->hashFunc(key);
index = hash % map->capacity;
for (;;)
{
HashMapBucket *bucket = map->entries[index];
if (!bucket)
{
break;
}
if (bucket->hash == hash)
{
bucket->hash = 0;
return bucket->value;
}
index = (index + 1) % map->capacity;
}
return NULL;
}
void
HashMapFree(HashMap * map)
{
if (map)
{
size_t i;
for (i = 0; i < map->capacity; i++)
{
if (map->entries[i])
{
Free(map->entries[i]->key);
Free(map->entries[i]);
}
}
Free(map->entries);
Free(map);
}
}
void *
HashMapGet(HashMap * map, const char *key)
{
unsigned long hash;
size_t index;
if (!map || !key)
{
return NULL;
}
hash = map->hashFunc(key);
index = hash % map->capacity;
for (;;)
{
HashMapBucket *bucket = map->entries[index];
if (!bucket)
{
break;
}
if (bucket->hash == hash)
{
return bucket->value;
}
index = (index + 1) % map->capacity;
}
return NULL;
}
int
HashMapIterateReentrant(HashMap * map, char **key, void **value, size_t * i)
{
if (!map)
{
return 0;
}
if (*i >= map->capacity)
{
*i = 0;
*key = NULL;
*value = NULL;
return 0;
}
while (*i < map->capacity)
{
HashMapBucket *bucket = map->entries[*i];
*i = *i + 1;
if (bucket && bucket->hash)
{
*key = bucket->key;
*value = bucket->value;
return 1;
}
}
*i = 0;
return 0;
}
int
HashMapIterate(HashMap * map, char **key, void **value)
{
if (!map)
{
return 0;
}
else
{
return HashMapIterateReentrant(map, key, value, &map->iterator);
}
}
void
HashMapMaxLoadSet(HashMap * map, float load)
{
if (!map || (load > 1.0 || load <= 0))
{
return;
}
map->maxLoad = load;
}
void
HashMapFunctionSet(HashMap * map, unsigned long (*hashFunc) (const char *))
{
if (!map || !hashFunc)
{
return;
}
map->hashFunc = hashFunc;
}
void *
HashMapSet(HashMap * map, char *key, void *value)
{
unsigned long hash;
size_t index;
if (!map || !key || !value)
{
return NULL;
}
key = StrDuplicate(key);
if (!key)
{
return NULL;
}
if (map->count + 1 > map->capacity * map->maxLoad)
{
HashMapGrow(map);
}
hash = map->hashFunc(key);
index = hash % map->capacity;
for (;;)
{
HashMapBucket *bucket = map->entries[index];
if (!bucket)
{
bucket = Malloc(sizeof(HashMapBucket));
if (!bucket)
{
break;
}
bucket->hash = hash;
bucket->key = key;
bucket->value = value;
map->entries[index] = bucket;
map->count++;
break;
}
if (!bucket->hash)
{
bucket->hash = hash;
Free(bucket->key);
bucket->key = key;
bucket->value = value;
break;
}
if (bucket->hash == hash)
{
void *oldValue = bucket->value;
Free(bucket->key);
bucket->key = key;
bucket->value = value;
return oldValue;
}
index = (index + 1) % map->capacity;
}
return NULL;
}
void
HashMapIterateFree(char *key, void *value)
{
if (key)
{
Free(key);
}
if (value)
{
Free(value);
}
}

664
src/HeaderParser.c Normal file
View file

@ -0,0 +1,664 @@
/*
* 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 <HeaderParser.h>
#include <Memory.h>
#include <Str.h>
#include <string.h>
#include <ctype.h>
static int
HeaderConsumeWhitespace(HeaderExpr * expr)
{
int c;
while (1)
{
c = StreamGetc(expr->state.stream);
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
{
expr->type = HP_EOF;
expr->data.error.msg = "End of stream reached.";
expr->data.error.lineNo = expr->state.lineNo;
break;
}
if (isspace(c))
{
if (c == '\n')
{
expr->state.lineNo++;
}
}
else
{
break;
}
}
return c;
}
static char *
HeaderConsumeWord(HeaderExpr * expr)
{
char *str = Malloc(16 * sizeof(char));
int len = 16;
int i;
int c;
if (!str)
{
return NULL;
}
c = HeaderConsumeWhitespace(expr);
i = 0;
str[i] = c;
i++;
while (!isspace(c = StreamGetc(expr->state.stream)))
{
if (i >= len)
{
len *= 2;
str = Realloc(str, len * sizeof(char));
}
str[i] = c;
i++;
}
if (i >= len)
{
len++;
str = Realloc(str, len * sizeof(char));
}
str[i] = '\0';
if (c != EOF)
{
StreamUngetc(expr->state.stream, c);
}
return str;
}
static char *
HeaderConsumeAlnum(HeaderExpr * expr)
{
char *str = Malloc(16 * sizeof(char));
int len = 16;
int i;
int c;
if (!str)
{
return NULL;
}
c = HeaderConsumeWhitespace(expr);
i = 0;
str[i] = c;
i++;
while (isalnum(c = StreamGetc(expr->state.stream)))
{
if (i >= len)
{
len *= 2;
str = Realloc(str, len * sizeof(char));
}
str[i] = c;
i++;
}
if (i >= len)
{
len++;
str = Realloc(str, len * sizeof(char));
}
str[i] = '\0';
if (c != EOF)
{
StreamUngetc(expr->state.stream, c);
}
return str;
}
static char *
HeaderConsumeArg(HeaderExpr * expr)
{
char *str = Malloc(16 * sizeof(char));
int len = 16;
int i;
int c;
int block = 0;
if (!str)
{
return NULL;
}
c = HeaderConsumeWhitespace(expr);
i = 0;
str[i] = c;
i++;
while (((c = StreamGetc(expr->state.stream)) != ',' && c != ')') || block > 0)
{
if (i >= len)
{
len *= 2;
str = Realloc(str, len * sizeof(char));
}
str[i] = c;
i++;
if (c == '(')
{
block++;
}
else if (c == ')')
{
block--;
}
else if (c == '\n')
{
expr->state.lineNo++;
}
}
if (i >= len)
{
len++;
str = Realloc(str, len * sizeof(char));
}
str[i] = '\0';
if (c != EOF)
{
StreamUngetc(expr->state.stream, c);
}
return str;
}
void
HeaderParse(Stream * stream, HeaderExpr * expr)
{
int c;
if (!expr)
{
return;
}
if (!stream)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "NULL pointer to stream.";
expr->data.error.lineNo = -1;
return;
}
if (expr->type == HP_DECLARATION && expr->data.declaration.args)
{
size_t i;
for (i = 0; i < ArraySize(expr->data.declaration.args); i++)
{
Free(ArrayGet(expr->data.declaration.args, i));
}
ArrayFree(expr->data.declaration.args);
}
expr->state.stream = stream;
if (!expr->state.lineNo)
{
expr->state.lineNo = 1;
}
c = HeaderConsumeWhitespace(expr);
if (StreamEof(stream) || StreamError(stream))
{
expr->type = HP_EOF;
expr->data.error.msg = "End of stream reached.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
if (c == '/')
{
int i = 0;
c = StreamGetc(expr->state.stream);
if (c != '*')
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Expected comment opening.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
expr->type = HP_COMMENT;
while (1)
{
if (i >= HEADER_EXPR_MAX - 1)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Memory limit exceeded while parsing comment.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
c = StreamGetc(expr->state.stream);
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Unterminated comment.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
if (c == '*')
{
c = StreamGetc(expr->state.stream);
if (c == '/')
{
expr->data.text[i] = '\0';
break;
}
else
{
expr->data.text[i] = '*';
i++;
expr->data.text[i] = c;
i++;
if (c == '\n')
{
expr->state.lineNo++;
}
}
}
else
{
expr->data.text[i] = c;
i++;
if (c == '\n')
{
expr->state.lineNo++;
}
}
}
}
else if (c == '#')
{
int i = 0;
char *word;
expr->type = HP_PREPROCESSOR_DIRECTIVE;
expr->data.text[i] = '#';
i++;
word = HeaderConsumeWord(expr);
strncpy(expr->data.text + i, word, HEADER_EXPR_MAX - i - 1);
i += strlen(word);
if (StrEquals(word, "include") ||
StrEquals(word, "undef") ||
StrEquals(word, "ifdef") ||
StrEquals(word, "ifndef"))
{
/* Read one more word */
Free(word);
word = HeaderConsumeWord(expr);
if (i + strlen(word) + 1 >= HEADER_EXPR_MAX)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Memory limit reached parsing preprocessor directive.";
expr->data.error.lineNo = expr->state.lineNo;
}
else
{
strncpy(expr->data.text + i + 1, word, HEADER_EXPR_MAX - i - 1);
expr->data.text[i] = ' ';
}
Free(word);
}
else if (StrEquals(word, "define") ||
StrEquals(word, "if") ||
StrEquals(word, "elif") ||
StrEquals(word, "error"))
{
int pC = 0;
Free(word);
expr->data.text[i] = ' ';
i++;
while (1)
{
if (i >= HEADER_EXPR_MAX - 1)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Memory limit reached parsing preprocessor directive.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
c = StreamGetc(expr->state.stream);
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Unterminated preprocessor directive.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
if (c == '\n')
{
expr->state.lineNo++;
if (pC != '\\')
{
expr->data.text[i] = '\0';
break;
}
}
expr->data.text[i] = c;
i++;
pC = c;
}
}
else if (StrEquals(word, "else") ||
StrEquals(word, "endif"))
{
/* Read no more words, that's the whole directive */
}
else
{
Free(word);
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Unknown preprocessor directive.";
expr->data.error.lineNo = expr->state.lineNo;
}
}
else
{
char *word;
StreamUngetc(expr->state.stream, c);
word = HeaderConsumeWord(expr);
if (StrEquals(word, "typedef"))
{
int block = 0;
int i = 0;
expr->type = HP_TYPEDEF;
strncpy(expr->data.text, word, HEADER_EXPR_MAX - 1);
i += strlen(word);
expr->data.text[i] = ' ';
i++;
while (1)
{
if (i >= HEADER_EXPR_MAX - 1)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Memory limit exceeded while parsing typedef.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
c = StreamGetc(expr->state.stream);
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Unterminated typedef.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
expr->data.text[i] = c;
i++;
if (c == '{')
{
block++;
}
else if (c == '}')
{
block--;
}
else if (c == '\n')
{
expr->state.lineNo++;
}
if (block <= 0 && c == ';')
{
expr->data.text[i] = '\0';
break;
}
}
}
else if (StrEquals(word, "extern"))
{
int wordLimit = sizeof(expr->data.declaration.returnType) - 8;
int wordLen;
Free(word);
word = HeaderConsumeWord(expr);
wordLen = strlen(word);
if (wordLen > wordLimit)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Return of declaration exceeds length limit.";
expr->data.error.lineNo = expr->state.lineNo;
}
else
{
int i = wordLen;
expr->type = HP_GLOBAL;
strncpy(expr->data.global.type, word, wordLimit);
if (StrEquals(word, "struct") ||
StrEquals(word, "enum") ||
StrEquals(word, "const") ||
StrEquals(word, "unsigned"))
{
Free(word);
word = HeaderConsumeWord(expr);
wordLen = strlen(word);
expr->data.global.type[i] = ' ';
strncpy(expr->data.global.type + i + 1, word, wordLen + 1);
i += wordLen + 1;
}
Free(word);
c = HeaderConsumeWhitespace(expr);
if (c == '*')
{
expr->data.global.type[i] = ' ';
i++;
expr->data.global.type[i] = '*';
i++;
while ((c = HeaderConsumeWhitespace(expr)) == '*')
{
expr->data.global.type[i] = c;
i++;
}
}
StreamUngetc(expr->state.stream, c);
word = HeaderConsumeAlnum(expr);
wordLen = strlen(word);
wordLimit = sizeof(expr->data.declaration.name) - 1;
if (wordLen > wordLimit)
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Function name too long.";
expr->data.error.lineNo = expr->state.lineNo;
}
else
{
strncpy(expr->data.global.name, word, wordLimit);
Free(word);
word = NULL;
c = HeaderConsumeWhitespace(expr);
if (c == ';')
{
/* That's the end of the global. */
}
else if (c == '[')
{
/* Looks like we have an array. Slurp all the
* dimensions */
int i = wordLen;
expr->data.global.name[i] = '[';
i++;
while (1)
{
if (i >= HEADER_EXPR_MAX - wordLen)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Memory limit exceeded while parsing global array.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
c = StreamGetc(expr->state.stream);
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Unterminated global array.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
if (c == ';')
{
expr->data.global.name[i] = '\0';
break;
}
else
{
expr->data.global.name[i] = c;
i++;
}
}
}
else if (c == '(')
{
expr->type = HP_DECLARATION;
expr->data.declaration.args = ArrayCreate();
do
{
word = HeaderConsumeArg(expr);
ArrayAdd(expr->data.declaration.args, word);
word = NULL;
}
while ((!StreamEof(expr->state.stream)) && ((c = HeaderConsumeWhitespace(expr)) != ')'));
if (StreamEof(expr->state.stream))
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "End of file reached before ')'.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
c = HeaderConsumeWhitespace(expr);
if (c != ';')
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Expected ';'.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
}
else
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Expected ';', '[', or '('";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
}
}
}
else
{
/* Cope with preprocessor macro expansions at the top
* level. */
expr->type = HP_UNKNOWN;
strncpy(expr->data.text, word, HEADER_EXPR_MAX);
}
Free(word);
}
}

642
src/Http.c Normal file
View file

@ -0,0 +1,642 @@
/*
* 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 <Http.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <Memory.h>
#include <HashMap.h>
#include <Util.h>
#include <Str.h>
#ifndef CYTOPLASM_STRING_CHUNK
#define CYTOPLASM_STRING_CHUNK 64
#endif
const char *
HttpRequestMethodToString(const HttpRequestMethod method)
{
switch (method)
{
case HTTP_GET:
return "GET";
case HTTP_HEAD:
return "HEAD";
case HTTP_POST:
return "POST";
case HTTP_PUT:
return "PUT";
case HTTP_DELETE:
return "DELETE";
case HTTP_CONNECT:
return "CONNECT";
case HTTP_OPTIONS:
return "OPTIONS";
case HTTP_TRACE:
return "TRACE";
case HTTP_PATCH:
return "PATCH";
default:
return NULL;
}
}
HttpRequestMethod
HttpRequestMethodFromString(const char *str)
{
if (StrEquals(str, "GET"))
{
return HTTP_GET;
}
if (StrEquals(str, "HEAD"))
{
return HTTP_HEAD;
}
if (StrEquals(str, "POST"))
{
return HTTP_POST;
}
if (StrEquals(str, "PUT"))
{
return HTTP_PUT;
}
if (StrEquals(str, "DELETE"))
{
return HTTP_DELETE;
}
if (StrEquals(str, "CONNECT"))
{
return HTTP_CONNECT;
}
if (StrEquals(str, "OPTIONS"))
{
return HTTP_OPTIONS;
}
if (StrEquals(str, "TRACE"))
{
return HTTP_TRACE;
}
if (StrEquals(str, "PATCH"))
{
return HTTP_PATCH;
}
return HTTP_METHOD_UNKNOWN;
}
const char *
HttpStatusToString(const HttpStatus status)
{
switch (status)
{
case HTTP_CONTINUE:
return "Continue";
case HTTP_SWITCHING_PROTOCOLS:
return "Switching Protocols";
case HTTP_EARLY_HINTS:
return "Early Hints";
case HTTP_OK:
return "Ok";
case HTTP_CREATED:
return "Created";
case HTTP_ACCEPTED:
return "Accepted";
case HTTP_NON_AUTHORITATIVE_INFORMATION:
return "Non-Authoritative Information";
case HTTP_NO_CONTENT:
return "No Content";
case HTTP_RESET_CONTENT:
return "Reset Content";
case HTTP_PARTIAL_CONTENT:
return "Partial Content";
case HTTP_MULTIPLE_CHOICES:
return "Multiple Choices";
case HTTP_MOVED_PERMANENTLY:
return "Moved Permanently";
case HTTP_FOUND:
return "Found";
case HTTP_SEE_OTHER:
return "See Other";
case HTTP_NOT_MODIFIED:
return "Not Modified";
case HTTP_TEMPORARY_REDIRECT:
return "Temporary Redirect";
case HTTP_PERMANENT_REDIRECT:
return "Permanent Redirect";
case HTTP_BAD_REQUEST:
return "Bad Request";
case HTTP_UNAUTHORIZED:
return "Unauthorized";
case HTTP_FORBIDDEN:
return "Forbidden";
case HTTP_NOT_FOUND:
return "Not Found";
case HTTP_METHOD_NOT_ALLOWED:
return "Method Not Allowed";
case HTTP_NOT_ACCEPTABLE:
return "Not Acceptable";
case HTTP_PROXY_AUTH_REQUIRED:
return "Proxy Authentication Required";
case HTTP_REQUEST_TIMEOUT:
return "Request Timeout";
case HTTP_CONFLICT:
return "Conflict";
case HTTP_GONE:
return "Gone";
case HTTP_LENGTH_REQUIRED:
return "Length Required";
case HTTP_PRECONDITION_FAILED:
return "Precondition Failed";
case HTTP_PAYLOAD_TOO_LARGE:
return "Payload Too Large";
case HTTP_URI_TOO_LONG:
return "URI Too Long";
case HTTP_UNSUPPORTED_MEDIA_TYPE:
return "Unsupported Media Type";
case HTTP_RANGE_NOT_SATISFIABLE:
return "Range Not Satisfiable";
case HTTP_EXPECTATION_FAILED:
return "Expectation Failed";
case HTTP_TEAPOT:
return "I'm a Teapot";
case HTTP_UPGRADE_REQUIRED:
return "Upgrade Required";
case HTTP_PRECONDITION_REQUIRED:
return "Precondition Required";
case HTTP_TOO_MANY_REQUESTS:
return "Too Many Requests";
case HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE:
return "Request Header Fields Too Large";
case HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
return "Unavailable For Legal Reasons";
case HTTP_INTERNAL_SERVER_ERROR:
return "Internal Server Error";
case HTTP_NOT_IMPLEMENTED:
return "Not Implemented";
case HTTP_BAD_GATEWAY:
return "Bad Gateway";
case HTTP_SERVICE_UNAVAILABLE:
return "Service Unavailable";
case HTTP_GATEWAY_TIMEOUT:
return "Gateway Timeout";
case HTTP_VERSION_NOT_SUPPORTED:
return "Version Not Supported";
case HTTP_VARIANT_ALSO_NEGOTIATES:
return "Variant Also Negotiates";
case HTTP_NOT_EXTENDED:
return "Not Extended";
case HTTP_NETWORK_AUTH_REQUIRED:
return "Network Authentication Required";
default:
return NULL;
}
}
char *
HttpUrlEncode(char *str)
{
size_t size;
size_t len;
char *encoded;
if (!str)
{
return NULL;
}
size = CYTOPLASM_STRING_CHUNK;
len = 0;
encoded = Malloc(size);
if (!encoded)
{
return NULL;
}
while (*str)
{
char c = *str;
if (len >= size - 4)
{
char *tmp;
size += CYTOPLASM_STRING_CHUNK;
tmp = Realloc(encoded, size);
if (!tmp)
{
Free(encoded);
return NULL;
}
encoded = tmp;
}
/* Control characters and extended characters */
if (c <= 0x1F || c >= 0x7F)
{
goto percentEncode;
}
/* Reserved and unsafe characters */
switch (c)
{
case '$':
case '&':
case '+':
case ',':
case '/':
case ':':
case ';':
case '=':
case '?':
case '@':
case ' ':
case '"':
case '<':
case '>':
case '#':
case '%':
case '{':
case '}':
case '|':
case '\\':
case '^':
case '~':
case '[':
case ']':
case '`':
goto percentEncode;
break;
default:
encoded[len] = c;
len++;
str++;
continue;
}
percentEncode:
encoded[len] = '%';
len++;
snprintf(encoded + len, 3, "%2X", c);
len += 2;
str++;
}
encoded[len] = '\0';
return encoded;
}
char *
HttpUrlDecode(char *str)
{
size_t i;
size_t inputLen;
char *decoded;
if (!str)
{
return NULL;
}
i = 0;
inputLen = strlen(str);
decoded = Malloc(inputLen + 1);
if (!decoded)
{
return NULL;
}
while (*str)
{
char c = *str;
if (c == '%')
{
unsigned int d;
str++;
if (sscanf(str, "%2X", &d) != 1)
{
/* Decoding error */
Free(decoded);
return NULL;
}
if (!d)
{
/* Null character given, don't put that in the string. */
continue;
}
c = (char) d;
str++;
}
decoded[i] = c;
i++;
str++;
}
decoded[i] = '\0';
return decoded;
}
HashMap *
HttpParamDecode(char *in)
{
HashMap *params;
if (!in)
{
return NULL;
}
params = HashMapCreate();
if (!params)
{
return NULL;
}
while (*in)
{
char *buf;
size_t allocated;
size_t len;
char *decKey;
char *decVal;
/* Read in key */
allocated = CYTOPLASM_STRING_CHUNK;
buf = Malloc(allocated);
len = 0;
while (*in && *in != '=')
{
if (len >= allocated - 1)
{
allocated += CYTOPLASM_STRING_CHUNK;
buf = Realloc(buf, allocated);
}
buf[len] = *in;
len++;
in++;
}
buf[len] = '\0';
/* Sanity check */
if (*in != '=')
{
/* Malformed param */
Free(buf);
HashMapFree(params);
return NULL;
}
in++;
/* Decode key */
decKey = HttpUrlDecode(buf);
Free(buf);
if (!decKey)
{
/* Decoding error */
HashMapFree(params);
return NULL;
}
/* Read in value */
allocated = CYTOPLASM_STRING_CHUNK;
buf = Malloc(allocated);
len = 0;
while (*in && *in != '&')
{
if (len >= allocated - 1)
{
allocated += CYTOPLASM_STRING_CHUNK;
buf = Realloc(buf, allocated);
}
buf[len] = *in;
len++;
in++;
}
buf[len] = '\0';
/* Decode value */
decVal = HttpUrlDecode(buf);
Free(buf);
if (!decVal)
{
/* Decoding error */
HashMapFree(params);
return NULL;
}
buf = HashMapSet(params, decKey, decVal);
if (buf)
{
Free(buf);
}
Free(decKey);
if (*in == '&')
{
in++;
continue;
}
else
{
break;
}
}
return params;
}
char *
HttpParamEncode(HashMap * params)
{
char *key;
char *val;
char *out = NULL;
if (!params || !out)
{
return NULL;
}
while (HashMapIterate(params, &key, (void *) &val))
{
char *encKey;
char *encVal;
encKey = HttpUrlEncode(key);
encVal = HttpUrlEncode(val);
if (!encKey || !encVal)
{
/* Memory error */
Free(encKey);
Free(encVal);
return NULL;
}
/* TODO */
Free(encKey);
Free(encVal);
}
return out;
}
HashMap *
HttpParseHeaders(Stream * fp)
{
HashMap *headers;
char *line;
ssize_t lineLen;
size_t lineSize;
char *headerKey;
char *headerValue;
if (!fp)
{
return NULL;
}
headers = HashMapCreate();
if (!headers)
{
return NULL;
}
line = NULL;
lineLen = 0;
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) != -1)
{
char *headerPtr;
ssize_t i;
size_t len;
if (StrEquals(line, "\r\n") || StrEquals(line, "\n"))
{
break;
}
for (i = 0; i < lineLen; i++)
{
if (line[i] == ':')
{
line[i] = '\0';
break;
}
line[i] = tolower((unsigned char) line[i]);
}
len = i + 1;
headerKey = Malloc(len * sizeof(char));
if (!headerKey)
{
goto error;
}
strncpy(headerKey, line, len);
headerPtr = line + i + 1;
while (isspace((unsigned char) *headerPtr))
{
headerPtr++;
}
for (i = lineLen - 1; i > (line + lineLen) - headerPtr; i--)
{
if (!isspace((unsigned char) line[i]))
{
break;
}
line[i] = '\0';
}
len = strlen(headerPtr) + 1;
headerValue = Malloc(len * sizeof(char));
if (!headerValue)
{
Free(headerKey);
goto error;
}
strncpy(headerValue, headerPtr, len);
HashMapSet(headers, headerKey, headerValue);
Free(headerKey);
}
Free(line);
return headers;
error:
Free(line);
while (HashMapIterate(headers, &headerKey, (void **) &headerValue))
{
Free(headerValue);
}
HashMapFree(headers);
return NULL;
}

298
src/HttpClient.c Normal file
View file

@ -0,0 +1,298 @@
/*
* 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 <HttpClient.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <Http.h>
#include <Memory.h>
#include <Util.h>
#include <Tls.h>
struct HttpClientContext
{
HashMap *responseHeaders;
Stream *stream;
};
HttpClientContext *
HttpRequest(HttpRequestMethod method, int flags, unsigned short port, char *host, char *path)
{
HttpClientContext *context;
int sd = -1;
struct addrinfo hints, *res, *res0;
int error;
char serv[8];
if (!method || !host || !path)
{
return NULL;
}
#ifndef TLS_IMPL
if (flags & HTTP_FLAG_TLS)
{
return NULL;
}
#endif
if (!port)
{
if (flags & HTTP_FLAG_TLS)
{
strcpy(serv, "https");
}
else
{
strcpy(serv, "www");
}
}
else
{
snprintf(serv, sizeof(serv), "%hu", port);
}
context = Malloc(sizeof(HttpClientContext));
if (!context)
{
return NULL;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(host, serv, &hints, &res0);
if (error)
{
Free(context);
return NULL;
}
for (res = res0; res; res = res->ai_next)
{
sd = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (sd < 0)
{
continue;
}
if (connect(sd, res->ai_addr, res->ai_addrlen) < 0)
{
close(sd);
sd = -1;
continue;
}
break;
}
if (sd < 0)
{
Free(context);
return NULL;
}
freeaddrinfo(res0);
#ifdef TLS_IMPL
if (flags & HTTP_FLAG_TLS)
{
context->stream = TlsClientStream(sd, host);
}
else
{
context->stream = StreamFd(sd);
}
#else
context->stream = StreamFd(sd);
#endif
if (!context->stream)
{
Free(context);
close(sd);
return NULL;
}
StreamPrintf(context->stream, "%s %s HTTP/1.0\r\n",
HttpRequestMethodToString(method), path);
HttpRequestHeader(context, "Connection", "close");
HttpRequestHeader(context, "User-Agent", LIB_NAME "/" LIB_VERSION);
HttpRequestHeader(context, "Host", host);
return context;
}
void
HttpRequestHeader(HttpClientContext * context, char *key, char *val)
{
if (!context || !key || !val)
{
return;
}
StreamPrintf(context->stream, "%s: %s\r\n", key, val);
}
void
HttpRequestSendHeaders(HttpClientContext * context)
{
if (!context)
{
return;
}
StreamPuts(context->stream, "\r\n");
StreamFlush(context->stream);
}
HttpStatus
HttpRequestSend(HttpClientContext * context)
{
HttpStatus status;
char *line = NULL;
ssize_t lineLen;
size_t lineSize = 0;
char *tmp;
if (!context)
{
return 0;
}
StreamFlush(context->stream);
lineLen = UtilGetLine(&line, &lineSize, context->stream);
while (lineLen == -1 && errno == EAGAIN)
{
StreamClearError(context->stream);
lineLen = UtilGetLine(&line, &lineSize, context->stream);
}
if (lineLen == -1)
{
return 0;
}
/* Line must contain at least "HTTP/x.x xxx" */
if (lineLen < 12)
{
return 0;
}
if (!(strncmp(line, "HTTP/1.0", 8) == 0 ||
strncmp(line, "HTTP/1.1", 8) == 0))
{
return 0;
}
tmp = line + 9;
while (isspace((unsigned char) *tmp) && *tmp != '\0')
{
tmp++;
}
if (!*tmp)
{
return 0;
}
status = atoi(tmp);
if (!status)
{
return 0;
}
context->responseHeaders = HttpParseHeaders(context->stream);
if (!context->responseHeaders)
{
return 0;
}
return status;
}
HashMap *
HttpResponseHeaders(HttpClientContext * context)
{
if (!context)
{
return NULL;
}
return context->responseHeaders;
}
Stream *
HttpClientStream(HttpClientContext * context)
{
if (!context)
{
return NULL;
}
return context->stream;
}
void
HttpClientContextFree(HttpClientContext * context)
{
char *key;
void *val;
if (!context)
{
return;
}
while (HashMapIterate(context->responseHeaders, &key, &val))
{
Free(val);
}
HashMapFree(context->responseHeaders);
StreamClose(context->stream);
Free(context);
}

296
src/HttpRouter.c Normal file
View file

@ -0,0 +1,296 @@
/*
* 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 <HttpRouter.h>
#include <Memory.h>
#include <HashMap.h>
#include <Str.h>
#include <regex.h>
#include <string.h>
#define REG_FLAGS (REG_EXTENDED)
#define REG_MAX_SUB 8
typedef struct RouteNode
{
HttpRouteFunc *exec;
HashMap *children;
regex_t regex;
} RouteNode;
struct HttpRouter
{
RouteNode *root;
};
static RouteNode *
RouteNodeCreate(char *regex, HttpRouteFunc * exec)
{
RouteNode *node;
if (!regex)
{
return NULL;
}
node = Malloc(sizeof(RouteNode));
if (!node)
{
return NULL;
}
node->children = HashMapCreate();
if (!node->children)
{
Free(node);
return NULL;
}
/* Force the regex to match the entire path part exactly. */
regex = StrConcat(3, "^", regex, "$");
if (!regex)
{
Free(node);
return NULL;
}
if (regcomp(&node->regex, regex, REG_FLAGS) != 0)
{
HashMapFree(node->children);
Free(node);
Free(regex);
return NULL;
}
node->exec = exec;
Free(regex);
return node;
}
static void
RouteNodeFree(RouteNode * node)
{
char *key;
RouteNode *val;
if (!node)
{
return;
}
while (HashMapIterate(node->children, &key, (void **) &val))
{
RouteNodeFree(val);
}
HashMapFree(node->children);
regfree(&node->regex);
Free(node);
}
HttpRouter *
HttpRouterCreate(void)
{
HttpRouter *router = Malloc(sizeof(HttpRouter));
if (!router)
{
return NULL;
}
router->root = RouteNodeCreate("/", NULL);
return router;
}
void
HttpRouterFree(HttpRouter * router)
{
if (!router)
{
return;
}
RouteNodeFree(router->root);
Free(router);
}
int
HttpRouterAdd(HttpRouter * router, char *regPath, HttpRouteFunc * exec)
{
RouteNode *node;
char *pathPart;
char *tmp;
if (!router || !regPath || !exec)
{
return 0;
}
if (StrEquals(regPath, "/"))
{
router->root->exec = exec;
return 1;
}
regPath = StrDuplicate(regPath);
if (!regPath)
{
return 0;
}
tmp = regPath;
node = router->root;
while ((pathPart = strtok_r(tmp, "/", &tmp)))
{
RouteNode *tNode = HashMapGet(node->children, pathPart);
if (!tNode)
{
tNode = RouteNodeCreate(pathPart, NULL);
RouteNodeFree(HashMapSet(node->children, pathPart, tNode));
}
node = tNode;
}
node->exec = exec;
Free(regPath);
return 1;
}
int
HttpRouterRoute(HttpRouter * router, char *path, void *args, void **ret)
{
RouteNode *node;
char *pathPart;
char *tmp;
HttpRouteFunc *exec = NULL;
Array *matches;
size_t i;
int retval;
if (!router || !path)
{
return 0;
}
matches = ArrayCreate();
if (!matches)
{
return 0;
}
node = router->root;
if (StrEquals(path, "/"))
{
exec = node->exec;
}
else
{
path = StrDuplicate(path);
tmp = path;
while ((pathPart = strtok_r(tmp, "/", &tmp)))
{
char *key;
RouteNode *val = NULL;
regmatch_t pmatch[REG_MAX_SUB];
i = 0;
while (HashMapIterateReentrant(node->children, &key, (void **) &val, &i))
{
if (regexec(&val->regex, pathPart, REG_MAX_SUB, pmatch, 0) == 0)
{
break;
}
val = NULL;
}
if (!val)
{
exec = NULL;
break;
}
node = val;
exec = node->exec;
/* If we want to pass an arg, the match must be in parens */
if (val->regex.re_nsub)
{
/* pmatch[0] is the whole string, not the first
* subexpression */
for (i = 1; i < REG_MAX_SUB; i++)
{
if (pmatch[i].rm_so == -1)
{
break;
}
ArrayAdd(matches, StrSubstr(pathPart, pmatch[i].rm_so, pmatch[i].rm_eo));
}
}
}
Free(path);
}
if (!exec)
{
retval = 0;
goto finish;
}
if (ret)
{
*ret = exec(matches, args);
}
else
{
exec(matches, args);
}
retval = 1;
finish:
for (i = 0; i < ArraySize(matches); i++)
{
Free(ArrayGet(matches, i));
}
ArrayFree(matches);
return retval;
}

753
src/HttpServer.c Normal file
View file

@ -0,0 +1,753 @@
/*
* 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 <HttpServer.h>
#include <Memory.h>
#include <Queue.h>
#include <Array.h>
#include <Util.h>
#include <Tls.h>
#include <Log.h>
#include <Str.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <string.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
static const char ENABLE = 1;
struct HttpServer
{
HttpServerConfig config;
int sd;
pthread_t socketThread;
volatile unsigned int stop:1;
volatile unsigned int isRunning:1;
Queue *connQueue;
pthread_mutex_t connQueueMutex;
Array *threadPool;
};
struct HttpServerContext
{
HashMap *requestHeaders;
HttpRequestMethod requestMethod;
char *requestPath;
HashMap *requestParams;
HashMap *responseHeaders;
HttpStatus responseStatus;
Stream *stream;
};
typedef struct HttpServerWorkerThreadArgs
{
HttpServer *server;
int id;
pthread_t thread;
} HttpServerWorkerThreadArgs;
static HttpServerContext *
HttpServerContextCreate(HttpRequestMethod requestMethod,
char *requestPath, HashMap * requestParams, Stream * stream)
{
HttpServerContext *c;
c = Malloc(sizeof(HttpServerContext));
if (!c)
{
return NULL;
}
c->responseHeaders = HashMapCreate();
if (!c->responseHeaders)
{
Free(c->requestHeaders);
Free(c);
return NULL;
}
c->requestMethod = requestMethod;
c->requestPath = requestPath;
c->requestParams = requestParams;
c->stream = stream;
c->responseStatus = HTTP_OK;
return c;
}
static void
HttpServerContextFree(HttpServerContext * c)
{
char *key;
void *val;
if (!c)
{
return;
}
while (HashMapIterate(c->requestHeaders, &key, &val))
{
Free(val);
}
HashMapFree(c->requestHeaders);
while (HashMapIterate(c->responseHeaders, &key, &val))
{
/*
* These are generated by code. As such, they may be either
* on the heap, or on the stack, depending on how they were
* added.
*
* Basically, if the memory API knows about a pointer, then
* it can be freed. If it doesn't know about a pointer, skip
* freeing it because it's probably a stack pointer.
*/
if (MemoryInfoGet(val))
{
Free(val);
}
}
HashMapFree(c->responseHeaders);
while (HashMapIterate(c->requestParams, &key, &val))
{
Free(val);
}
HashMapFree(c->requestParams);
Free(c->requestPath);
StreamClose(c->stream);
Free(c);
}
HashMap *
HttpRequestHeaders(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->requestHeaders;
}
HttpRequestMethod
HttpRequestMethodGet(HttpServerContext * c)
{
if (!c)
{
return HTTP_METHOD_UNKNOWN;
}
return c->requestMethod;
}
char *
HttpRequestPath(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->requestPath;
}
HashMap *
HttpRequestParams(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->requestParams;
}
char *
HttpResponseHeader(HttpServerContext * c, char *key, char *val)
{
if (!c)
{
return NULL;
}
return HashMapSet(c->responseHeaders, key, val);
}
void
HttpResponseStatus(HttpServerContext * c, HttpStatus status)
{
if (!c)
{
return;
}
c->responseStatus = status;
}
HttpStatus
HttpResponseStatusGet(HttpServerContext * c)
{
if (!c)
{
return HTTP_STATUS_UNKNOWN;
}
return c->responseStatus;
}
Stream *
HttpServerStream(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->stream;
}
void
HttpSendHeaders(HttpServerContext * c)
{
Stream *fp = c->stream;
char *key;
char *val;
StreamPrintf(fp, "HTTP/1.0 %d %s\n", c->responseStatus, HttpStatusToString(c->responseStatus));
while (HashMapIterate(c->responseHeaders, &key, (void **) &val))
{
StreamPrintf(fp, "%s: %s\n", key, val);
}
StreamPuts(fp, "\n");
}
static Stream *
DequeueConnection(HttpServer * server)
{
Stream *fp;
if (!server)
{
return NULL;
}
pthread_mutex_lock(&server->connQueueMutex);
fp = QueuePop(server->connQueue);
pthread_mutex_unlock(&server->connQueueMutex);
return fp;
}
HttpServer *
HttpServerCreate(HttpServerConfig * config)
{
HttpServer *server;
struct sockaddr_in sa;
if (!config)
{
return NULL;
}
if (!config->handler)
{
return NULL;
}
#ifndef TLS_IMPL
if (config->flags & HTTP_FLAG_TLS)
{
return NULL;
}
#endif
server = Malloc(sizeof(HttpServer));
if (!server)
{
goto error;
}
memset(server, 0, sizeof(HttpServer));
server->config = *config;
server->config.tlsCert = StrDuplicate(config->tlsCert);
server->config.tlsKey = StrDuplicate(config->tlsKey);
server->threadPool = ArrayCreate();
if (!server->threadPool)
{
goto error;
}
server->connQueue = QueueCreate(config->maxConnections);
if (!server->connQueue)
{
goto error;
}
if (pthread_mutex_init(&server->connQueueMutex, NULL) != 0)
{
goto error;
}
server->sd = socket(AF_INET, SOCK_STREAM, 0);
if (server->sd < 0)
{
goto error;
}
if (fcntl(server->sd, F_SETFL, O_NONBLOCK) == -1)
{
goto error;
}
if (setsockopt(server->sd, SOL_SOCKET, SO_REUSEADDR, &ENABLE, sizeof(int)) < 0)
{
goto error;
}
#ifdef SO_REUSEPORT
if (setsockopt(server->sd, SOL_SOCKET, SO_REUSEPORT, &ENABLE, sizeof(int)) < 0)
{
goto error;
}
#endif
memset(&sa, 0, sizeof(struct sockaddr_in));
sa.sin_family = AF_INET;
sa.sin_port = htons(config->port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(server->sd, (struct sockaddr *) & sa, sizeof(sa)) < 0)
{
goto error;
}
if (listen(server->sd, config->maxConnections) < 0)
{
goto error;
}
server->stop = 0;
server->isRunning = 0;
return server;
error:
if (server)
{
if (server->connQueue)
{
QueueFree(server->connQueue);
}
pthread_mutex_destroy(&server->connQueueMutex);
if (server->threadPool)
{
ArrayFree(server->threadPool);
}
if (server->sd)
{
close(server->sd);
}
Free(server);
}
return NULL;
}
HttpServerConfig *
HttpServerConfigGet(HttpServer * server)
{
if (!server)
{
return NULL;
}
return &server->config;
}
void
HttpServerFree(HttpServer * server)
{
if (!server)
{
return;
}
close(server->sd);
QueueFree(server->connQueue);
pthread_mutex_destroy(&server->connQueueMutex);
ArrayFree(server->threadPool);
Free(server->config.tlsCert);
Free(server->config.tlsKey);
Free(server);
}
static void *
HttpServerWorkerThread(void *args)
{
HttpServerWorkerThreadArgs *wArgs = (HttpServerWorkerThreadArgs *) args;
HttpServer *server = wArgs->server;
while (!server->stop)
{
Stream *fp;
HttpServerContext *context;
char *line = NULL;
size_t lineSize = 0;
ssize_t lineLen = 0;
char *requestMethodPtr;
char *pathPtr;
char *requestPath;
char *requestProtocol;
HashMap *requestParams;
ssize_t requestPathLen;
ssize_t i = 0;
HttpRequestMethod requestMethod;
long firstRead;
fp = DequeueConnection(server);
if (!fp)
{
/* Block for 1 millisecond before continuing so we don't
* murder the CPU if the queue is empty. */
UtilSleepMillis(1);
continue;
}
/* Get the first line of the request.
*
* Every once in a while, we're too fast for the client. When this
* happens, UtilGetLine() sets errno to EAGAIN. If we get
* EAGAIN, then clear the error on the stream and try again
* after a few ms. This is typically more than enough time for
* the client to send data. */
firstRead = UtilServerTs();
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) == -1
&& errno == EAGAIN)
{
StreamClearError(fp);
/* If the server is stopped, or it's been a while, just
* give up so we aren't wasting a thread on this client. */
if (server->stop || (UtilServerTs() - firstRead) > 1000 * 30)
{
goto finish;
}
UtilSleepMillis(5);
}
if (lineLen == -1)
{
goto bad_request;
}
requestMethodPtr = line;
for (i = 0; i < lineLen; i++)
{
if (line[i] == ' ')
{
line[i] = '\0';
break;
}
}
if (i == lineLen)
{
goto bad_request;
}
requestMethod = HttpRequestMethodFromString(requestMethodPtr);
if (requestMethod == HTTP_METHOD_UNKNOWN)
{
goto bad_request;
}
pathPtr = line + i + 1;
for (i = 0; i < (line + lineLen) - pathPtr; i++)
{
if (pathPtr[i] == ' ')
{
pathPtr[i] = '\0';
break;
}
}
requestPathLen = i;
requestPath = Malloc(((requestPathLen + 1) * sizeof(char)));
strncpy(requestPath, pathPtr, requestPathLen + 1);
requestProtocol = &pathPtr[i + 1];
line[lineLen - 2] = '\0'; /* Get rid of \r and \n */
if (!StrEquals(requestProtocol, "HTTP/1.1") && !StrEquals(requestProtocol, "HTTP/1.0"))
{
Free(requestPath);
goto bad_request;
}
/* Find request params */
for (i = 0; i < requestPathLen; i++)
{
if (requestPath[i] == '?')
{
break;
}
}
requestPath[i] = '\0';
requestParams = (i == requestPathLen) ? NULL : HttpParamDecode(requestPath + i + 1);
context = HttpServerContextCreate(requestMethod, requestPath, requestParams, fp);
if (!context)
{
Free(requestPath);
goto internal_error;
}
context->requestHeaders = HttpParseHeaders(fp);
if (!context->requestHeaders)
{
goto internal_error;
}
server->config.handler(context, server->config.handlerArgs);
HttpServerContextFree(context);
fp = NULL; /* The above call will close this
* Stream */
goto finish;
internal_error:
StreamPuts(fp, "HTTP/1.0 500 Internal Server Error\n");
StreamPuts(fp, "Connection: close\n");
goto finish;
bad_request:
StreamPuts(fp, "HTTP/1.0 400 Bad Request\n");
StreamPuts(fp, "Connection: close\n");
goto finish;
finish:
Free(line);
if (fp)
{
StreamClose(fp);
}
}
return NULL;
}
static void *
HttpServerEventThread(void *args)
{
HttpServer *server = (HttpServer *) args;
struct pollfd pollFds[1];
Stream *fp;
size_t i;
server->isRunning = 1;
server->stop = 0;
pollFds[0].fd = server->sd;
pollFds[0].events = POLLIN;
for (i = 0; i < server->config.threads; i++)
{
HttpServerWorkerThreadArgs *workerThread = Malloc(sizeof(HttpServerWorkerThreadArgs));
if (!workerThread)
{
/* TODO: Make the event thread return an error to the main
* thread */
return NULL;
}
workerThread->server = server;
workerThread->id = i;
if (pthread_create(&workerThread->thread, NULL, HttpServerWorkerThread, workerThread) != 0)
{
/* TODO: Make the event thread return an error to the main
* thread */
return NULL;
}
ArrayAdd(server->threadPool, workerThread);
}
while (!server->stop)
{
struct sockaddr_storage addr;
socklen_t addrLen = sizeof(addr);
int connFd;
int pollResult;
pollResult = poll(pollFds, 1, 500);
if (pollResult < 0)
{
/* The poll either timed out, or was interrupted. */
continue;
}
pthread_mutex_lock(&server->connQueueMutex);
/* Don't even accept connections if the queue is full. */
if (!QueueFull(server->connQueue))
{
connFd = accept(server->sd, (struct sockaddr *) & addr, &addrLen);
if (connFd < 0)
{
pthread_mutex_unlock(&server->connQueueMutex);
continue;
}
#ifdef TLS_IMPL
if (server->config.flags & HTTP_FLAG_TLS)
{
fp = TlsServerStream(connFd, server->config.tlsCert, server->config.tlsKey);
}
else
{
fp = StreamFd(connFd);
}
#else
fp = StreamFd(connFd);
#endif
if (!fp)
{
pthread_mutex_unlock(&server->connQueueMutex);
close(connFd);
continue;
}
QueuePush(server->connQueue, fp);
}
pthread_mutex_unlock(&server->connQueueMutex);
}
for (i = 0; i < server->config.threads; i++)
{
HttpServerWorkerThreadArgs *workerThread = ArrayGet(server->threadPool, i);
pthread_join(workerThread->thread, NULL);
Free(workerThread);
}
while ((fp = DequeueConnection(server)))
{
StreamClose(fp);
}
server->isRunning = 0;
return NULL;
}
int
HttpServerStart(HttpServer * server)
{
if (!server)
{
return 0;
}
if (server->isRunning)
{
return 1;
}
if (pthread_create(&server->socketThread, NULL, HttpServerEventThread, server) != 0)
{
return 0;
}
return 1;
}
void
HttpServerJoin(HttpServer * server)
{
if (!server)
{
return;
}
pthread_join(server->socketThread, NULL);
}
void
HttpServerStop(HttpServer * server)
{
if (!server)
{
return;
}
server->stop = 1;
}

212
src/Io.c Normal file
View file

@ -0,0 +1,212 @@
/*
* 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 <Io.h>
#include <Memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
struct Io
{
IoFunctions io;
void *cookie;
};
Io *
IoCreate(void *cookie, IoFunctions funcs)
{
Io *io;
/* Must have at least read or write */
if (!funcs.read && !funcs.write)
{
return NULL;
}
io = Malloc(sizeof(Io));
if (!io)
{
return NULL;
}
io->cookie = cookie;
io->io.read = funcs.read;
io->io.write = funcs.write;
io->io.seek = funcs.seek;
io->io.close = funcs.close;
return io;
}
ssize_t
IoRead(Io * io, void *buf, size_t nBytes)
{
if (!io || !io->io.read)
{
errno = EBADF;
return -1;
}
return io->io.read(io->cookie, buf, nBytes);
}
ssize_t
IoWrite(Io * io, void *buf, size_t nBytes)
{
if (!io || !io->io.write)
{
errno = EBADF;
return -1;
}
return io->io.write(io->cookie, buf, nBytes);
}
off_t
IoSeek(Io * io, off_t offset, int whence)
{
if (!io)
{
errno = EBADF;
return -1;
}
if (!io->io.seek)
{
errno = EINVAL;
return -1;
}
return io->io.seek(io->cookie, offset, whence);
}
int
IoClose(Io * io)
{
int ret;
if (!io)
{
errno = EBADF;
return -1;
}
if (io->io.close)
{
ret = io->io.close(io->cookie);
}
else
{
ret = 0;
}
Free(io);
return ret;
}
int
IoVprintf(Io * io, const char *fmt, va_list ap)
{
char *buf = NULL;
size_t bufSize = 0;
FILE *fp;
int ret;
if (!io || !fmt)
{
return -1;
}
fp = open_memstream(&buf, &bufSize);
if (!fp)
{
return -1;
}
ret = vfprintf(fp, fmt, ap);
fclose(fp);
if (ret >= 0)
{
ret = IoWrite(io, buf, bufSize);
}
free(buf); /* Allocated by stdlib, not Memory
* API */
return ret;
}
int
IoPrintf(Io * io, const char *fmt,...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = IoVprintf(io, fmt, ap);
va_end(ap);
return ret;
}
ssize_t
IoCopy(Io * in, Io * out)
{
ssize_t nBytes = 0;
char buf[IO_BUFFER];
ssize_t rRes;
ssize_t wRes;
if (!in || !out)
{
errno = EBADF;
return -1;
}
while ((rRes = IoRead(in, &buf, IO_BUFFER)) != 0)
{
if (rRes == -1)
{
return -1;
}
wRes = IoWrite(out, &buf, rRes);
if (wRes == -1)
{
return -1;
}
nBytes += wRes;
}
return nBytes;
}

95
src/Io/IoFd.c Normal file
View file

@ -0,0 +1,95 @@
/*
* 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 <Io.h>
#include <Memory.h>
#include <fcntl.h>
static ssize_t
IoReadFd(void *cookie, void *buf, size_t nBytes)
{
int fd = *((int *) cookie);
return read(fd, buf, nBytes);
}
static ssize_t
IoWriteFd(void *cookie, void *buf, size_t nBytes)
{
int fd = *((int *) cookie);
return write(fd, buf, nBytes);
}
static off_t
IoSeekFd(void *cookie, off_t offset, int whence)
{
int fd = *((int *) cookie);
return lseek(fd, offset, whence);
}
static int
IoCloseFd(void *cookie)
{
int fd = *((int *) cookie);
Free(cookie);
return close(fd);
}
Io *
IoFd(int fd)
{
int *cookie = Malloc(sizeof(int));
IoFunctions f;
if (!cookie)
{
return NULL;
}
*cookie = fd;
f.read = IoReadFd;
f.write = IoWriteFd;
f.seek = IoSeekFd;
f.close = IoCloseFd;
return IoCreate(cookie, f);
}
Io *
IoOpen(const char *path, int flags, mode_t mode)
{
int fd = open(path, flags, mode);
if (fd == -1)
{
return NULL;
}
return IoFd(fd);
}

91
src/Io/IoFile.c Normal file
View file

@ -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.
*/
#include <Io.h>
#include <stdio.h>
static ssize_t
IoReadFile(void *cookie, void *buf, size_t nBytes)
{
FILE *fp = cookie;
return fread(buf, 1, nBytes, fp);
}
static ssize_t
IoWriteFile(void *cookie, void *buf, size_t nBytes)
{
FILE *fp = cookie;
size_t res = fwrite(buf, 1, nBytes, fp);
/*
* fwrite() may be buffered on some platforms, but at this low level,
* it should not be; buffering happens in Stream, not Io.
*/
fflush(fp);
return res;
}
static off_t
IoSeekFile(void *cookie, off_t offset, int whence)
{
FILE *fp = cookie;
off_t ret = fseeko(fp, offset, whence);
if (ret > -1)
{
return ftello(fp);
}
else
{
return ret;
}
}
static int
IoCloseFile(void *cookie)
{
FILE *fp = cookie;
return fclose(fp);
}
Io *
IoFile(FILE * fp)
{
IoFunctions f;
if (!fp)
{
return NULL;
}
f.read = IoReadFile;
f.write = IoWriteFile;
f.seek = IoSeekFile;
f.close = IoCloseFile;
return IoCreate(fp, f);
}

1384
src/Json.c Normal file

File diff suppressed because it is too large Load diff

388
src/Log.c Normal file
View file

@ -0,0 +1,388 @@
/*
* 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 <Log.h>
#include <Memory.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <ctype.h>
#include <stdarg.h>
#include <pthread.h>
#define LOG_TSBUFFER 64
struct LogConfig
{
int level;
size_t indent;
Stream *out;
int flags;
char *tsFmt;
pthread_mutex_t lock;
};
LogConfig *globalConfig = NULL;
LogConfig *
LogConfigCreate(void)
{
LogConfig *config;
config = Malloc(sizeof(LogConfig));
if (!config)
{
return NULL;
}
memset(config, 0, sizeof(LogConfig));
LogConfigLevelSet(config, LOG_INFO);
LogConfigIndentSet(config, 0);
LogConfigOutputSet(config, NULL); /* Will set to stdout */
LogConfigFlagSet(config, LOG_FLAG_COLOR);
LogConfigTimeStampFormatSet(config, "%y-%m-%d %H:%M:%S");
return config;
}
LogConfig *
LogConfigGlobal(void)
{
if (!globalConfig)
{
globalConfig = LogConfigCreate();
}
return globalConfig;
}
void
LogConfigFlagClear(LogConfig * config, int flags)
{
if (!config)
{
return;
}
config->flags &= ~flags;
}
static int
LogConfigFlagGet(LogConfig * config, int flags)
{
if (!config)
{
return 0;
}
return config->flags & flags;
}
void
LogConfigFlagSet(LogConfig * config, int flags)
{
if (!config)
{
return;
}
config->flags |= flags;
}
void
LogConfigFree(LogConfig * config)
{
if (!config)
{
return;
}
Free(config);
if (config == globalConfig)
{
globalConfig = NULL;
}
}
void
LogConfigIndent(LogConfig * config)
{
if (config)
{
config->indent += 2;
}
}
void
LogConfigIndentSet(LogConfig * config, size_t indent)
{
if (!config)
{
return;
}
config->indent = indent;
}
int
LogConfigLevelGet(LogConfig * config)
{
if (!config)
{
return -1;
}
return config->level;
}
void
LogConfigLevelSet(LogConfig * config, int level)
{
if (!config)
{
return;
}
switch (level)
{
case LOG_ERR:
case LOG_WARNING:
case LOG_INFO:
case LOG_DEBUG:
config->level = level;
default:
break;
}
}
void
LogConfigOutputSet(LogConfig * config, Stream * out)
{
if (!config)
{
return;
}
if (out)
{
config->out = out;
}
else
{
config->out = StreamStdout();
}
}
void
LogConfigTimeStampFormatSet(LogConfig * config, char *tsFmt)
{
if (config)
{
config->tsFmt = tsFmt;
}
}
void
LogConfigUnindent(LogConfig * config)
{
if (config && config->indent >= 2)
{
config->indent -= 2;
}
}
void
Logv(LogConfig * config, int level, const char *msg, va_list argp)
{
size_t i;
int doColor;
char indicator;
/*
* Only proceed if we have a config and its log level is set to a
* value that permits us to log. This is as close as we can get
* to a no-op function if we aren't logging anything, without doing
* some crazy macro magic.
*/
if (!config || level > config->level)
{
return;
}
/* Misconfiguration */
if (!config->out)
{
return;
}
pthread_mutex_lock(&config->lock);
if (LogConfigFlagGet(config, LOG_FLAG_SYSLOG))
{
/* No further print logic is needed; syslog will handle it all
* for us. */
vsyslog(level, msg, argp);
pthread_mutex_unlock(&config->lock);
return;
}
doColor = LogConfigFlagGet(config, LOG_FLAG_COLOR)
&& isatty(StreamFileno(config->out));
if (doColor)
{
char *ansi;
switch (level)
{
case LOG_EMERG:
case LOG_ALERT:
case LOG_CRIT:
case LOG_ERR:
/* Bold Red */
ansi = "\033[1;31m";
break;
case LOG_WARNING:
/* Bold Yellow */
ansi = "\033[1;33m";
break;
case LOG_NOTICE:
/* Bold Magenta */
ansi = "\033[1;35m";
break;
case LOG_INFO:
/* Bold Green */
ansi = "\033[1;32m";
break;
case LOG_DEBUG:
/* Bold Blue */
ansi = "\033[1;34m";
break;
default:
ansi = "";
break;
}
StreamPuts(config->out, ansi);
}
StreamPutc(config->out, '[');
if (config->tsFmt)
{
time_t timer = time(NULL);
struct tm *timeInfo = localtime(&timer);
char tsBuffer[LOG_TSBUFFER];
int tsLength = strftime(tsBuffer, LOG_TSBUFFER, config->tsFmt,
timeInfo);
if (tsLength)
{
StreamPuts(config->out, tsBuffer);
if (!isspace((unsigned char) tsBuffer[tsLength - 1]))
{
StreamPutc(config->out, ' ');
}
}
}
switch (level)
{
case LOG_EMERG:
indicator = '#';
break;
case LOG_ALERT:
indicator = '@';
break;
case LOG_CRIT:
indicator = 'X';
break;
case LOG_ERR:
indicator = 'x';
break;
case LOG_WARNING:
indicator = '!';
break;
case LOG_NOTICE:
indicator = '~';
break;
case LOG_INFO:
indicator = '>';
break;
case LOG_DEBUG:
indicator = '*';
break;
default:
indicator = '?';
break;
}
StreamPrintf(config->out, "%c]", indicator);
if (doColor)
{
/* ANSI Reset */
StreamPuts(config->out, "\033[0m");
}
StreamPutc(config->out, ' ');
for (i = 0; i < config->indent; i++)
{
StreamPutc(config->out, ' ');
}
StreamVprintf(config->out, msg, argp);
StreamPutc(config->out, '\n');
StreamFlush(config->out);
pthread_mutex_unlock(&config->lock);
}
void
LogTo(LogConfig * config, int level, const char *fmt,...)
{
va_list argp;
va_start(argp, fmt);
Logv(config, level, fmt, argp);
va_end(argp);
}
extern void
Log(int level, const char *fmt,...)
{
va_list argp;
va_start(argp, fmt);
Logv(LogConfigGlobal(), level, fmt, argp);
va_end(argp);
}

493
src/Memory.c Normal file
View file

@ -0,0 +1,493 @@
/*
* 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 <Memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <pthread.h>
#ifndef MEMORY_TABLE_CHUNK
#define MEMORY_TABLE_CHUNK 256
#endif
#ifndef MEMORY_HEXDUMP_WIDTH
#define MEMORY_HEXDUMP_WIDTH 16
#endif
struct MemoryInfo
{
size_t size;
const char *file;
int line;
void *pointer;
};
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static void (*hook) (MemoryAction, MemoryInfo *, void *) = NULL;
static void *hookArgs = NULL;
static MemoryInfo **allocations = NULL;
static size_t allocationsSize = 0;
static size_t allocationsLen = 0;
static size_t
MemoryHash(void *p)
{
return (((size_t) p) >> 2 * 7) % allocationsSize;
}
static int
MemoryInsert(MemoryInfo * a)
{
size_t hash;
if (!allocations)
{
allocationsSize = MEMORY_TABLE_CHUNK;
allocations = calloc(allocationsSize, sizeof(void *));
if (!allocations)
{
return 0;
}
}
/* 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;
allocationsLen++;
return 1;
}
static void
MemoryDelete(MemoryInfo * a)
{
size_t hash = MemoryHash(a->pointer);
size_t count = 0;
while (count <= allocationsSize)
{
if (allocations[hash] && allocations[hash] == a)
{
allocations[hash] = NULL;
allocationsLen--;
return;
}
else
{
hash = (hash + 1) % allocationsSize;
count++;
}
}
}
void *
MemoryAllocate(size_t size, const char *file, int line)
{
void *p;
MemoryInfo *a;
pthread_mutex_lock(&lock);
p = malloc(size);
if (!p)
{
pthread_mutex_unlock(&lock);
return NULL;
}
a = malloc(sizeof(MemoryInfo));
if (!a)
{
free(p);
pthread_mutex_unlock(&lock);
return NULL;
}
a->size = size;
a->file = file;
a->line = line;
a->pointer = p;
if (!MemoryInsert(a))
{
free(a);
free(p);
pthread_mutex_unlock(&lock);
return NULL;
}
if (hook)
{
hook(MEMORY_ALLOCATE, a, hookArgs);
}
pthread_mutex_unlock(&lock);
return p;
}
void *
MemoryReallocate(void *p, size_t size, const char *file, int line)
{
MemoryInfo *a;
void *new = NULL;
if (!p)
{
return MemoryAllocate(size, file, line);
}
a = MemoryInfoGet(p);
if (a)
{
pthread_mutex_lock(&lock);
new = realloc(a->pointer, size);
if (new)
{
MemoryDelete(a);
a->size = size;
a->file = file;
a->line = line;
a->pointer = new;
MemoryInsert(a);
if (hook)
{
hook(MEMORY_REALLOCATE, a, hookArgs);
}
}
pthread_mutex_unlock(&lock);
}
else if (hook)
{
a = malloc(sizeof(MemoryInfo));
if (a)
{
a->size = 0;
a->file = file;
a->line = line;
a->pointer = p;
hook(MEMORY_BAD_POINTER, a, hookArgs);
free(a);
}
}
return new;
}
void
MemoryFree(void *p, const char *file, int line)
{
MemoryInfo *a;
if (!p)
{
return;
}
a = MemoryInfoGet(p);
if (a)
{
pthread_mutex_lock(&lock);
if (hook)
{
a->file = file;
a->line = line;
hook(MEMORY_FREE, a, hookArgs);
}
MemoryDelete(a);
free(a->pointer);
free(a);
pthread_mutex_unlock(&lock);
}
else if (hook)
{
a = malloc(sizeof(MemoryInfo));
if (a)
{
a->file = file;
a->line = line;
a->size = 0;
a->pointer = p;
hook(MEMORY_BAD_POINTER, a, hookArgs);
free(a);
}
}
}
size_t
MemoryAllocated(void)
{
size_t i;
size_t total = 0;
pthread_mutex_lock(&lock);
for (i = 0; i < allocationsSize; i++)
{
if (allocations[i])
{
total += allocations[i]->size;
}
}
pthread_mutex_unlock(&lock);
return total;
}
void
MemoryFreeAll(void)
{
size_t i;
pthread_mutex_lock(&lock);
for (i = 0; i < allocationsSize; i++)
{
if (allocations[i])
{
free(allocations[i]->pointer);
free(allocations[i]);
}
}
free(allocations);
allocations = NULL;
allocationsSize = 0;
allocationsLen = 0;
pthread_mutex_unlock(&lock);
}
MemoryInfo *
MemoryInfoGet(void *p)
{
size_t hash, count;
pthread_mutex_lock(&lock);
hash = MemoryHash(p);
count = 0;
while (count <= allocationsSize)
{
if (!allocations[hash] || allocations[hash]->pointer != p)
{
hash = (hash + 1) % allocationsSize;
count++;
}
else
{
pthread_mutex_unlock(&lock);
return allocations[hash];
}
}
pthread_mutex_unlock(&lock);
return NULL;
}
size_t
MemoryInfoGetSize(MemoryInfo * a)
{
if (!a)
{
return 0;
}
return a->size;
}
const char *
MemoryInfoGetFile(MemoryInfo * a)
{
if (!a)
{
return NULL;
}
return a->file;
}
int
MemoryInfoGetLine(MemoryInfo * a)
{
if (!a)
{
return -1;
}
return a->line;
}
void *
MemoryInfoGetPointer(MemoryInfo * a)
{
if (!a)
{
return NULL;
}
return a->pointer;
}
void
MemoryIterate(void (*iterFunc) (MemoryInfo *, void *), void *args)
{
size_t i;
pthread_mutex_lock(&lock);
for (i = 0; i < allocationsSize; i++)
{
if (allocations[i])
{
iterFunc(allocations[i], args);
}
}
pthread_mutex_unlock(&lock);
}
void
MemoryHook(void (*memHook) (MemoryAction, MemoryInfo *, void *), void *args)
{
pthread_mutex_lock(&lock);
hook = memHook;
hookArgs = args;
pthread_mutex_unlock(&lock);
}
void
MemoryHexDump(MemoryInfo * info, void (*printFunc) (size_t, char *, char *, void *), void *args)
{
char hexBuf[(MEMORY_HEXDUMP_WIDTH * 2) + MEMORY_HEXDUMP_WIDTH + 1];
char asciiBuf[MEMORY_HEXDUMP_WIDTH + 1];
size_t pI = 0;
size_t hI = 0;
size_t aI = 0;
const unsigned char *pc;
if (!info || !printFunc)
{
return;
}
pc = MemoryInfoGetPointer(info);
for (pI = 0; pI < MemoryInfoGetSize(info); pI++)
{
if (pI > 0 && pI % MEMORY_HEXDUMP_WIDTH == 0)
{
hexBuf[hI - 1] = '\0';
asciiBuf[aI] = '\0';
printFunc(pI - MEMORY_HEXDUMP_WIDTH, hexBuf, asciiBuf, args);
snprintf(hexBuf, 4, "%02x ", pc[pI]);
hI = 3;
asciiBuf[0] = isprint(pc[pI]) ? pc[pI] : '.';
asciiBuf[1] = '\0';
aI = 1;
}
else
{
asciiBuf[aI] = isprint(pc[pI]) ? pc[pI] : '.';
aI++;
snprintf(hexBuf + hI, 4, "%02x ", pc[pI]);
hI += 3;
}
}
hexBuf[hI] = '\0';
hI--;
while (hI < sizeof(hexBuf) - 2)
{
hexBuf[hI] = ' ';
hI++;
}
while (aI < sizeof(asciiBuf) - 1)
{
asciiBuf[aI] = ' ';
aI++;
}
hexBuf[hI] = '\0';
asciiBuf[aI] = '\0';
printFunc(pI - ((pI % MEMORY_HEXDUMP_WIDTH) ?
(pI % MEMORY_HEXDUMP_WIDTH) : MEMORY_HEXDUMP_WIDTH),
hexBuf, asciiBuf, args);
printFunc(pI, NULL, NULL, args);
}

176
src/Queue.c Normal file
View file

@ -0,0 +1,176 @@
/*
* 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 <Queue.h>
#include <Memory.h>
struct Queue
{
void **items;
size_t size;
size_t front;
size_t rear;
};
Queue *
QueueCreate(size_t size)
{
Queue *q;
if (!size)
{
/* Can't have a queue of length zero */
return NULL;
}
q = Malloc(sizeof(Queue));
if (!q)
{
return NULL;
}
q->items = Malloc(size * sizeof(void *));
if (!q->items)
{
Free(q);
return NULL;
}
q->size = size;
q->front = size + 1;
q->rear = size + 1;
return q;
}
void
QueueFree(Queue * q)
{
if (q)
{
Free(q->items);
}
Free(q);
}
int
QueueFull(Queue * q)
{
if (!q)
{
return 0;
}
return ((q->front == q->rear + 1) || (q->front == 0 && q->rear == q->size - 1));
}
int
QueueEmpty(Queue * q)
{
if (!q)
{
return 0;
}
return q->front == q->size + 1;
}
int
QueuePush(Queue * q, void *element)
{
if (!q || !element)
{
return 0;
}
if (QueueFull(q))
{
return 0;
}
if (q->front == q->size + 1)
{
q->front = 0;
}
if (q->rear == q->size + 1)
{
q->rear = 0;
}
else
{
q->rear = (q->rear + 1) % q->size;
}
q->items[q->rear] = element;
return 1;
}
void *
QueuePop(Queue * q)
{
void *element;
if (!q)
{
return NULL;
}
if (QueueEmpty(q))
{
return NULL;
}
element = q->items[q->front];
if (q->front == q->rear)
{
q->front = q->size + 1;
q->rear = q->size + 1;
}
else
{
q->front = (q->front + 1) % q->size;
}
return element;
}
void *
QueuePeek(Queue * q)
{
if (!q)
{
return NULL;
}
if (QueueEmpty(q))
{
return NULL;
}
return q->items[q->front];
}

172
src/Rand.c Normal file
View file

@ -0,0 +1,172 @@
/*
* 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 <Rand.h>
#include <Int.h>
#include <Util.h>
#include <Memory.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define RAND_STATE_VECTOR_LENGTH 624
#define RAND_STATE_VECTOR_M 397
#define RAND_UPPER_MASK 0x80000000
#define RAND_LOWER_MASK 0x7FFFFFFF
#define RAND_TEMPER_B 0x9D2C5680
#define RAND_TEMPER_C 0xEFC60000
typedef struct RandState
{
UInt32 mt[RAND_STATE_VECTOR_LENGTH];
int index;
} RandState;
static void
RandSeed(RandState * state, UInt32 seed)
{
state->mt[0] = seed & 0xFFFFFFFF;
for (state->index = 1; state->index < RAND_STATE_VECTOR_LENGTH; state->index++)
{
state->mt[state->index] = (6069 * state->mt[state->index - 1]) & 0xFFFFFFFF;
}
}
static UInt32
RandGenerate(RandState * state)
{
static const UInt32 mag[2] = {0x0, 0x9908B0DF};
UInt32 result;
if (state->index >= RAND_STATE_VECTOR_LENGTH || state->index < 0)
{
int kk;
if (state->index >= RAND_STATE_VECTOR_LENGTH + 1 || state->index < 0)
{
RandSeed(state, 4357);
}
for (kk = 0; kk < RAND_STATE_VECTOR_LENGTH - RAND_STATE_VECTOR_M; kk++)
{
result = (state->mt[kk] & RAND_UPPER_MASK) | (state->mt[kk + 1] & RAND_LOWER_MASK);
state->mt[kk] = state->mt[kk + RAND_STATE_VECTOR_M] ^ (result >> 1) ^ mag[result & 0x1];
}
for (; kk < RAND_STATE_VECTOR_LENGTH - 1; kk++)
{
result = (state->mt[kk] & RAND_UPPER_MASK) | (state->mt[kk + 1] & RAND_LOWER_MASK);
state->mt[kk] = state->mt[kk + (RAND_STATE_VECTOR_M - RAND_STATE_VECTOR_LENGTH)] ^ (result >> 1) ^ mag[result & 0x1];
}
result = (state->mt[RAND_STATE_VECTOR_LENGTH - 1] & RAND_UPPER_MASK) | (state->mt[0] & RAND_LOWER_MASK);
state->mt[RAND_STATE_VECTOR_LENGTH - 1] = state->mt[RAND_STATE_VECTOR_M - 1] ^ (result >> 1) ^ mag[result & 0x1];
state->index = 0;
}
result = state->mt[state->index++];
result ^= (result >> 11);
result ^= (result << 7) & RAND_TEMPER_B;
result ^= (result << 15) & RAND_TEMPER_C;
result ^= (result >> 18);
return result;
}
static void
RandDestructor(void *p)
{
Free(p);
}
/* Generate random numbers using rejection sampling. The basic idea is
* to "reroll" if a number happens to be outside the range. However
* this could be extremely inefficient.
*
* Another idea would just be to "reroll" if the generated number ends up
* in the previously "biased" range, and THEN do a modulo.
*
* This would be far more efficient for small values of max, and fixes the
* bias issue. */
/* This algorithm therefore computes N random numbers generally in O(N)
* time, while being less biased. */
void
RandIntN(int *buf, size_t size, unsigned int max)
{
static pthread_key_t stateKey;
static int createdKey = 0;
/* Limit the range to banish all previously biased results */
const int allowed = RAND_MAX - RAND_MAX % max;
RandState *state;
int tmp;
size_t i;
if (!createdKey)
{
pthread_key_create(&stateKey, RandDestructor);
createdKey = 1;
}
state = pthread_getspecific(stateKey);
if (!state)
{
/* Generate a seed from the system time, PID, and TID */
UInt32 seed = UtilServerTs() ^ getpid() ^ (unsigned long) pthread_self();
state = Malloc(sizeof(RandState));
RandSeed(state, seed);
pthread_setspecific(stateKey, state);
}
/* Generate {size} random numbers. */
for (i = 0; i < size; i++)
{
/* Most of the time, this will take about 1 loop */
do
{
tmp = RandGenerate(state);
} while (tmp > allowed);
buf[i] = tmp % max;
}
}
/* Generate just 1 random number */
int
RandInt(unsigned int max)
{
int val = 0;
RandIntN(&val, 1, max);
return val;
}

162
src/RtStub.c Normal file
View file

@ -0,0 +1,162 @@
/*
* 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 <Runtime.h>
#include <Array.h>
#include <HashMap.h>
#include <Stream.h>
#include <Log.h>
#include <Memory.h>
#include <Str.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
/* Specified by POSIX to contain environment variables */
extern char **environ;
/* The linking program is expected to provide Main */
extern int Main(Array *, HashMap *);
typedef struct MainArgs
{
Array *args;
HashMap *env;
} MainArgs;
static void *
MainThread(void *argp)
{
long ret;
MainArgs *args = argp;
args = argp;
ret = Main(args->args, args->env);
return (void *) ret;
}
int
main(int argc, char **argv)
{
pthread_t mainThread;
size_t i;
int ret;
char *key;
char *val;
char **envp;
MainArgs args;
args.args = NULL;
args.env = NULL;
args.args = ArrayCreate();
if (!args.args)
{
Log(LOG_ERR, "Bootstrap error: Unable to allocate memory for arguments.");
ret = EXIT_FAILURE;
goto finish;
}
args.env = HashMapCreate();
if (!args.env)
{
Log(LOG_ERR, "Bootstrap error: Unable to allocate memory for environment.");
ret = EXIT_FAILURE;
goto finish;
}
for (i = 0; i < (size_t) argc; i++)
{
ArrayAdd(args.args, StrDuplicate(argv[i]));
}
envp = environ;
while (*envp)
{
size_t valInd;
/* It is unclear whether or not envp strings are writable, so
* we make our own copy to manipulate it */
key = StrDuplicate(*envp);
valInd = strcspn(key, "=");
key[valInd] = '\0';
val = key + valInd + 1;
HashMapSet(args.env, key, StrDuplicate(val));
Free(key);
envp++;
}
if (pthread_create(&mainThread, NULL, MainThread, &args) != 0)
{
Log(LOG_ERR, "Bootstrap error: Unable to create main thread.");
ret = EXIT_FAILURE;
goto finish;
}
if (pthread_join(mainThread, (void **) &ret) != 0)
{
/* Should never happen */
Log(LOG_ERR, "Unable to join main thread.");
ret = EXIT_FAILURE;
goto finish;
}
finish:
if (args.args)
{
for (i = 0; i < ArraySize(args.args); i++)
{
Free(ArrayGet(args.args, i));
}
ArrayFree(args.args);
}
if (args.env)
{
while (HashMapIterate(args.env, &key, (void **) &val))
{
Free(val);
}
HashMapFree(args.env);
}
Log(LOG_DEBUG, "Exitting with code: %d", ret);
LogConfigFree(LogConfigGlobal());
StreamClose(StreamStdout());
StreamClose(StreamStdin());
StreamClose(StreamStderr());
GenerateMemoryReport(argv[0]);
MemoryFreeAll();
return ret;
}

111
src/Runtime.c Normal file
View file

@ -0,0 +1,111 @@
/*
* 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 <Runtime.h>
#include <Array.h>
#include <Stream.h>
#include <Log.h>
#include <Memory.h>
#include <stdlib.h>
#include <time.h>
#include <libgen.h>
static void
HexDump(size_t off, char *hexBuf, char *asciiBuf, void *args)
{
FILE *report = args;
if (hexBuf && asciiBuf)
{
fprintf(report, "%04lx: %s | %s |\n", off, hexBuf, asciiBuf);
}
else
{
fprintf(report, "%04lx\n", off);
}
}
static void
MemoryIterator(MemoryInfo * i, void *args)
{
FILE *report = args;
fprintf(report, "%s:%d: %lu bytes at %p\n",
MemoryInfoGetFile(i), MemoryInfoGetLine(i),
MemoryInfoGetSize(i), MemoryInfoGetPointer(i));
MemoryHexDump(i, HexDump, report);
fprintf(report, "\n");
}
void
GenerateMemoryReport(const char *prog)
{
char reportName[128];
char *namePtr;
/*
* Use C standard IO instead of the Stream or Io APIs, because
* those use the Memory API, and that's exactly what we're trying
* to assess, so using it would generate false positives. None of
* this code should leak memory.
*/
FILE *report;
time_t currentTime;
struct tm *timeInfo;
char tsBuffer[1024];
if (!MemoryAllocated())
{
/* No memory leaked, no need to write the report. This is the
* ideal situation; we only want the report to show up if leaks
* occurred. */
return;
}
snprintf(reportName, sizeof(reportName), "%s-leaked.txt", prog);
/* Make sure the report goes in the current working directory. */
namePtr = basename(reportName);
report = fopen(namePtr, "a");
if (!report)
{
return;
}
currentTime = time(NULL);
timeInfo = localtime(&currentTime);
strftime(tsBuffer, sizeof(tsBuffer), "%c", timeInfo);
fprintf(report, "---------- Memory Report ----------\n");
fprintf(report, "Program: %s\n", prog);
fprintf(report, "Date: %s\n", tsBuffer);
fprintf(report, "Total Bytes: %lu\n", MemoryAllocated());
fprintf(report, "\n");
MemoryIterate(MemoryIterator, report);
fclose(report);
}

238
src/Sha2.c Normal file
View file

@ -0,0 +1,238 @@
/*
* 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 <Sha2.h>
#include <Memory.h>
#include <Int.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#define GET_UINT32(x) \
(((UInt32)(x)[0] << 24) | \
((UInt32)(x)[1] << 16) | \
((UInt32)(x)[2] << 8) | \
((UInt32)(x)[3]))
#define PUT_UINT32(dst, x) { \
(dst)[0] = (x) >> 24; \
(dst)[1] = (x) >> 16; \
(dst)[2] = (x) >> 8; \
(dst)[3] = (x); \
}
#define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
#define S0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ (x >> 3))
#define S1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ (x >> 10))
#define T0(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
#define T1(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
#define CH(a, b, c) (((a) & (b)) ^ ((~(a)) & (c)))
#define MAJ(a, b, c) (((a) & (b)) ^ ((a) & (c)) ^ ((b) & (c)))
#define WW(i) (w[i] = w[i - 16] + S0(w[i - 15]) + w[i - 7] + S1(w[i - 2]))
#define ROUND(a, b, c, d, e, f, g, h, k, w) { \
UInt32 tmp0 = h + T0(e) + CH(e, f, g) + k + w; \
UInt32 tmp1 = T1(a) + MAJ(a, b, c); \
h = tmp0 + tmp1; \
d += tmp0; \
}
typedef struct Sha256Context
{
size_t length;
UInt32 state[8];
size_t bufLen;
unsigned char buffer[64];
} Sha256Context;
static void
Sha256Chunk(Sha256Context * context, unsigned char chunk[64])
{
const UInt32 rk[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
UInt32 w[64];
UInt32 a, b, c, d, e, f, g, h;
int i;
for (i = 0; i < 16; i++)
{
w[i] = GET_UINT32(&chunk[4 * i]);
}
a = context->state[0];
b = context->state[1];
c = context->state[2];
d = context->state[3];
e = context->state[4];
f = context->state[5];
g = context->state[6];
h = context->state[7];
for (i = 0; i < 16; i += 8)
{
ROUND(a, b, c, d, e, f, g, h, rk[i], w[i]);
ROUND(h, a, b, c, d, e, f, g, rk[i + 1], w[i + 1]);
ROUND(g, h, a, b, c, d, e, f, rk[i + 2], w[i + 2]);
ROUND(f, g, h, a, b, c, d, e, rk[i + 3], w[i + 3]);
ROUND(e, f, g, h, a, b, c, d, rk[i + 4], w[i + 4]);
ROUND(d, e, f, g, h, a, b, c, rk[i + 5], w[i + 5]);
ROUND(c, d, e, f, g, h, a, b, rk[i + 6], w[i + 6]);
ROUND(b, c, d, e, f, g, h, a, rk[i + 7], w[i + 7]);
}
for (i = 16; i < 64; i += 8)
{
ROUND(a, b, c, d, e, f, g, h, rk[i], WW(i));
ROUND(h, a, b, c, d, e, f, g, rk[i + 1], WW(i + 1));
ROUND(g, h, a, b, c, d, e, f, rk[i + 2], WW(i + 2));
ROUND(f, g, h, a, b, c, d, e, rk[i + 3], WW(i + 3));
ROUND(e, f, g, h, a, b, c, d, rk[i + 4], WW(i + 4));
ROUND(d, e, f, g, h, a, b, c, rk[i + 5], WW(i + 5));
ROUND(c, d, e, f, g, h, a, b, rk[i + 6], WW(i + 6));
ROUND(b, c, d, e, f, g, h, a, rk[i + 7], WW(i + 7));
}
context->state[0] += a;
context->state[1] += b;
context->state[2] += c;
context->state[3] += d;
context->state[4] += e;
context->state[5] += f;
context->state[6] += g;
context->state[7] += h;
}
static void
Sha256Process(Sha256Context * context, unsigned char *data, size_t length)
{
context->length += length;
if (context->bufLen && context->bufLen + length >= 64)
{
int len = 64 - context->bufLen;
memcpy(context->buffer + context->bufLen, data, len);
Sha256Chunk(context, context->buffer);
data += len;
length -= len;
context->bufLen = 0;
}
while (length >= 64)
{
Sha256Chunk(context, data);
data += 64;
length -= 64;
}
if (length)
{
memcpy(context->buffer + context->bufLen, data, length);
context->bufLen += length;
}
}
char *
Sha256(char *str)
{
Sha256Context context;
size_t i;
unsigned char out[32];
char *outStr;
unsigned char fill[64];
UInt32 fillLen;
unsigned char buf[8];
UInt32 hiLen;
UInt32 loLen;
if (!str)
{
return NULL;
}
outStr = Malloc(65);
if (!outStr)
{
return NULL;
}
context.state[0] = 0x6a09e667;
context.state[1] = 0xbb67ae85;
context.state[2] = 0x3c6ef372;
context.state[3] = 0xa54ff53a;
context.state[4] = 0x510e527f;
context.state[5] = 0x9b05688c;
context.state[6] = 0x1f83d9ab;
context.state[7] = 0x5be0cd19;
context.bufLen = 0;
context.length = 0;
memset(context.buffer, 0, 64);
Sha256Process(&context, (unsigned char *) str, strlen(str));
memset(fill, 0, 64);
fill[0] = 0x80;
fillLen = (context.bufLen < 56) ? 56 - context.bufLen : 120 - context.bufLen;
hiLen = (UInt32) (context.length >> 29);
loLen = (UInt32) (context.length << 3);
PUT_UINT32(&buf[0], hiLen);
PUT_UINT32(&buf[4], loLen);
Sha256Process(&context, fill, fillLen);
Sha256Process(&context, buf, 8);
for (i = 0; i < 8; i++)
{
PUT_UINT32(&out[4 * i], context.state[i]);
}
/* Convert to string */
for (i = 0; i < 32; i++)
{
snprintf(outStr + (2 * i), 3, "%02x", out[i]);
}
return outStr;
}

297
src/Str.c Normal file
View file

@ -0,0 +1,297 @@
/*
* 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 <Str.h>
#include <Memory.h>
#include <Util.h>
#include <Rand.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <pthread.h>
#include <unistd.h>
char *
StrUtf8Encode(unsigned long utf8)
{
char *str;
str = Malloc(5 * sizeof(char));
if (!str)
{
return NULL;
}
if (utf8 <= 0x7F) /* Plain ASCII */
{
str[0] = (char) utf8;
str[1] = '\0';
}
else if (utf8 <= 0x07FF) /* 2-byte */
{
str[0] = (char) (((utf8 >> 6) & 0x1F) | 0xC0);
str[1] = (char) (((utf8 >> 0) & 0x3F) | 0x80);
str[2] = '\0';
}
else if (utf8 <= 0xFFFF) /* 3-byte */
{
str[0] = (char) (((utf8 >> 12) & 0x0F) | 0xE0);
str[1] = (char) (((utf8 >> 6) & 0x3F) | 0x80);
str[2] = (char) (((utf8 >> 0) & 0x3F) | 0x80);
str[3] = '\0';
}
else if (utf8 <= 0x10FFFF) /* 4-byte */
{
str[0] = (char) (((utf8 >> 18) & 0x07) | 0xF0);
str[1] = (char) (((utf8 >> 12) & 0x3F) | 0x80);
str[2] = (char) (((utf8 >> 6) & 0x3F) | 0x80);
str[3] = (char) (((utf8 >> 0) & 0x3F) | 0x80);
str[4] = '\0';
}
else
{
/* Send replacement character */
str[0] = (char) 0xEF;
str[1] = (char) 0xBF;
str[2] = (char) 0xBD;
str[3] = '\0';
}
return str;
}
char *
StrDuplicate(const char *inStr)
{
size_t len;
char *outStr;
if (!inStr)
{
return NULL;
}
len = strlen(inStr);
outStr = Malloc(len + 1); /* For the null terminator */
if (!outStr)
{
return NULL;
}
strncpy(outStr, inStr, len + 1);
return outStr;
}
char *
StrSubstr(const char *inStr, size_t start, size_t end)
{
size_t len;
size_t i;
size_t j;
char *outStr;
if (!inStr)
{
return NULL;
}
if (start >= end)
{
return NULL;
}
len = end - start;
outStr = Malloc(len + 1);
if (!outStr)
{
return NULL;
}
j = 0;
for (i = start; i < end; i++)
{
if (inStr[i] == '\0')
{
break;
}
outStr[j] = inStr[i];
j++;
}
outStr[j] = '\0';
return outStr;
}
char *
StrConcat(size_t nStr,...)
{
va_list argp;
char *str;
char *strp;
size_t strLen = 0;
size_t i;
va_start(argp, nStr);
for (i = 0; i < nStr; i++)
{
char *argStr = va_arg(argp, char *);
if (argStr)
{
strLen += strlen(argStr);
}
}
va_end(argp);
str = Malloc(strLen + 1);
strp = str;
va_start(argp, nStr);
for (i = 0; i < nStr; i++)
{
/* Manually copy chars instead of using strcopy() so we don't
* have to call strlen() on the strings again, and we aren't
* writing useless null chars. */
char *argStr = va_arg(argp, char *);
if (argStr)
{
while (*argStr)
{
*strp = *argStr;
strp++;
argStr++;
}
}
}
va_end(argp);
str[strLen] = '\0';
return str;
}
int
StrBlank(const char *str)
{
int blank = 1;
size_t i = 0;
while (str[i])
{
blank &= isspace((unsigned char) str[i]);
/* No need to continue if we don't have a blank */
if (!blank)
{
break;
}
i++;
}
return blank;
}
char *
StrRandom(size_t len)
{
static const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
char *str;
int *nums;
size_t i;
if (!len)
{
return NULL;
}
str = Malloc(len + 1);
if (!str)
{
return NULL;
}
nums = Malloc(len * sizeof(int));
if (!nums)
{
Free(str);
return NULL;
}
/* TODO: This seems slow. */
RandIntN(nums, len, sizeof(charset) - 1);
for (i = 0; i < len; i++)
{
str[i] = charset[nums[i]];
}
Free(nums);
str[len] = '\0';
return str;
}
char *
StrInt(long i)
{
char *str;
int len;
len = snprintf(NULL, 0, "%ld", i);
str = Malloc(len + 1);
if (!str)
{
return NULL;
}
snprintf(str, len + 1, "%ld", i);
return str;
}
int
StrEquals(const char *str1, const char *str2)
{
/* Both strings are NULL, they're equal */
if (!str1 && !str2)
{
return 1;
}
/* One or the other is NULL, they're not equal */
if (!str1 || !str2)
{
return 0;
}
/* Neither are NULL, do a regular string comparison */
return strcmp(str1, str2) == 0;
}

657
src/Stream.c Normal file
View file

@ -0,0 +1,657 @@
/*
* 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 <Stream.h>
#include <Io.h>
#include <Memory.h>
#include <Util.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#ifndef STREAM_RETRIES
#define STREAM_RETRIES 10
#endif
#ifndef STREAM_DELAY
#define STREAM_DELAY 2
#endif
#define STREAM_EOF (1 << 0)
#define STREAM_ERR (1 << 1)
#define STREAM_TTY (1 << 2)
struct Stream
{
Io *io;
char *rBuf;
size_t rLen;
size_t rOff;
char *wBuf;
size_t wLen;
char *ugBuf;
size_t ugSize;
size_t ugLen;
int flags;
int fd;
};
Stream *
StreamIo(Io * io)
{
Stream *stream;
if (!io)
{
return NULL;
}
stream = Malloc(sizeof(Stream));
if (!stream)
{
return NULL;
}
memset(stream, 0, sizeof(Stream));
stream->io = io;
stream->fd = -1;
return stream;
}
Stream *
StreamFd(int fd)
{
Io *io = IoFd(fd);
Stream *stream;
if (!io)
{
return NULL;
}
stream = StreamIo(io);