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:
commit
40eac30b5c
60 changed files with 15048 additions and 0 deletions
3
.cvsignore
Normal file
3
.cvsignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
build
|
||||
out
|
||||
*-leaked.txt
|
28
.indent.pro
vendored
Normal file
28
.indent.pro
vendored
Normal 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
24
LICENSE.txt
Normal 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
158
README.txt
Normal 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
301
make.sh
Executable 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
118
src/Args.c
Normal 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
339
src/Array.c
Normal 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
244
src/Base64.c
Normal 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
249
src/Cron.c
Normal 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
968
src/Db.c
Normal 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
|
||||
*/
|
||||