mirror of
https://git.telodendria.io/Telodendria/Cytoplasm.git
synced 2024-11-23 04:28:49 +03:00
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
|
||||||
|
*/
|
||||||
|
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
401
src/HashMap.c
Normal 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
664
src/HeaderParser.c
Normal 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
642
src/Http.c
Normal 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
298
src/HttpClient.c
Normal 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
296
src/HttpRouter.c
Normal 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
753
src/HttpServer.c
Normal 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
212
src/Io.c
Normal 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
95
src/Io/IoFd.c
Normal 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
91
src/Io/IoFile.c
Normal 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
1384
src/Json.c
Normal file
File diff suppressed because it is too large
Load diff
388
src/Log.c
Normal file
388
src/Log.c
Normal 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
493
src/Memory.c
Normal 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
176
src/Queue.c
Normal 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
172
src/Rand.c
Normal 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
162
src/RtStub.c
Normal 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
111
src/Runtime.c
Normal 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(¤tTime);
|
||||||
|
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
238
src/Sha2.c
Normal 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
297
src/Str.c
Normal 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
657
src/Stream.c
Normal 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);
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->fd = fd;
|
||||||
|
|
||||||
|
if (isatty(stream->fd))
|
||||||
|
{
|
||||||
|
stream->flags |= STREAM_TTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream *
|
||||||
|
StreamFile(FILE * fp)
|
||||||
|
{
|
||||||
|
Io *io = IoFile(fp);
|
||||||
|
Stream *stream;
|
||||||
|
|
||||||
|
if (!io)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream = StreamIo(io);
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->fd = fileno(fp);
|
||||||
|
|
||||||
|
if (isatty(stream->fd))
|
||||||
|
{
|
||||||
|
stream->flags |= STREAM_TTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream *
|
||||||
|
StreamOpen(const char *path, const char *mode)
|
||||||
|
{
|
||||||
|
FILE *fp = fopen(path, mode);
|
||||||
|
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StreamFile(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream *
|
||||||
|
StreamStdout(void)
|
||||||
|
{
|
||||||
|
static Stream *stdOut = NULL;
|
||||||
|
|
||||||
|
if (!stdOut)
|
||||||
|
{
|
||||||
|
stdOut = StreamFd(STDOUT_FILENO);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream *
|
||||||
|
StreamStderr(void)
|
||||||
|
{
|
||||||
|
static Stream *stdErr = NULL;
|
||||||
|
|
||||||
|
if (!stdErr)
|
||||||
|
{
|
||||||
|
stdErr = StreamFd(STDERR_FILENO);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdErr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream *
|
||||||
|
StreamStdin(void)
|
||||||
|
{
|
||||||
|
static Stream *stdIn = NULL;
|
||||||
|
|
||||||
|
if (!stdIn)
|
||||||
|
{
|
||||||
|
stdIn = StreamFd(STDIN_FILENO);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
StreamClose(Stream * stream)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->rBuf)
|
||||||
|
{
|
||||||
|
Free(stream->rBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->wBuf)
|
||||||
|
{
|
||||||
|
ssize_t writeRes = IoWrite(stream->io, stream->wBuf, stream->wLen);
|
||||||
|
|
||||||
|
Free(stream->wBuf);
|
||||||
|
|
||||||
|
if (writeRes == -1)
|
||||||
|
{
|
||||||
|
ret = EOF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->ugBuf)
|
||||||
|
{
|
||||||
|
Free(stream->ugBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = IoClose(stream->io);
|
||||||
|
Free(stream);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
StreamVprintf(Stream * stream, const char *fmt, va_list ap)
|
||||||
|
{
|
||||||
|
/* This might look like very similar code to IoVprintf(), but I
|
||||||
|
* chose not to defer to IoVprintf() because that would require us
|
||||||
|
* to immediately flush the buffer, since the Io API is unbuffered.
|
||||||
|
* StreamPuts() uses StreamPutc() under the hood, which is
|
||||||
|
* buffered. It therefore allows us to finish filling the buffer
|
||||||
|
* and then only flush it when necessary, preventing superfluous
|
||||||
|
* writes. */
|
||||||
|
|
||||||
|
char *buf = NULL;
|
||||||
|
size_t bufSize = 0;
|
||||||
|
FILE *fp;
|
||||||
|
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!fmt)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fp = open_memstream(&buf, &bufSize);
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = vfprintf(fp, fmt, ap);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
if (ret >= 0 && stream)
|
||||||
|
{
|
||||||
|
if (StreamPuts(stream, buf) < 0)
|
||||||
|
{
|
||||||
|
ret = -1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buf); /* Allocated by stdlib, not Memory
|
||||||
|
* API */
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
StreamPrintf(Stream * stream, const char *fmt,...)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
ret = StreamVprintf(stream, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
StreamGetc(Stream * stream)
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empty the ungetc stack first */
|
||||||
|
if (stream->ugLen)
|
||||||
|
{
|
||||||
|
c = stream->ugBuf[stream->ugLen - 1];
|
||||||
|
stream->ugLen--;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->flags & STREAM_EOF)
|
||||||
|
{
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream->rBuf)
|
||||||
|
{
|
||||||
|
/* No buffer allocated yet */
|
||||||
|
stream->rBuf = Malloc(IO_BUFFER);
|
||||||
|
if (!stream->rBuf)
|
||||||
|
{
|
||||||
|
stream->flags |= STREAM_ERR;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->rOff = 0;
|
||||||
|
stream->rLen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->rOff >= stream->rLen)
|
||||||
|
{
|
||||||
|
/* We read through the entire buffer; get a new one */
|
||||||
|
ssize_t readRes = IoRead(stream->io, stream->rBuf, IO_BUFFER);
|
||||||
|
|
||||||
|
if (readRes == 0)
|
||||||
|
{
|
||||||
|
stream->flags |= STREAM_EOF;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readRes == -1)
|
||||||
|
{
|
||||||
|
stream->flags |= STREAM_ERR;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->rOff = 0;
|
||||||
|
stream->rLen = readRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read the character in the buffer and advance the offset */
|
||||||
|
c = stream->rBuf[stream->rOff];
|
||||||
|
stream->rOff++;
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
StreamUngetc(Stream * stream, int c)
|
||||||
|
{
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream->ugBuf)
|
||||||
|
{
|
||||||
|
stream->ugSize = IO_BUFFER;
|
||||||
|
stream->ugBuf = Malloc(stream->ugSize);
|
||||||
|
|
||||||
|
if (!stream->ugBuf)
|
||||||
|
{
|
||||||
|
stream->flags |= STREAM_ERR;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->ugLen >= stream->ugSize)
|
||||||
|
{
|
||||||
|
char *new;
|
||||||
|
|
||||||
|
stream->ugSize += IO_BUFFER;
|
||||||
|
new = Realloc(stream->ugBuf, stream->ugSize);
|
||||||
|
if (!new)
|
||||||
|
{
|
||||||
|
stream->flags |= STREAM_ERR;
|
||||||
|
Free(stream->ugBuf);
|
||||||
|
stream->ugBuf = NULL;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
Free(stream->ugBuf);
|
||||||
|
stream->ugBuf = new;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->ugBuf[stream->ugLen] = c;
|
||||||
|
stream->ugLen++;
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
StreamPutc(Stream * stream, int c)
|
||||||
|
{
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream->wBuf)
|
||||||
|
{
|
||||||
|
stream->wBuf = Malloc(IO_BUFFER);
|
||||||
|
if (!stream->wBuf)
|
||||||
|
{
|
||||||
|
stream->flags |= STREAM_ERR;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->wLen == IO_BUFFER)
|
||||||
|
{
|
||||||
|
/* Buffer full; write it */
|
||||||
|
ssize_t writeRes = IoWrite(stream->io, stream->wBuf, stream->wLen);
|
||||||
|
|
||||||
|
if (writeRes == -1)
|
||||||
|
{
|
||||||
|
stream->flags |= STREAM_ERR;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->wLen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->wBuf[stream->wLen] = c;
|
||||||
|
stream->wLen++;
|
||||||
|
|
||||||
|
if (stream->flags & STREAM_TTY && c == '\n')
|
||||||
|
{
|
||||||
|
/* Newline encountered on a TTY; write now. This fixes some
|
||||||
|
* strange behavior on certain TTYs where a newline is written
|
||||||
|
* to the screen upon flush even when no newline exists in the
|
||||||
|
* stream. We just flush on newlines, but only if we're
|
||||||
|
* directly writing to a TTY. */
|
||||||
|
ssize_t writeRes = IoWrite(stream->io, stream->wBuf, stream->wLen);
|
||||||
|
|
||||||
|
if (writeRes == -1)
|
||||||
|
{
|
||||||
|
stream->flags |= STREAM_ERR;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->wLen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
StreamPuts(Stream * stream, char *str)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*str)
|
||||||
|
{
|
||||||
|
if (StreamPutc(stream, *str) == EOF)
|
||||||
|
{
|
||||||
|
ret = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
StreamGets(Stream * stream, char *str, int size)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size <= 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < size - 1; i++)
|
||||||
|
{
|
||||||
|
int c = StreamGetc(stream);
|
||||||
|
|
||||||
|
if (StreamEof(stream) || StreamError(stream))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
str[i] = c;
|
||||||
|
|
||||||
|
if (c == '\n')
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str[i] = '\0';
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t
|
||||||
|
StreamSeek(Stream * stream, off_t offset, int whence)
|
||||||
|
{
|
||||||
|
off_t result;
|
||||||
|
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = IoSeek(stream->io, offset, whence);
|
||||||
|
if (result < 0)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Successful seek; clear the buffers */
|
||||||
|
stream->rOff = 0;
|
||||||
|
stream->wLen = 0;
|
||||||
|
stream->ugLen = 0;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
StreamEof(Stream * stream)
|
||||||
|
{
|
||||||
|
return stream && (stream->flags & STREAM_EOF);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
StreamError(Stream * stream)
|
||||||
|
{
|
||||||
|
return stream && (stream->flags & STREAM_ERR);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
StreamClearError(Stream * stream)
|
||||||
|
{
|
||||||
|
if (stream)
|
||||||
|
{
|
||||||
|
stream->flags &= ~STREAM_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
StreamFlush(Stream * stream)
|
||||||
|
{
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->wLen)
|
||||||
|
{
|
||||||
|
ssize_t writeRes = IoWrite(stream->io, stream->wBuf, stream->wLen);
|
||||||
|
|
||||||
|
if (writeRes == -1)
|
||||||
|
{
|
||||||
|
stream->flags |= STREAM_ERR;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->wLen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
StreamCopy(Stream * in, Stream * out)
|
||||||
|
{
|
||||||
|
ssize_t nBytes = 0;
|
||||||
|
int c;
|
||||||
|
int tries = 0;
|
||||||
|
int readFlg = 0;
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
c = StreamGetc(in);
|
||||||
|
|
||||||
|
if (StreamEof(in))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StreamError(in))
|
||||||
|
{
|
||||||
|
if (errno == EAGAIN)
|
||||||
|
{
|
||||||
|
StreamClearError(in);
|
||||||
|
tries++;
|
||||||
|
|
||||||
|
if (tries >= STREAM_RETRIES || readFlg)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UtilSleepMillis(STREAM_DELAY);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* As soon as we've successfully read a byte, treat future
|
||||||
|
* EAGAINs as EOF, because somebody might have forgotten to
|
||||||
|
* close their stream. */
|
||||||
|
|
||||||
|
readFlg = 1;
|
||||||
|
tries = 0;
|
||||||
|
|
||||||
|
StreamPutc(out, c);
|
||||||
|
nBytes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamFlush(out);
|
||||||
|
return nBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
StreamFileno(Stream * stream)
|
||||||
|
{
|
||||||
|
return stream ? stream->fd : -1;
|
||||||
|
}
|
85
src/Tls.c
Normal file
85
src/Tls.c
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* 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 <Tls.h>
|
||||||
|
|
||||||
|
#ifdef TLS_IMPL
|
||||||
|
|
||||||
|
#include <Io.h>
|
||||||
|
#include <Stream.h>
|
||||||
|
|
||||||
|
Stream *
|
||||||
|
TlsClientStream(int fd, const char *serverName)
|
||||||
|
{
|
||||||
|
Io *io;
|
||||||
|
void *cookie;
|
||||||
|
IoFunctions funcs;
|
||||||
|
|
||||||
|
cookie = TlsInitClient(fd, serverName);
|
||||||
|
if (!cookie)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
funcs.read = TlsRead;
|
||||||
|
funcs.write = TlsWrite;
|
||||||
|
funcs.seek = NULL;
|
||||||
|
funcs.close = TlsClose;
|
||||||
|
|
||||||
|
io = IoCreate(cookie, funcs);
|
||||||
|
if (!io)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StreamIo(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream *
|
||||||
|
TlsServerStream(int fd, const char *crt, const char *key)
|
||||||
|
{
|
||||||
|
Io *io;
|
||||||
|
void *cookie;
|
||||||
|
IoFunctions funcs;
|
||||||
|
|
||||||
|
cookie = TlsInitServer(fd, crt, key);
|
||||||
|
if (!cookie)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
funcs.read = TlsRead;
|
||||||
|
funcs.write = TlsWrite;
|
||||||
|
funcs.seek = NULL;
|
||||||
|
funcs.close = TlsClose;
|
||||||
|
|
||||||
|
io = IoCreate(cookie, funcs);
|
||||||
|
if (!io)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StreamIo(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
247
src/Tls/TlsLibreSSL.c
Normal file
247
src/Tls/TlsLibreSSL.c
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
/*
|
||||||
|
* 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 <Tls.h>
|
||||||
|
|
||||||
|
#if TLS_IMPL == TLS_LIBRESSL
|
||||||
|
|
||||||
|
#include <Memory.h>
|
||||||
|
#include <Log.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <tls.h> /* LibreSSL TLS */
|
||||||
|
|
||||||
|
typedef struct LibreSSLCookie
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
struct tls *ctx;
|
||||||
|
struct tls *cctx;
|
||||||
|
struct tls_config *cfg;
|
||||||
|
} LibreSSLCookie;
|
||||||
|
|
||||||
|
void *
|
||||||
|
TlsInitClient(int fd, const char *serverName)
|
||||||
|
{
|
||||||
|
LibreSSLCookie *cookie = Malloc(sizeof(LibreSSLCookie));
|
||||||
|
|
||||||
|
if (!cookie)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie->ctx = tls_client();
|
||||||
|
cookie->cctx = NULL;
|
||||||
|
cookie->cfg = tls_config_new();
|
||||||
|
cookie->fd = fd;
|
||||||
|
|
||||||
|
|
||||||
|
if (!cookie->ctx || !cookie->cfg)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tls_config_set_ca_file(cookie->cfg, tls_default_ca_cert_file()) == -1)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tls_configure(cookie->ctx, cookie->cfg) == -1)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tls_connect_socket(cookie->ctx, fd, serverName) == -1)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tls_handshake(cookie->ctx) == -1)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookie;
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (cookie->ctx)
|
||||||
|
{
|
||||||
|
if (tls_error(cookie->ctx))
|
||||||
|
{
|
||||||
|
Log(LOG_ERR, "TlsInitClient(): %s", tls_error(cookie->ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
tls_free(cookie->ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookie->cfg)
|
||||||
|
{
|
||||||
|
tls_config_free(cookie->cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
Free(cookie);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
TlsInitServer(int fd, const char *crt, const char *key)
|
||||||
|
{
|
||||||
|
LibreSSLCookie *cookie = Malloc(sizeof(LibreSSLCookie));
|
||||||
|
|
||||||
|
if (!cookie)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie->ctx = tls_server();
|
||||||
|
cookie->cctx = NULL;
|
||||||
|
cookie->cfg = tls_config_new();
|
||||||
|
cookie->fd = fd;
|
||||||
|
|
||||||
|
if (!cookie->ctx || !cookie->cfg)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tls_config_set_cert_file(cookie->cfg, crt) == -1)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tls_config_set_key_file(cookie->cfg, key) == -1)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tls_configure(cookie->ctx, cookie->cfg) == -1)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tls_accept_fds(cookie->ctx, &cookie->cctx, fd, fd) == -1)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tls_handshake(cookie->cctx) == -1)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookie;
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (cookie->ctx)
|
||||||
|
{
|
||||||
|
if (tls_error(cookie->ctx))
|
||||||
|
{
|
||||||
|
Log(LOG_ERR, "TlsInitServer(): %s", tls_error(cookie->ctx));
|
||||||
|
}
|
||||||
|
tls_free(cookie->ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookie->cctx)
|
||||||
|
{
|
||||||
|
if (tls_error(cookie->cctx))
|
||||||
|
{
|
||||||
|
Log(LOG_ERR, "TlsInitServer(): %s", tls_error(cookie->cctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
tls_free(cookie->cctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookie->cfg)
|
||||||
|
{
|
||||||
|
tls_config_free(cookie->cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
Free(cookie);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
TlsRead(void *cookie, void *buf, size_t nBytes)
|
||||||
|
{
|
||||||
|
LibreSSLCookie *tls = cookie;
|
||||||
|
struct tls *ctx = tls->cctx ? tls->cctx : tls->ctx;
|
||||||
|
ssize_t ret = tls_read(ctx, buf, nBytes);
|
||||||
|
|
||||||
|
if (ret == -1)
|
||||||
|
{
|
||||||
|
errno = EIO;
|
||||||
|
}
|
||||||
|
else if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
|
||||||
|
{
|
||||||
|
errno = EAGAIN;
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
TlsWrite(void *cookie, void *buf, size_t nBytes)
|
||||||
|
{
|
||||||
|
LibreSSLCookie *tls = cookie;
|
||||||
|
struct tls *ctx = tls->cctx ? tls->cctx : tls->ctx;
|
||||||
|
ssize_t ret = tls_write(ctx, buf, nBytes);
|
||||||
|
|
||||||
|
if (ret == -1)
|
||||||
|
{
|
||||||
|
errno = EIO;
|
||||||
|
}
|
||||||
|
else if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
|
||||||
|
{
|
||||||
|
errno = EAGAIN;
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
TlsClose(void *cookie)
|
||||||
|
{
|
||||||
|
LibreSSLCookie *tls = cookie;
|
||||||
|
|
||||||
|
int tlsRet = tls_close(tls->cctx ? tls->cctx : tls->ctx);
|
||||||
|
int sdRet;
|
||||||
|
|
||||||
|
if (tls->cctx)
|
||||||
|
{
|
||||||
|
tls_free(tls->cctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
tls_free(tls->ctx);
|
||||||
|
tls_config_free(tls->cfg);
|
||||||
|
|
||||||
|
sdRet = close(tls->fd);
|
||||||
|
|
||||||
|
Free(tls);
|
||||||
|
|
||||||
|
return (tlsRet == -1 || sdRet == -1) ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
296
src/Tls/TlsOpenSSL.c
Normal file
296
src/Tls/TlsOpenSSL.c
Normal 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 <Tls.h>
|
||||||
|
|
||||||
|
#if TLS_IMPL == TLS_OPENSSL
|
||||||
|
|
||||||
|
#include <Memory.h>
|
||||||
|
#include <Log.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
|
||||||
|
typedef struct OpenSSLCookie
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
const SSL_METHOD *method;
|
||||||
|
SSL_CTX *ctx;
|
||||||
|
SSL *ssl;
|
||||||
|
} OpenSSLCookie;
|
||||||
|
|
||||||
|
static char *
|
||||||
|
SSLErrorString(int err)
|
||||||
|
{
|
||||||
|
switch (err)
|
||||||
|
{
|
||||||
|
case SSL_ERROR_NONE:
|
||||||
|
return "No error.";
|
||||||
|
case SSL_ERROR_ZERO_RETURN:
|
||||||
|
return "The TLS/SSL connection has been closed.";
|
||||||
|
case SSL_ERROR_WANT_READ:
|
||||||
|
case SSL_ERROR_WANT_WRITE:
|
||||||
|
case SSL_ERROR_WANT_CONNECT:
|
||||||
|
case SSL_ERROR_WANT_ACCEPT:
|
||||||
|
return "The operation did not complete.";
|
||||||
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
||||||
|
return "X509 lookup failed.";
|
||||||
|
case SSL_ERROR_SYSCALL:
|
||||||
|
return "I/O Error.";
|
||||||
|
case SSL_ERROR_SSL:
|
||||||
|
return "SSL library error.";
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
TlsInitClient(int fd, const char *serverName)
|
||||||
|
{
|
||||||
|
OpenSSLCookie *cookie;
|
||||||
|
char errorStr[256];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: Seems odd that this isn't needed to make the
|
||||||
|
* connection... we should figure out how to verify the
|
||||||
|
* certificate matches the server we think we're
|
||||||
|
* connecting to.
|
||||||
|
*/
|
||||||
|
(void) serverName;
|
||||||
|
|
||||||
|
cookie = Malloc(sizeof(OpenSSLCookie));
|
||||||
|
if (!cookie)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(cookie, 0, sizeof(OpenSSLCookie));
|
||||||
|
|
||||||
|
cookie->method = TLS_client_method();
|
||||||
|
cookie->ctx = SSL_CTX_new(cookie->method);
|
||||||
|
if (!cookie->ctx)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie->ssl = SSL_new(cookie->ctx);
|
||||||
|
if (!cookie->ssl)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SSL_set_fd(cookie->ssl, fd))
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SSL_connect(cookie->ssl) <= 0)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookie;
|
||||||
|
|
||||||
|
error:
|
||||||
|
Log(LOG_ERR, "TlsClientInit(): %s", ERR_error_string(ERR_get_error(), errorStr));
|
||||||
|
|
||||||
|
if (cookie->ssl)
|
||||||
|
{
|
||||||
|
SSL_shutdown(cookie->ssl);
|
||||||
|
SSL_free(cookie->ssl);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(cookie->fd);
|
||||||
|
|
||||||
|
if (cookie->ctx)
|
||||||
|
{
|
||||||
|
SSL_CTX_free(cookie->ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
TlsInitServer(int fd, const char *crt, const char *key)
|
||||||
|
{
|
||||||
|
OpenSSLCookie *cookie;
|
||||||
|
char errorStr[256];
|
||||||
|
int acceptRet = 0;
|
||||||
|
|
||||||
|
cookie = Malloc(sizeof(OpenSSLCookie));
|
||||||
|
if (!cookie)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(cookie, 0, sizeof(OpenSSLCookie));
|
||||||
|
|
||||||
|
cookie->method = TLS_server_method();
|
||||||
|
cookie->ctx = SSL_CTX_new(cookie->method);
|
||||||
|
if (!cookie->ctx)
|
||||||
|
{
|
||||||
|
Log(LOG_ERR, "TlsInitServer(): Unable to create SSL Context.");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SSL_CTX_use_certificate_file(cookie->ctx, crt, SSL_FILETYPE_PEM) <= 0)
|
||||||
|
{
|
||||||
|
Log(LOG_ERR, "TlsInitServer(): Unable to set certificate file: %s", crt);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SSL_CTX_use_PrivateKey_file(cookie->ctx, key, SSL_FILETYPE_PEM) <= 0)
|
||||||
|
{
|
||||||
|
Log(LOG_ERR, "TlsInitServer(): Unable to set key file.");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie->ssl = SSL_new(cookie->ctx);
|
||||||
|
if (!cookie->ssl)
|
||||||
|
{
|
||||||
|
Log(LOG_ERR, "TlsInitServer(): Unable to create SSL object.");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SSL_set_fd(cookie->ssl, fd))
|
||||||
|
{
|
||||||
|
Log(LOG_ERR, "TlsInitServer(): Unable to set file descriptor.");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((acceptRet = SSL_accept(cookie->ssl)) <= 0)
|
||||||
|
{
|
||||||
|
switch (SSL_get_error(cookie->ssl, acceptRet))
|
||||||
|
{
|
||||||
|
case SSL_ERROR_WANT_READ:
|
||||||
|
case SSL_ERROR_WANT_WRITE:
|
||||||
|
case SSL_ERROR_WANT_CONNECT:
|
||||||
|
case SSL_ERROR_WANT_ACCEPT:
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
Log(LOG_ERR, "TlsInitServer(): Unable to accept connection.");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookie;
|
||||||
|
|
||||||
|
error:
|
||||||
|
Log(LOG_ERR, "TlsServerInit(): %s", SSLErrorString(SSL_get_error(cookie->ssl, acceptRet)));
|
||||||
|
Log(LOG_ERR, "TlsServerInit(): %s", ERR_error_string(ERR_get_error(), errorStr));
|
||||||
|
|
||||||
|
if (cookie->ssl)
|
||||||
|
{
|
||||||
|
SSL_shutdown(cookie->ssl);
|
||||||
|
SSL_free(cookie->ssl);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(cookie->fd);
|
||||||
|
|
||||||
|
if (cookie->ctx)
|
||||||
|
{
|
||||||
|
SSL_CTX_free(cookie->ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
Free(cookie);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
TlsRead(void *cookie, void *buf, size_t nBytes)
|
||||||
|
{
|
||||||
|
OpenSSLCookie *ssl = cookie;
|
||||||
|
int ret = SSL_read(ssl->ssl, buf, nBytes);
|
||||||
|
|
||||||
|
if (ret <= 0)
|
||||||
|
{
|
||||||
|
switch (SSL_get_error(ssl->ssl, ret))
|
||||||
|
{
|
||||||
|
case SSL_ERROR_WANT_READ:
|
||||||
|
case SSL_ERROR_WANT_WRITE:
|
||||||
|
case SSL_ERROR_WANT_CONNECT:
|
||||||
|
case SSL_ERROR_WANT_ACCEPT:
|
||||||
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
||||||
|
errno = EAGAIN;
|
||||||
|
break;
|
||||||
|
case SSL_ERROR_ZERO_RETURN:
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errno = EIO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
TlsWrite(void *cookie, void *buf, size_t nBytes)
|
||||||
|
{
|
||||||
|
OpenSSLCookie *ssl = cookie;
|
||||||
|
int ret = SSL_write(ssl->ssl, buf, nBytes);
|
||||||
|
|
||||||
|
if (ret <= 0)
|
||||||
|
{
|
||||||
|
switch (SSL_get_error(ssl->ssl, ret))
|
||||||
|
{
|
||||||
|
case SSL_ERROR_WANT_READ:
|
||||||
|
case SSL_ERROR_WANT_WRITE:
|
||||||
|
case SSL_ERROR_WANT_CONNECT:
|
||||||
|
case SSL_ERROR_WANT_ACCEPT:
|
||||||
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
||||||
|
errno = EAGAIN;
|
||||||
|
break;
|
||||||
|
case SSL_ERROR_ZERO_RETURN:
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errno = EIO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
TlsClose(void *cookie)
|
||||||
|
{
|
||||||
|
OpenSSLCookie *ssl = cookie;
|
||||||
|
|
||||||
|
SSL_shutdown(ssl->ssl);
|
||||||
|
SSL_free(ssl->ssl);
|
||||||
|
close(ssl->fd);
|
||||||
|
SSL_CTX_free(ssl->ctx);
|
||||||
|
|
||||||
|
Free(ssl);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
73
src/Uri.c
Normal file
73
src/Uri.c
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* 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 <Uri.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <Memory.h>
|
||||||
|
|
||||||
|
Uri *
|
||||||
|
UriParse(const char *str)
|
||||||
|
{
|
||||||
|
Uri *uri;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if (!str)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = Malloc(sizeof(Uri));
|
||||||
|
if (!uri)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(uri, 0, sizeof(Uri));
|
||||||
|
|
||||||
|
res = sscanf(str, "%7[^:]://%127[^:]:%hu%255[^\n]", uri->proto, uri->host, &uri->port, uri->path) == 4
|
||||||
|
|| sscanf(str, "%7[^:]://%127[^/]%255[^\n]", uri->proto, uri->host, uri->path) == 3
|
||||||
|
|| sscanf(str, "%7[^:]://%127[^:]:%hu[^\n]", uri->proto, uri->host, &uri->port) == 3
|
||||||
|
|| sscanf(str, "%7[^:]://%127[^\n]", uri->proto, uri->host) == 2;
|
||||||
|
|
||||||
|
if (!res)
|
||||||
|
{
|
||||||
|
Free(uri);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uri->path[0])
|
||||||
|
{
|
||||||
|
strcpy(uri->path, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
UriFree(Uri * uri)
|
||||||
|
{
|
||||||
|
Free(uri);
|
||||||
|
}
|
246
src/Util.c
Normal file
246
src/Util.c
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
/*
|
||||||
|
* 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 <Util.h>
|
||||||
|
|
||||||
|
#include <Memory.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#ifndef PATH_MAX
|
||||||
|
#define PATH_MAX 256
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SSIZE_MAX
|
||||||
|
#define SSIZE_MAX LONG_MAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
unsigned long
|
||||||
|
UtilServerTs(void)
|
||||||
|
{
|
||||||
|
struct timeval tv;
|
||||||
|
unsigned long ts;
|
||||||
|
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
|
ts = (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
|
||||||
|
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long
|
||||||
|
UtilLastModified(char *path)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
unsigned long ts;
|
||||||
|
|
||||||
|
if (stat(path, &st) == 0)
|
||||||
|
{
|
||||||
|
ts = (st.st_mtim.tv_sec * 1000) + (st.st_mtim.tv_nsec / 1000000);
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
UtilMkdir(const char *dir, const mode_t mode)
|
||||||
|
{
|
||||||
|
char tmp[PATH_MAX];
|
||||||
|
char *p = NULL;
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
len = strnlen(dir, PATH_MAX);
|
||||||
|
if (!len || len == PATH_MAX)
|
||||||
|
{
|
||||||
|
errno = ENAMETOOLONG;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(tmp, dir, len);
|
||||||
|
tmp[len] = '\0';
|
||||||
|
|
||||||
|
if (tmp[len - 1] == '/')
|
||||||
|
{
|
||||||
|
tmp[len - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stat(tmp, &st) == 0 && S_ISDIR(st.st_mode))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (p = tmp + 1; *p; p++)
|
||||||
|
{
|
||||||
|
if (*p == '/')
|
||||||
|
{
|
||||||
|
*p = 0;
|
||||||
|
|
||||||
|
if (stat(tmp, &st) != 0)
|
||||||
|
{
|
||||||
|
if (mkdir(tmp, mode) < 0)
|
||||||
|
{
|
||||||
|
/* errno already set by mkdir() */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!S_ISDIR(st.st_mode))
|
||||||
|
{
|
||||||
|
errno = ENOTDIR;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stat(tmp, &st) != 0)
|
||||||
|
{
|
||||||
|
if (mkdir(tmp, mode) < 0)
|
||||||
|
{
|
||||||
|
/* errno already set by mkdir() */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!S_ISDIR(st.st_mode))
|
||||||
|
{
|
||||||
|
errno = ENOTDIR;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
UtilSleepMillis(long ms)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
ts.tv_sec = ms / 1000;
|
||||||
|
ts.tv_nsec = (ms % 1000) * 1000000;
|
||||||
|
|
||||||
|
res = nanosleep(&ts, &ts);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
UtilGetDelim(char **linePtr, size_t * n, int delim, Stream * stream)
|
||||||
|
{
|
||||||
|
char *curPos, *newLinePtr;
|
||||||
|
size_t newLinePtrLen;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
if (!linePtr || !n || !stream)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*linePtr == NULL)
|
||||||
|
{
|
||||||
|
*n = 128;
|
||||||
|
|
||||||
|
if (!(*linePtr = Malloc(*n)))
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curPos = *linePtr;
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
c = StreamGetc(stream);
|
||||||
|
|
||||||
|
if (StreamError(stream) || (c == EOF && curPos == *linePtr))
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == EOF)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*linePtr + *n - curPos) < 2)
|
||||||
|
{
|
||||||
|
if (SSIZE_MAX / 2 < *n)
|
||||||
|
{
|
||||||
|
#ifdef EOVERFLOW
|
||||||
|
errno = EOVERFLOW;
|
||||||
|
#else
|
||||||
|
errno = ERANGE;
|
||||||
|
#endif
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
newLinePtrLen = *n * 2;
|
||||||
|
|
||||||
|
if (!(newLinePtr = Realloc(*linePtr, newLinePtrLen)))
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
curPos = newLinePtr + (curPos - *linePtr);
|
||||||
|
*linePtr = newLinePtr;
|
||||||
|
*n = newLinePtrLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
*curPos++ = (char) c;
|
||||||
|
|
||||||
|
if (c == delim)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*curPos = '\0';
|
||||||
|
return (ssize_t) (curPos - *linePtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
UtilGetLine(char **linePtr, size_t * n, Stream * stream)
|
||||||
|
{
|
||||||
|
return UtilGetDelim(linePtr, n, '\n', stream);
|
||||||
|
}
|
82
src/include/Args.h
Normal file
82
src/include/Args.h
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_ARGS_H
|
||||||
|
#define CYTOPLASM_ARGS_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Args
|
||||||
|
* @Nd Getopt-style argument parser that operates on arrays.
|
||||||
|
* @Dd May 12 2023
|
||||||
|
* @Xr Array
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* provides a simple argument parser in the style of
|
||||||
|
* .Xr getopt 3 .
|
||||||
|
* It exists because the runtime passes the program arguments as
|
||||||
|
* an Array, and it is often useful to parse the arguments to
|
||||||
|
* provide the standard command line interface.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Array.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All state is stored in this structure, instead of global
|
||||||
|
* variables. This makes
|
||||||
|
* .Nm
|
||||||
|
* thread-safe and easy to reset.
|
||||||
|
*/
|
||||||
|
typedef struct ArgParseState
|
||||||
|
{
|
||||||
|
int optInd;
|
||||||
|
int optErr;
|
||||||
|
int optOpt;
|
||||||
|
char *optArg;
|
||||||
|
|
||||||
|
int optPos;
|
||||||
|
} ArgParseState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the variables in the given parser state structure
|
||||||
|
* to their default values. This should be called before
|
||||||
|
* .Fn ArgParse
|
||||||
|
* is called with the parser state. It should also be called if
|
||||||
|
* .Fn ArgParse
|
||||||
|
* will be used again on a different array, or the same array all
|
||||||
|
* over again.
|
||||||
|
*/
|
||||||
|
extern void ArgParseStateInit(ArgParseState *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse command line arguments stored in the given array, using
|
||||||
|
* the given state and option string. This function behaves
|
||||||
|
* identically to the POSIX
|
||||||
|
* .Fn getopt
|
||||||
|
* function, and should be used in exactly the same way. Refer to
|
||||||
|
* your system's
|
||||||
|
* .Xr getopt 3
|
||||||
|
* page for details.
|
||||||
|
*/
|
||||||
|
extern int ArgParse(ArgParseState *, Array *, const char *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_ARGS_H */
|
164
src/include/Array.h
Normal file
164
src/include/Array.h
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CYTOPLASM_ARRAY_H
|
||||||
|
#define CYTOPLASM_ARRAY_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Array
|
||||||
|
* @Nd A simple dynamic array data structure.
|
||||||
|
* @Dd November 24 2022
|
||||||
|
* @Xr HashMap Queue
|
||||||
|
*
|
||||||
|
* These functions implement a simple array data structure that is
|
||||||
|
* automatically resized as necessary when new values are added. This
|
||||||
|
* implementation does not actually store the values of the items in it;
|
||||||
|
* it only stores pointers to the data. As such, you will still have to
|
||||||
|
* manually maintain all your data. The advantage of this is that these
|
||||||
|
* functions don't have to copy data, and thus don't care how big the
|
||||||
|
* data is. Furthermore, arbitrary data can be stored in the array.
|
||||||
|
* .Pp
|
||||||
|
* This array implementation is optimized for storage space and
|
||||||
|
* appending. Deletions are expensive in that all the items of the list
|
||||||
|
* above a deletion are moved down to fill the hole where the deletion
|
||||||
|
* occurred. Insertions are also expensive in that all the elements
|
||||||
|
* above the given index must be shifted up to make room for the new
|
||||||
|
* element.
|
||||||
|
* .Pp
|
||||||
|
* Due to these design choices, this array implementation is best
|
||||||
|
* suited to linear writing, and then linear or random reading.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The functions in this API operate on an array structure which is
|
||||||
|
* opaque to the caller.
|
||||||
|
*/
|
||||||
|
typedef struct Array Array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate a new array. This function returns a pointer that can be
|
||||||
|
* used with the other functions in this API, or NULL if there was an
|
||||||
|
* error allocating memory for the array.
|
||||||
|
*/
|
||||||
|
extern Array * ArrayCreate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deallocate an array. Note that this function does not free any of
|
||||||
|
* the values stored in the array; it is the caller's job to manage the
|
||||||
|
* memory for each item. Typically, the caller would iterate over all
|
||||||
|
* the items in the array and free them before freeing the array.
|
||||||
|
*/
|
||||||
|
extern void ArrayFree(Array *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size, in number of elements, of the given array.
|
||||||
|
*/
|
||||||
|
extern size_t ArraySize(Array *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the element at the specified index from the specified array.
|
||||||
|
* This function will return NULL if the array is NULL, or the index
|
||||||
|
* is out of bounds. Otherwise, it will return a pointer to a value
|
||||||
|
* put into the array using
|
||||||
|
* .Fn ArrayInsert
|
||||||
|
* or
|
||||||
|
* .Fn ArraySet .
|
||||||
|
*/
|
||||||
|
extern void * ArrayGet(Array *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert the specified element at the specified index in the specified
|
||||||
|
* array. This function will shift the element currently at that index,
|
||||||
|
* and any elements after it before inserting the given element.
|
||||||
|
* .Pp
|
||||||
|
* This function returns a boolean value indicating whether or not it
|
||||||
|
* suceeded.
|
||||||
|
*/
|
||||||
|
extern int ArrayInsert(Array *, size_t, void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the value at the specified index in the specified array to the
|
||||||
|
* specified value. This function will return the old value at that
|
||||||
|
* index, if any.
|
||||||
|
*/
|
||||||
|
extern void * ArraySet(Array *, size_t, void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append the specified element to the end of the specified array. This
|
||||||
|
* function uses
|
||||||
|
* .Fn ArrayInsert
|
||||||
|
* under the hood to insert an element at the end. It thus has the same
|
||||||
|
* return value as
|
||||||
|
* .Fn ArrayInsert .
|
||||||
|
*/
|
||||||
|
extern int ArrayAdd(Array *, void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the element at the specified index from the specified array.
|
||||||
|
* This function returns the element removed, if any.
|
||||||
|
*/
|
||||||
|
extern void * ArrayDelete(Array *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the specified array using the specified sort function. The
|
||||||
|
* sort function compares two elements. It takes two void pointers as
|
||||||
|
* parameters, and returns an integer. The return value indicates to
|
||||||
|
* the sorting algorithm how the elements relate to each other. A
|
||||||
|
* return value of 0 indicates that the elements are identical. A
|
||||||
|
* return value greater than 0 indicates that the first item is
|
||||||
|
* ``bigger'' than the second item and should thus appear after it in
|
||||||
|
* the array. A return value less than 0 indicates the opposite: the
|
||||||
|
* second element should appear after the first in the array.
|
||||||
|
*/
|
||||||
|
extern void ArraySort(Array *, int (*) (void *, void *));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If possible, reduce the amount of memory allocated to this array
|
||||||
|
* by calling
|
||||||
|
* .Fn Realloc
|
||||||
|
* on the internal structure to perfectly fit the elements in the
|
||||||
|
* array. This function is intended to be used by functions that return
|
||||||
|
* relatively read-only arrays that will be long-lived.
|
||||||
|
*/
|
||||||
|
extern int ArrayTrim(Array *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a variadic arguments list into an Array. In most cases, the
|
||||||
|
* Array API is much easier to work with than
|
||||||
|
* .Fn va_arg
|
||||||
|
* and friends.
|
||||||
|
*/
|
||||||
|
extern Array * ArrayFromVarArgs(size_t, va_list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicate an existing array. Note that arrays only hold pointers to
|
||||||
|
* their data, not the data itself, so the duplicated array will point
|
||||||
|
* to the same places in memory as the original array.
|
||||||
|
*/
|
||||||
|
extern Array * ArrayDuplicate(Array *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_ARRAY_H */
|
99
src/include/Base64.h
Normal file
99
src/include/Base64.h
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CYTOPLASM_BASE64_H
|
||||||
|
#define CYTOPLASM_BASE64_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Base64
|
||||||
|
* @Nd A simple base64 encoder/decoder with unpadded base64 support.
|
||||||
|
* @Dd September 30 2022
|
||||||
|
* @Xr Sha2
|
||||||
|
*
|
||||||
|
* This is an efficient yet simple base64 encoding and decoding API
|
||||||
|
* that supports regular base64, as well as the Matrix specification's
|
||||||
|
* extension to base64, called ``unpadded base64.'' This API provides
|
||||||
|
* the ability to convert between the two, instead of just implementing
|
||||||
|
* unpadded base64.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function computes the amount of bytes needed to store a message
|
||||||
|
* of the specified number of bytes as base64.
|
||||||
|
*/
|
||||||
|
extern size_t
|
||||||
|
Base64EncodedSize(size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function computes the amount of bytes needed to store a decoded
|
||||||
|
* representation of the encoded message. It takes a pointer to the
|
||||||
|
* encoded string because it must read a few bytes off the end in order
|
||||||
|
* to accurately compute the size.
|
||||||
|
*/
|
||||||
|
extern size_t
|
||||||
|
Base64DecodedSize(const char *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode the specified number of bytes from the specified buffer as
|
||||||
|
* base64. This function returns a string on the heap that should be
|
||||||
|
* freed with
|
||||||
|
* .Fn Free ,
|
||||||
|
* or NULL if a memory allocation error ocurred.
|
||||||
|
*/
|
||||||
|
extern char *
|
||||||
|
Base64Encode(const char *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the specified number of bytes from the specified buffer of
|
||||||
|
* base64. This function returns a string on the heap that should be
|
||||||
|
* freed with
|
||||||
|
* .Fn Free ,
|
||||||
|
* or NULL if a memory allocation error occured.
|
||||||
|
*/
|
||||||
|
extern char *
|
||||||
|
Base64Decode(const char *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the padding from a specified base64 string. This function
|
||||||
|
* modifies the specified string in place. It thus has no return value
|
||||||
|
* because it cannot fail. If the passed pointer is invalid, the
|
||||||
|
* behavior is undefined.
|
||||||
|
*/
|
||||||
|
extern void
|
||||||
|
Base64Unpad(char *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add padding to an unpadded base64 string. This function takes a
|
||||||
|
* pointer to a pointer because it may be necessary to grow the memory
|
||||||
|
* allocated to the string. This function returns a boolean value
|
||||||
|
* indicating whether the pad operation was successful. In practice,
|
||||||
|
* this means it will only fail if a bigger string is necessary, but it
|
||||||
|
* could not be automatically allocated on the heap.
|
||||||
|
*/
|
||||||
|
extern int
|
||||||
|
Base64Pad(char **, size_t);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_BASE64_H */
|
139
src/include/Cron.h
Normal file
139
src/include/Cron.h
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_CRON_H
|
||||||
|
#define CYTOPLASM_CRON_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Cron
|
||||||
|
* @Nd Basic periodic job scheduler.
|
||||||
|
* @Dd December 24 2022
|
||||||
|
*
|
||||||
|
* This is an extremely basic job scheduler. So basic, in fact, that
|
||||||
|
* it currently runs all jobs on a single thread, which means that it
|
||||||
|
* is intended for short-lived jobs. In the future, it might be
|
||||||
|
* extended to support a one-thread-per-job model, but for now, jobs
|
||||||
|
* should take into consideration the fact that they are sharing their
|
||||||
|
* thread, so they should not be long-running.
|
||||||
|
* .Pp
|
||||||
|
* .Nm
|
||||||
|
* works by ``ticking'' at an interval defined by the caller of
|
||||||
|
* .Fn CronCreate .
|
||||||
|
* At each tick, all the registered jobs are queried, and if they are
|
||||||
|
* due to run again, their function is executed. As much as possible,
|
||||||
|
* .Nm
|
||||||
|
* tries to tick at constant intervals, however it is possible that one
|
||||||
|
* or more jobs may overrun the tick duration. If this happens,
|
||||||
|
* .Nm
|
||||||
|
* ticks again immediately after all the jobs for the previous tick
|
||||||
|
* have completed. This is in an effort to compensate for lost time,
|
||||||
|
* however it is important to note that when jobs overrun the tick
|
||||||
|
* interval, the interval is pushed back by the amount that it was
|
||||||
|
* overrun. Because of this,
|
||||||
|
* .Nm
|
||||||
|
* is best suited for scheduling jobs that should happen
|
||||||
|
* ``aproximately'' every so often; it is not a real-time scheduler
|
||||||
|
* by any means.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All functions defined here operate on a structure opaque to the
|
||||||
|
* caller.
|
||||||
|
*/
|
||||||
|
typedef struct Cron Cron;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A job function is a function that takes a void pointer and returns
|
||||||
|
* nothing. The pointer is passed when the job is scheduled, and
|
||||||
|
* is expected to remain valid until the job is no longer registered.
|
||||||
|
* The pointer is passed each time the job executes.
|
||||||
|
*/
|
||||||
|
typedef void (JobFunc) (void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new
|
||||||
|
* .Nm
|
||||||
|
* object that all other functions operate on. Like most of the other
|
||||||
|
* APIs in this project, it must be freed with
|
||||||
|
* .Fn CronFree
|
||||||
|
* when it is no longer needed.
|
||||||
|
* .Pp
|
||||||
|
* This function takes the tick interval in milliseconds.
|
||||||
|
*/
|
||||||
|
extern Cron *
|
||||||
|
CronCreate(unsigned long);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule a one-off job to be executed only at the next tick, and
|
||||||
|
* then immediately discarded. This is useful for scheduling tasks that
|
||||||
|
* only have to happen once, or very infrequently depending on
|
||||||
|
* conditions other than the current time, but don't have to happen
|
||||||
|
* immediately. The caller simply indicates that it wishes for the task
|
||||||
|
* to execute at some time in the future. How far into the future this
|
||||||
|
* practically ends up being is determined by how long it takes for
|
||||||
|
* other registered jobs to finish, and what the tick interval is.
|
||||||
|
* .Pp
|
||||||
|
* This function takes a job function and a pointer to pass to that
|
||||||
|
* function when it is executed.
|
||||||
|
*/
|
||||||
|
extern void
|
||||||
|
CronOnce(Cron *, JobFunc *, void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule a repetitive task to be executed at aproximately the given
|
||||||
|
* interval. As stated above, this is as fuzzy interval; depending on
|
||||||
|
* the jobs being run and the tick interval, tasks may not execute at
|
||||||
|
* exactly the scheduled time, but they will eventually execute.
|
||||||
|
* .Pp
|
||||||
|
* This function takes an interval in milliseconds, a job function,
|
||||||
|
* and a pointer to pass to that function when it is executed.
|
||||||
|
*/
|
||||||
|
extern void
|
||||||
|
CronEvery(Cron *, unsigned long, JobFunc *, void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start ticking the clock and executing registered jobs.
|
||||||
|
*/
|
||||||
|
extern void
|
||||||
|
CronStart(Cron *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop ticking the clock. Jobs that are already executing will
|
||||||
|
* continue to execute, but when they are finished, no new jobs will
|
||||||
|
* be executed until
|
||||||
|
* .Fn CronStart
|
||||||
|
* is called.
|
||||||
|
*/
|
||||||
|
extern void
|
||||||
|
CronStop(Cron *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discard all job references and free all memory associated with the
|
||||||
|
* given
|
||||||
|
* .Nm Cron
|
||||||
|
* instance.
|
||||||
|
*/
|
||||||
|
extern void
|
||||||
|
CronFree(Cron *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_CRON_H */
|
169
src/include/Db.h
Normal file
169
src/include/Db.h
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_DB_H
|
||||||
|
#define CYTOPLASM_DB_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Db
|
||||||
|
* @Nd A minimal flat-file database with mutex locking and cache.
|
||||||
|
* @Dd April 27 2023
|
||||||
|
* @Xr Json
|
||||||
|
*
|
||||||
|
* Cytoplasm operates on a flat-file database instead of a
|
||||||
|
* traditional relational database. This greatly simplifies the
|
||||||
|
* persistent storage code, and creates a relatively basic API,
|
||||||
|
* described here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include <HashMap.h>
|
||||||
|
#include <Array.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All functions in this API operate on a database structure that is
|
||||||
|
* opaque to the caller.
|
||||||
|
*/
|
||||||
|
typedef struct Db Db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When an object is locked, a reference is returned. This reference
|
||||||
|
* is owned by the current thread, and the database is inaccessible to
|
||||||
|
* other threads until all references have been returned to the
|
||||||
|
* database.
|
||||||
|
* .Pp
|
||||||
|
* This reference is opaque, but can be manipulated by the functions
|
||||||
|
* defined here.
|
||||||
|
*/
|
||||||
|
typedef struct DbRef DbRef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a data directory. This function takes a path to open, and a
|
||||||
|
* cache size in bytes. If the cache size is 0, then caching is
|
||||||
|
* disabled and objects are loaded off the disk every time they are
|
||||||
|
* locked. Otherwise, objects are stored in the cache, and they are
|
||||||
|
* evicted in a least-recently-used manner.
|
||||||
|
*/
|
||||||
|
extern Db * DbOpen(char *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the database. This function will flush anything in the cache
|
||||||
|
* to the disk, and then close the data directory. It assumes that
|
||||||
|
* all references have been unlocked. If a reference has not been
|
||||||
|
* unlocked, undefined behavior results.
|
||||||
|
*/
|
||||||
|
extern void DbClose(Db *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the maximum cache size allowed before
|
||||||
|
* .Nm
|
||||||
|
* starts evicting old objects. If this is set to 0, everything in the
|
||||||
|
* cache is immediately evicted and caching is disabled. If the
|
||||||
|
* database was opened with a cache size of 0, setting this will
|
||||||
|
* initialize the cache, and subsequent calls to
|
||||||
|
* .Fn DbLock
|
||||||
|
* will begin caching objects.
|
||||||
|
*/
|
||||||
|
extern void DbMaxCacheSet(Db *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new object in the database with the specified name. This
|
||||||
|
* function will fail if the object already exists in the database. It
|
||||||
|
* takes a variable number of C strings, with the exact number being
|
||||||
|
* specified by the second parameter. These C strings are used to
|
||||||
|
* generate a filesystem path at which to store the object. These paths
|
||||||
|
* ensure each object is uniquely identifiable, and provides semantic
|
||||||
|
* meaning to an object.
|
||||||
|
*/
|
||||||
|
extern DbRef * DbCreate(Db *, size_t,...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock an existing object in the database. This function will fail
|
||||||
|
* if the object does not exist. It takes a variable number of C
|
||||||
|
* strings, with the exact number being specified by the second
|
||||||
|
* parameter. These C strings are used to generate the filesystem path
|
||||||
|
* at which to load the object. These paths ensure each object is
|
||||||
|
* uniquely identifiable, and provides semantic meaning to an object.
|
||||||
|
*/
|
||||||
|
extern DbRef * DbLock(Db *, size_t,...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately and permanently remove an object from the database.
|
||||||
|
* This function assumes the object is not locked, otherwise undefined
|
||||||
|
* behavior will result.
|
||||||
|
*/
|
||||||
|
extern int DbDelete(Db *, size_t,...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlock an object and return it back to the database. This function
|
||||||
|
* immediately syncs the object to the filesystem. The cache is a
|
||||||
|
* read cache; writes are always immediate to ensure data integrity in
|
||||||
|
* the event of a system failure.
|
||||||
|
*/
|
||||||
|
extern int DbUnlock(Db *, DbRef *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the existence of the given database object in a more efficient
|
||||||
|
* manner than attempting to lock it with
|
||||||
|
* .Fn DbLock .
|
||||||
|
* This function does not lock the object, nor does it load it into
|
||||||
|
* memory if it exists.
|
||||||
|
*/
|
||||||
|
extern int DbExists(Db *, size_t,...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all of the objects at a given path. Unlike the other varargs
|
||||||
|
* functions, this one does not take a path to a specific object; it
|
||||||
|
* takes a directory to be iterated, where each path part is its own
|
||||||
|
* C string. Note that the resulting list only contains the objects
|
||||||
|
* in the specified directory, it does not list any subdirectories.
|
||||||
|
* .Pp
|
||||||
|
* The array returned is an array of C strings containing the object
|
||||||
|
* name.
|
||||||
|
*/
|
||||||
|
extern Array * DbList(Db *, size_t,...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free the list returned by
|
||||||
|
* .Fn DbListFree .
|
||||||
|
*/
|
||||||
|
extern void DbListFree(Array *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a database reference into JSON that can be manipulated.
|
||||||
|
* At this time, the database actually stores objects as JSON on the
|
||||||
|
* disk, so this function just returns an internal pointer, but in the
|
||||||
|
* future it may have to be generated by decompressing a binary blob,
|
||||||
|
* or something of that nature.
|
||||||
|
*/
|
||||||
|
extern HashMap * DbJson(DbRef *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free the existing JSON associated with the given reference, and
|
||||||
|
* replace it with new JSON. This is more efficient than duplicating
|
||||||
|
* a separate object into the database reference.
|
||||||
|
*/
|
||||||
|
extern int DbJsonSet(DbRef *, HashMap *);
|
||||||
|
|
||||||
|
#endif
|
167
src/include/HashMap.h
Normal file
167
src/include/HashMap.h
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CYTOPLASM_HASHMAP_H
|
||||||
|
#define CYTOPLASM_HASHMAP_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm HashMap
|
||||||
|
* @Nd A simple hash map implementation.
|
||||||
|
* @Dd October 11 2022
|
||||||
|
* @Xr Array Queue
|
||||||
|
*
|
||||||
|
* This is the public interface for Cytoplasm's hash map
|
||||||
|
* implementation. This hash map is designed to be simple,
|
||||||
|
* well-documented, and generally readable and understandable, yet also
|
||||||
|
* performant enough to be useful, because it is used extensively
|
||||||
|
* throughout the project.
|
||||||
|
* .Pp
|
||||||
|
* Fundamentally, this is an entirely generic map implementation. It
|
||||||
|
* can be used for many general purposes, but it is designed only to
|
||||||
|
* implement the features Cytoplasm needs to be functional. One
|
||||||
|
* example of a Cytoplasm-specific feature is that keys cannot be
|
||||||
|
* arbitrary data; they are NULL-terminated C strings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These functions operate on an opaque structure, which the caller
|
||||||
|
* has no knowledge about.
|
||||||
|
*/
|
||||||
|
typedef struct HashMap HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new hash map that is ready to be used with the rest of the
|
||||||
|
* functions defined here.
|
||||||
|
*/
|
||||||
|
extern HashMap * HashMapCreate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free the specified hash map such that it becomes invalid and any
|
||||||
|
* future use results in undefined behavior. Note that this function
|
||||||
|
* does not free the values stored in the hash map, but since it stores
|
||||||
|
* the keys internally, it will free the keys. You should use
|
||||||
|
* .Fn HashMapIterate
|
||||||
|
* to free the values stored in this map appropriately before calling
|
||||||
|
* this function.
|
||||||
|
*/
|
||||||
|
extern void HashMapFree(HashMap *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control the maximum load of the hash map before it is expanded.
|
||||||
|
* When the hash map reaches the given capacity, it is grown. You
|
||||||
|
* don't want to only grow hash maps when they are full, because that
|
||||||
|
* makes them perform very poorly. The maximum load value is a
|
||||||
|
* percentage of how full the hash map is, and it should be between
|
||||||
|
* 0 and 1, where 0 means that no elements will cause the map to be
|
||||||
|
* expanded, and 1 means that the hash map must be completely full
|
||||||
|
* before it is expanded. The default maximum load on a new hash map
|
||||||
|
* is 0.75, which should be good enough for most purposes, however,
|
||||||
|
* this function exists specifically so that the maximum load can be
|
||||||
|
* fine-tuned.
|
||||||
|
*/
|
||||||
|
extern void HashMapMaxLoadSet(HashMap *, float);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use a custom hashing function with the given hash map. New hash
|
||||||
|
* maps have a sane hashing function that should work okay for most
|
||||||
|
* use cases, but if you have a better hashing function, it can be
|
||||||
|
* specified this way. Do not change the hash function after keys have
|
||||||
|
* been added; doing so results in undefined behavior. Only set a new
|
||||||
|
* hash function immediately after constructing a new hash map, before
|
||||||
|
* anything has been added to it.
|
||||||
|
* .Pp
|
||||||
|
* The hash function takes a pointer to a C string, and is expected
|
||||||
|
* to return a fairly unique numerical hash value which will be
|
||||||
|
* converted into an array index.
|
||||||
|
*/
|
||||||
|
extern void
|
||||||
|
HashMapFunctionSet(HashMap *, unsigned long (*) (const char *));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the given string key to the given value. Note that the key is
|
||||||
|
* copied into the hash map's own memory space, but the value is not.
|
||||||
|
* It is the caller's job to ensure that the value pointer remains
|
||||||
|
* valid for the life of the hash map, and are freed when no longer
|
||||||
|
* needed.
|
||||||
|
*/
|
||||||
|
extern void * HashMapSet(HashMap *, char *, void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the value for the given key, or return NULL if no such
|
||||||
|
* key exists in the hash map.
|
||||||
|
*/
|
||||||
|
extern void * HashMapGet(HashMap *, const char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a value specified by the given key from the hash map, and
|
||||||
|
* return it to the caller to deal with. This function returns NULL
|
||||||
|
* if no such key exists.
|
||||||
|
*/
|
||||||
|
extern void * HashMapDelete(HashMap *, const char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate over all the keys and values of a hash map. This function
|
||||||
|
* works very similarly to
|
||||||
|
* .Xr getopt 3 ,
|
||||||
|
* where calls are repeatedly made in a while loop until there are no
|
||||||
|
* more items to go over. The difference is that this function does not
|
||||||
|
* rely on globals; it takes pointer pointers, and stores all
|
||||||
|
* necessary state inside the hash map itself.
|
||||||
|
* .Pp
|
||||||
|
* Note that this function is not thread-safe; two threads cannot be
|
||||||
|
* iterating over any given hash map at the same time, though they
|
||||||
|
* can each be iterating over different hash maps.
|
||||||
|
* .Pp
|
||||||
|
* This function can be tricky to use in some scenarios, as it
|
||||||
|
* continues where it left off on each call, until there are no more
|
||||||
|
* elements to go through in the hash map. If you are not iterating
|
||||||
|
* over the entire map in one go, and happen to break the loop, then
|
||||||
|
* the next time you attempt to iterate the hash map, you'll start
|
||||||
|
* somewhere in the middle, which is most likely not the intended
|
||||||
|
* behavior. Thus, it is always recommended to iterate over the entire
|
||||||
|
* hash map if you're going to use this function.
|
||||||
|
* .Pp
|
||||||
|
* Also note that the behavior of this function is undefined if
|
||||||
|
* insertions or deletions occur during the iteration. This
|
||||||
|
* functionality has not been tested, and will likely not work.
|
||||||
|
*/
|
||||||
|
extern int HashMapIterate(HashMap *, char **, void **);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reentrant version of
|
||||||
|
* .Fn HashMapIterate
|
||||||
|
* that allows the caller to overcome the flaws of that function by
|
||||||
|
* storing the cursor outside of the hash map structure itself. This
|
||||||
|
* allows multiple threads to iterate over the same hash map at the
|
||||||
|
* same time, and it allows the iteration to be halted midway through
|
||||||
|
* without causing any unintended side effects.
|
||||||
|
* .Pp
|
||||||
|
* The cursor should be initialized to 0 at the start of iteration.
|
||||||
|
*/
|
||||||
|
extern int
|
||||||
|
HashMapIterateReentrant(HashMap *, char **, void **, size_t *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_HASHMAP_H */
|
125
src/include/HeaderParser.h
Normal file
125
src/include/HeaderParser.h
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_HEADERPARSER_H
|
||||||
|
#define CYTOPLASM_HEADERPARSER_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm HeaderParser
|
||||||
|
* @Nd Parse simple C header files.
|
||||||
|
* @Dd April 29 2023
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* is an extremely simple parser that lacks most of the functionality
|
||||||
|
* one would expect from a C code parser. It simply maps a stream
|
||||||
|
* of tokens into a few categories, parsing major ``milestones'' in
|
||||||
|
* a header, without actually understanding any of the details.
|
||||||
|
* .Pp
|
||||||
|
* This exists because it is used to generate man pages from headers.
|
||||||
|
* See
|
||||||
|
* .Xr hdoc 1
|
||||||
|
* for example usage of this parser.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Stream.h>
|
||||||
|
#include <Array.h>
|
||||||
|
|
||||||
|
#define HEADER_EXPR_MAX 4096
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Headers are parsed as expressions. These are the expressions that
|
||||||
|
* this parser recognizes.
|
||||||
|
*/
|
||||||
|
typedef enum HeaderExprType
|
||||||
|
{
|
||||||
|
HP_COMMENT,
|
||||||
|
HP_PREPROCESSOR_DIRECTIVE,
|
||||||
|
HP_TYPEDEF,
|
||||||
|
HP_DECLARATION,
|
||||||
|
HP_GLOBAL,
|
||||||
|
HP_UNKNOWN,
|
||||||
|
HP_SYNTAX_ERROR,
|
||||||
|
HP_PARSE_ERROR,
|
||||||
|
HP_EOF
|
||||||
|
} HeaderExprType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a function declaration.
|
||||||
|
*/
|
||||||
|
typedef struct HeaderDeclaration
|
||||||
|
{
|
||||||
|
char returnType[64];
|
||||||
|
char name[32]; /* Enforced by ANSI C */
|
||||||
|
Array *args;
|
||||||
|
} HeaderDeclaration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A global variable declaration. The type must be of the same size
|
||||||
|
* as the function declaration's return type due to the way parsing
|
||||||
|
* them is implemented.
|
||||||
|
*/
|
||||||
|
typedef struct HeaderGlobal
|
||||||
|
{
|
||||||
|
char type[64];
|
||||||
|
char name[HEADER_EXPR_MAX - 64];
|
||||||
|
} HeaderGlobal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a single header expression. Note that that state
|
||||||
|
* structure is entirely internally managed, so it should not be
|
||||||
|
* accessed or manipulated by functions outside the functions defined
|
||||||
|
* here.
|
||||||
|
* .Pp
|
||||||
|
* The type field should be used to determine which field in the data
|
||||||
|
* union is valid.
|
||||||
|
*/
|
||||||
|
typedef struct HeaderExpr
|
||||||
|
{
|
||||||
|
HeaderExprType type;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
char text[HEADER_EXPR_MAX];
|
||||||
|
HeaderDeclaration declaration;
|
||||||
|
HeaderGlobal global;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
int lineNo;
|
||||||
|
char *msg;
|
||||||
|
} error;
|
||||||
|
} data;
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
Stream *stream;
|
||||||
|
int lineNo;
|
||||||
|
} state;
|
||||||
|
} HeaderExpr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the next expression into the given header expression structure.
|
||||||
|
* To parse an entire C header, this function should be called in a
|
||||||
|
* loop until the type of the expression is HP_EOF.
|
||||||
|
*/
|
||||||
|
extern void HeaderParse(Stream *, HeaderExpr *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_HEADERPARSER_H */
|
211
src/include/Http.h
Normal file
211
src/include/Http.h
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_HTTP_H
|
||||||
|
#define CYTOPLASM_HTTP_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Http
|
||||||
|
* @Nd Encode and decode various parts of the HTTP protocol.
|
||||||
|
* @Dd March 12 2023
|
||||||
|
* @Xr HttpClient HttpServer HashMap Queue Memory
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* is a collection of utility functions and type definitions that are
|
||||||
|
* useful for dealing with HTTP. HTTP is not a complex protocol, but
|
||||||
|
* this API makes it a lot easier to work with.
|
||||||
|
* .Pp
|
||||||
|
* Note that this API doesn't target any particular HTTP version, but
|
||||||
|
* it is currently used with HTTP 1.0 clients and servers, and
|
||||||
|
* therefore may be lacking functionality added in later HTTP versions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <HashMap.h>
|
||||||
|
#include <Stream.h>
|
||||||
|
|
||||||
|
#define HTTP_FLAG_NONE 0
|
||||||
|
#define HTTP_FLAG_TLS (1 << 0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request methods defined by the HTTP standard. These numeric
|
||||||
|
* constants should be preferred to strings when building HTTP APIs
|
||||||
|
* because they are more efficient.
|
||||||
|
*/
|
||||||
|
typedef enum HttpRequestMethod
|
||||||
|
{
|
||||||
|
HTTP_METHOD_UNKNOWN,
|
||||||
|
HTTP_GET,
|
||||||
|
HTTP_HEAD,
|
||||||
|
HTTP_POST,
|
||||||
|
HTTP_PUT,
|
||||||
|
HTTP_DELETE,
|
||||||
|
HTTP_CONNECT,
|
||||||
|
HTTP_OPTIONS,
|
||||||
|
HTTP_TRACE,
|
||||||
|
HTTP_PATCH
|
||||||
|
} HttpRequestMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enumeration that corresponds to the actual integer values of the
|
||||||
|
* valid HTTP response codes.
|
||||||
|
*/
|
||||||
|
typedef enum HttpStatus
|
||||||
|
{
|
||||||
|
HTTP_STATUS_UNKNOWN = 0,
|
||||||
|
|
||||||
|
/* Informational responses */
|
||||||
|
HTTP_CONTINUE = 100,
|
||||||
|
HTTP_SWITCHING_PROTOCOLS = 101,
|
||||||
|
HTTP_EARLY_HINTS = 103,
|
||||||
|
|
||||||
|
/* Successful responses */
|
||||||
|
HTTP_OK = 200,
|
||||||
|
HTTP_CREATED = 201,
|
||||||
|
HTTP_ACCEPTED = 202,
|
||||||
|
HTTP_NON_AUTHORITATIVE_INFORMATION = 203,
|
||||||
|
HTTP_NO_CONTENT = 204,
|
||||||
|
HTTP_RESET_CONTENT = 205,
|
||||||
|
HTTP_PARTIAL_CONTENT = 206,
|
||||||
|
|
||||||
|
/* Redirection messages */
|
||||||
|
HTTP_MULTIPLE_CHOICES = 300,
|
||||||
|
HTTP_MOVED_PERMANENTLY = 301,
|
||||||
|
HTTP_FOUND = 302,
|
||||||
|
HTTP_SEE_OTHER = 303,
|
||||||
|
HTTP_NOT_MODIFIED = 304,
|
||||||
|
HTTP_TEMPORARY_REDIRECT = 307,
|
||||||
|
HTTP_PERMANENT_REDIRECT = 308,
|
||||||
|
|
||||||
|
/* Client error messages */
|
||||||
|
HTTP_BAD_REQUEST = 400,
|
||||||
|
HTTP_UNAUTHORIZED = 401,
|
||||||
|
HTTP_FORBIDDEN = 403,
|
||||||
|
HTTP_NOT_FOUND = 404,
|
||||||
|
HTTP_METHOD_NOT_ALLOWED = 405,
|
||||||
|
HTTP_NOT_ACCEPTABLE = 406,
|
||||||
|
HTTP_PROXY_AUTH_REQUIRED = 407,
|
||||||
|
HTTP_REQUEST_TIMEOUT = 408,
|
||||||
|
HTTP_CONFLICT = 409,
|
||||||
|
HTTP_GONE = 410,
|
||||||
|
HTTP_LENGTH_REQUIRED = 411,
|
||||||
|
HTTP_PRECONDITION_FAILED = 412,
|
||||||
|
HTTP_PAYLOAD_TOO_LARGE = 413,
|
||||||
|
HTTP_URI_TOO_LONG = 414,
|
||||||
|
HTTP_UNSUPPORTED_MEDIA_TYPE = 415,
|
||||||
|
HTTP_RANGE_NOT_SATISFIABLE = 416,
|
||||||
|
HTTP_EXPECTATION_FAILED = 417,
|
||||||
|
HTTP_TEAPOT = 418,
|
||||||
|
HTTP_UPGRADE_REQUIRED = 426,
|
||||||
|
HTTP_PRECONDITION_REQUIRED = 428,
|
||||||
|
HTTP_TOO_MANY_REQUESTS = 429,
|
||||||
|
HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
|
||||||
|
HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451,
|
||||||
|
|
||||||
|
/* Server error responses */
|
||||||
|
HTTP_INTERNAL_SERVER_ERROR = 500,
|
||||||
|
HTTP_NOT_IMPLEMENTED = 501,
|
||||||
|
HTTP_BAD_GATEWAY = 502,
|
||||||
|
HTTP_SERVICE_UNAVAILABLE = 503,
|
||||||
|
HTTP_GATEWAY_TIMEOUT = 504,
|
||||||
|
HTTP_VERSION_NOT_SUPPORTED = 505,
|
||||||
|
HTTP_VARIANT_ALSO_NEGOTIATES = 506,
|
||||||
|
HTTP_NOT_EXTENDED = 510,
|
||||||
|
HTTP_NETWORK_AUTH_REQUIRED = 511
|
||||||
|
} HttpStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an HTTP status enumeration value into a string description
|
||||||
|
* of the status, which is to be used in server response to a client,
|
||||||
|
* or a client response to a user. For example, calling
|
||||||
|
* .Fn HttpStatusToString "HTTP_GATEWAY_TIMEOUT"
|
||||||
|
* (or
|
||||||
|
* .Fn HttpStatusToString "504" )
|
||||||
|
* produces the string "Gateway Timeout". Note that the returned
|
||||||
|
* pointers point to static space, so their manipulation is forbidden.
|
||||||
|
*/
|
||||||
|
extern const char * HttpStatusToString(const HttpStatus);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a string into a numeric code that can be used throughout
|
||||||
|
* the code of a program in an efficient manner. See the definition
|
||||||
|
* of HttpRequestMethod. This function does case-sensitive matching,
|
||||||
|
* and does not trim or otherwise process the input string.
|
||||||
|
*/
|
||||||
|
extern HttpRequestMethod HttpRequestMethodFromString(const char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a numeric code as defined by HttpRequestMethod into a
|
||||||
|
* string that can be sent to a server. Note that the returned pointers
|
||||||
|
* point to static space, so their manipulation is forbidden.
|
||||||
|
*/
|
||||||
|
extern const char * HttpRequestMethodToString(const HttpRequestMethod);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a C string such that it can safely appear in a URL by
|
||||||
|
* performing the necessary percent escaping. A new string on the
|
||||||
|
* heap is returned. It should be freed with
|
||||||
|
* .Fn Free ,
|
||||||
|
* defined in the
|
||||||
|
* .Xr Memory 3
|
||||||
|
* API.
|
||||||
|
*/
|
||||||
|
extern char * HttpUrlEncode(char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a percent-encoded string into a C string, ignoring encoded
|
||||||
|
* null characters entirely, because those would do nothing but cause
|
||||||
|
* problems.
|
||||||
|
*/
|
||||||
|
extern char * HttpUrlDecode(char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode an encoded parameter string in the form of
|
||||||
|
* ``key=val&key2=val2'' into a hash map whose values are C strings.
|
||||||
|
* This function properly decodes keys and values using the functions
|
||||||
|
* defined above.
|
||||||
|
*/
|
||||||
|
extern HashMap * HttpParamDecode(char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a hash map whose values are strings as an HTTP parameter
|
||||||
|
* string suitable for GET or POST requests.
|
||||||
|
*/
|
||||||
|
extern char * HttpParamEncode(HashMap *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read HTTP headers from a stream and return a hash map whose values
|
||||||
|
* are strings. All keys are lowercased to make querying them
|
||||||
|
* consistent and not dependent on the case that was read from the
|
||||||
|
* stream. This is useful for both client and server code, since the
|
||||||
|
* headers are in the same format. This function should be used after
|
||||||
|
* parsing the HTTP status line, because it does not parse that line.
|
||||||
|
* It will stop when it encounters the first blank line, which
|
||||||
|
* indicates that the body is beginning. After this function completes,
|
||||||
|
* the body may be immediately read from the stream without any
|
||||||
|
* additional processing.
|
||||||
|
*/
|
||||||
|
extern HashMap * HttpParseHeaders(Stream *);
|
||||||
|
|
||||||
|
#endif
|
104
src/include/HttpClient.h
Normal file
104
src/include/HttpClient.h
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_HTTPCLIENT_H
|
||||||
|
#define CYTOPLASM_HTTPCLIENT_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm HttpClient
|
||||||
|
* @Nd Extremely simple HTTP client.
|
||||||
|
* @Dd April 29 2023
|
||||||
|
* @Xr Http HttpServer Tls
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* HttpClient
|
||||||
|
* builds on the HTTP API to provide a simple yet functional HTTP
|
||||||
|
* client. It aims at being easy to use and minimal, yet also
|
||||||
|
* efficient.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <HashMap.h>
|
||||||
|
#include <Http.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A server response is represented by a client context. It is
|
||||||
|
* opaque, so the functions defined in this API should be used to
|
||||||
|
* fetch data from and manipulate it.
|
||||||
|
*/
|
||||||
|
typedef struct HttpClientContext HttpClientContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make an HTTP request. This function takes the request method,
|
||||||
|
* any flags defined in the HTTP API, the port, hostname, and path,
|
||||||
|
* all in that order. It returns NULL if there was an error making
|
||||||
|
* the request. Otherwise it returns a client context. Note that this
|
||||||
|
* function does not actually send any data, it simply makes the
|
||||||
|
* connection. Use
|
||||||
|
* .Fn HttpRequestHeader
|
||||||
|
* to add headers to the request. Then, send headers with
|
||||||
|
* .Fn HttpRequestSendHeaders .
|
||||||
|
* Finally, the request body, if any, can be written to the output
|
||||||
|
* stream, and then the request can be fully sent using
|
||||||
|
* .Fn HttpRequestSend .
|
||||||
|
*/
|
||||||
|
extern HttpClientContext *
|
||||||
|
HttpRequest(HttpRequestMethod, int, unsigned short, char *, char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a request header to send to the server when making the
|
||||||
|
* request.
|
||||||
|
*/
|
||||||
|
extern void HttpRequestHeader(HttpClientContext *, char *, char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the request headers to the server. This must be called before
|
||||||
|
* the request body can be written or a response body can be read.
|
||||||
|
*/
|
||||||
|
extern void HttpRequestSendHeaders(HttpClientContext *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush the request stream to the server. This function should be
|
||||||
|
* called before the response body is read.
|
||||||
|
*/
|
||||||
|
extern HttpStatus HttpRequestSend(HttpClientContext *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the headers returned by the server.
|
||||||
|
*/
|
||||||
|
extern HashMap * HttpResponseHeaders(HttpClientContext *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the stream used to write the request body and read the
|
||||||
|
* response body.
|
||||||
|
*/
|
||||||
|
extern Stream * HttpClientStream(HttpClientContext *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free all memory associated with the client context. This closes the
|
||||||
|
* connection, if it was still open.
|
||||||
|
*/
|
||||||
|
extern void HttpClientContextFree(HttpClientContext *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_HTTPCLIENT_H */
|
91
src/include/HttpRouter.h
Normal file
91
src/include/HttpRouter.h
Normal 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.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_HTTPROUTER_H
|
||||||
|
#define CYTOPLASM_HTTPROUTER_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm HttpRouter
|
||||||
|
* @Nd Simple HTTP request router with regular expression support.
|
||||||
|
* @Dd April 29 2023
|
||||||
|
* @Xr HttpServer Http
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* provides a simple mechanism for assigning functions to an HTTP
|
||||||
|
* request path. It is a simple tree data structure that parses the
|
||||||
|
* registered request paths and maps functions onto each part of the
|
||||||
|
* path. Then, requests can be easily routed to their appropriate
|
||||||
|
* handler functions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Array.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The router structure is opaque and thus managed entirely by the
|
||||||
|
* functions defined in this API.
|
||||||
|
*/
|
||||||
|
typedef struct HttpRouter HttpRouter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function written to handle an HTTP request takes an array
|
||||||
|
* consisting of the matched path parts in the order they appear in
|
||||||
|
* the path, and a pointer to caller-provided arguments, if any.
|
||||||
|
* It returns a pointer that the caller is assumed to know how to
|
||||||
|
* handle.
|
||||||
|
*/
|
||||||
|
typedef void *(HttpRouteFunc) (Array *, void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new empty routing tree.
|
||||||
|
*/
|
||||||
|
extern HttpRouter * HttpRouterCreate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free all the memory associated with the given routing tree.
|
||||||
|
*/
|
||||||
|
extern void HttpRouterFree(HttpRouter *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the specified route function to be executed upon requests
|
||||||
|
* for the specified HTTP path. The path is parsed by splitting at
|
||||||
|
* each path separator. Each part of the path is a regular expression
|
||||||
|
* that matches the entire path part. A regular expression cannot
|
||||||
|
* match more than one path part. This allows for paths like
|
||||||
|
* .Pa /some/path/(.*)/parts
|
||||||
|
* to work as one would expect.
|
||||||
|
*/
|
||||||
|
extern int HttpRouterAdd(HttpRouter *, char *, HttpRouteFunc *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route the specified request path using the specified routing
|
||||||
|
* tree. This function will parse the path and match it to the
|
||||||
|
* appropriate route handler function. The return value is a boolean
|
||||||
|
* value that indicates whether or not an appropriate route function
|
||||||
|
* was found. If an appropriate function was found, then the void
|
||||||
|
* pointer is passed to it as arguments that it is expected to know
|
||||||
|
* how to handle, and the pointer to a void pointer is where the
|
||||||
|
* route function's response will be placed.
|
||||||
|
*/
|
||||||
|
extern int HttpRouterRoute(HttpRouter *, char *, void *, void **);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_HTTPROUTER_H */
|
234
src/include/HttpServer.h
Normal file
234
src/include/HttpServer.h
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_HTTPSERVER_H
|
||||||
|
#define CYTOPLASM_HTTPSERVER_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm HttpServer
|
||||||
|
* @Nd Extremely simple HTTP server.
|
||||||
|
* @Dd December 13 2022
|
||||||
|
* @Xr Http HttpClient
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* builds on the
|
||||||
|
* .Xr Http 3
|
||||||
|
* API, and provides a very simple, yet very functional API for
|
||||||
|
* creating an HTTP server. It aims at being easy to use and minimal,
|
||||||
|
* yet also efficient. It uses non-blocking I/O, is fully
|
||||||
|
* multi-threaded, and is very configurable. It can be set up in just
|
||||||
|
* two function calls and minimal supporting code.
|
||||||
|
* .Pp
|
||||||
|
* This API should be familar to those that have dealt with the HTTP
|
||||||
|
* server libraries of other programming languages, particularly Java.
|
||||||
|
* In fact, much of the terminology used in this API came from Java,
|
||||||
|
* and you'll notice that the way responses are sent and received very
|
||||||
|
* closely resembles Java.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Http.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <HashMap.h>
|
||||||
|
#include <Stream.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The functions on this API operate on an opaque structure.
|
||||||
|
*/
|
||||||
|
typedef struct HttpServer HttpServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each request receives a context structure. It is opaque, so the
|
||||||
|
* functions defined in this API should be used to fetch data from
|
||||||
|
* it. These functions allow the handler to figure out the context of
|
||||||
|
* the request, which includes the path requested, any parameters,
|
||||||
|
* and the headers and method used to make the request. The context
|
||||||
|
* also provides the means by which the handler responds to the
|
||||||
|
* request, allowing it to set the status code, headers, and body.
|
||||||
|
*/
|
||||||
|
typedef struct HttpServerContext HttpServerContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request handler function is executed when an HTTP request is
|
||||||
|
* received. It takes a request context, and a pointer as specified
|
||||||
|
* in the server configuration.
|
||||||
|
*/
|
||||||
|
typedef void (HttpHandler) (HttpServerContext *, void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of arguments to
|
||||||
|
* .Fn HttpServerCreate
|
||||||
|
* has grown so large that arguments are now stuffed into a
|
||||||
|
* configuration structure, which is in turn passed to
|
||||||
|
* .Fn HttpServerCreate .
|
||||||
|
* This configuration is copied by value into the internal
|
||||||
|
* structures of the server. It is copied with very minimal
|
||||||
|
* validation, so ensure that all values are sensible. It may
|
||||||
|
* make sense to use
|
||||||
|
* .Fn memset
|
||||||
|
* to zero out everything in here before assigning values.
|
||||||
|
*/
|
||||||
|
typedef struct HttpServerConfig
|
||||||
|
{
|
||||||
|
unsigned short port;
|
||||||
|
unsigned int threads;
|
||||||
|
unsigned int maxConnections;
|
||||||
|
|
||||||
|
int flags; /* Http(3) flags */
|
||||||
|
char *tlsCert; /* File path */
|
||||||
|
char *tlsKey; /* File path */
|
||||||
|
|
||||||
|
HttpHandler *handler;
|
||||||
|
void *handlerArgs;
|
||||||
|
} HttpServerConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new HTTP server using the specified configuration.
|
||||||
|
* This will set up all internal structures used by the server,
|
||||||
|
* and bind the socket and start listening for connections. However,
|
||||||
|
* it will not start accepting connections.
|
||||||
|
*/
|
||||||
|
extern HttpServer * HttpServerCreate(HttpServerConfig *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the configuration that was used to instantiate the given
|
||||||
|
* server. Note that this configuration is not necessarily the exact
|
||||||
|
* one that was provided; even though its values are the same, it
|
||||||
|
* should be treated as an entirely separate configuration with no
|
||||||
|
* connection to the original.
|
||||||
|
*/
|
||||||
|
extern HttpServerConfig * HttpServerConfigGet(HttpServer *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free the resources associated with the given HTTP server. Note that
|
||||||
|
* the server can only be freed after it has been stopped. Calling this
|
||||||
|
* function while the server is still running results in undefined
|
||||||
|
* behavior.
|
||||||
|
*/
|
||||||
|
extern void HttpServerFree(HttpServer *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to start the HTTP server, and return immediately with the
|
||||||
|
* status. This API is fully multi-threaded and asynchronous, so the
|
||||||
|
* caller can continue working while the HTTP server is running in a
|
||||||
|
* separate thread and managing a pool of threads to handle responses.
|
||||||
|
*/
|
||||||
|
extern int HttpServerStart(HttpServer *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typically, at some point after calling
|
||||||
|
* .Fn HttpServerStart ,
|
||||||
|
* the program will have no more work to do, so it will want to wait
|
||||||
|
* for the HTTP server to finish. This is accomplished via this
|
||||||
|
* function, which joins the HTTP worker thread to the calling thread,
|
||||||
|
* pausing the calling thread until the HTTP server has stopped.
|
||||||
|
*/
|
||||||
|
extern void HttpServerJoin(HttpServer *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the HTTP server. Only the execution of this function will
|
||||||
|
* cause the proper shutdown of the HTTP server. If the main program
|
||||||
|
* is joined to the HTTP thread, then either another thread or a
|
||||||
|
* signal handler will have to stop the server using this function.
|
||||||
|
* The typical use case is to install a signal handler that executes
|
||||||
|
* this function on a global HTTP server.
|
||||||
|
*/
|
||||||
|
extern void HttpServerStop(HttpServer *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the request headers for the request represented by the given
|
||||||
|
* context. The data in the returned hash map should be treated as
|
||||||
|
* read only and should not be freed; it is managed entirely by the
|
||||||
|
* server.
|
||||||
|
*/
|
||||||
|
extern HashMap * HttpRequestHeaders(HttpServerContext *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the request method used to make the request represented by
|
||||||
|
* the given context.
|
||||||
|
*/
|
||||||
|
extern HttpRequestMethod HttpRequestMethodGet(HttpServerContext *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the request path for the request represented by the given
|
||||||
|
* context. The return value of this function should be treated as
|
||||||
|
* read-only, and should not be freed; it is managed entirely by the
|
||||||
|
* server.
|
||||||
|
*/
|
||||||
|
extern char * HttpRequestPath(HttpServerContext *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the parsed GET parameters for the request represented by
|
||||||
|
* the given context. The returned hash map should be treated as
|
||||||
|
* read-only, and should not be freed; it is managed entirely by the
|
||||||
|
* server.
|
||||||
|
*/
|
||||||
|
extern HashMap * HttpRequestParams(HttpServerContext *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a response header to return to the client. The old value for
|
||||||
|
* the given header is returned, if any, otherwise NULL is returned.
|
||||||
|
*/
|
||||||
|
extern char * HttpResponseHeader(HttpServerContext *, char *, char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the response status to return to the client.
|
||||||
|
*/
|
||||||
|
extern void HttpResponseStatus(HttpServerContext *, HttpStatus);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current response status that will be sent to the client
|
||||||
|
* making the request represented by the given context.
|
||||||
|
*/
|
||||||
|
extern HttpStatus HttpResponseStatusGet(HttpServerContext *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the response headers to the client that made the request
|
||||||
|
* represented by the specified context. This function must be called
|
||||||
|
* before the response body can be written, otherwise a malformed
|
||||||
|
* response will be sent.
|
||||||
|
*/
|
||||||
|
extern void HttpSendHeaders(HttpServerContext *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a stream that is both readable and writable. Reading from the
|
||||||
|
* stream reads the request body that the client sent, if there is one.
|
||||||
|
* Note that the rquest headers have already been read, so the stream
|
||||||
|
* is correctly positioned at the beginning of the body of the request.
|
||||||
|
* .Fn HttpSendHeaders
|
||||||
|
* must be called before the stream is written, otherwise a malformed
|
||||||
|
* HTTP response will be sent. An HTTP handler should properly set all
|
||||||
|
* the headers it itends to send, send those headers, and then write
|
||||||
|
* the response body to this stream.
|
||||||
|
* .Pp
|
||||||
|
* Note that the stream does not need to be closed by the HTTP
|
||||||
|
* handler; in fact doing so results in undefined behavior. The stream
|
||||||
|
* is managed entirely by the server itself, so it will close it when
|
||||||
|
* necessary. This allows the underlying protocol to differ: for
|
||||||
|
* instance, an HTTP/1.1 connection may stay for multiple requests and
|
||||||
|
* responses.
|
||||||
|
*/
|
||||||
|
extern Stream * HttpServerStream(HttpServerContext *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_HTTPSERVER_H */
|
125
src/include/Int.h
Normal file
125
src/include/Int.h
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_INT_H
|
||||||
|
#define CYTOPLASM_INT_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Int
|
||||||
|
* @Nd Fixed-width integer types.
|
||||||
|
* @Dd April 27 2023
|
||||||
|
*
|
||||||
|
* This header provides cross-platform, fixed-width integer types.
|
||||||
|
* Specifically, it uses preprocessor magic to define the following
|
||||||
|
* types:
|
||||||
|
* .Bl -bullet -offset indent
|
||||||
|
* .It
|
||||||
|
* Int8 and UInt8
|
||||||
|
* .It
|
||||||
|
* Int16 and UInt16
|
||||||
|
* .It
|
||||||
|
* Int32 and UInt32
|
||||||
|
* .El
|
||||||
|
* .Pp
|
||||||
|
* Note that there is no 64-bit integer type, because the ANSI C
|
||||||
|
* standard makes no guarantee that such a type will exist, even
|
||||||
|
* though it does on most platforms.
|
||||||
|
* .Pp
|
||||||
|
* The reason Cytoplasm provides its own header for this is
|
||||||
|
* because ANSI C does not define fixed-width types, and while it
|
||||||
|
* should be safe to rely on C99 fixed-width types in most cases,
|
||||||
|
* there may be cases where even that is not possible.
|
||||||
|
*
|
||||||
|
* @ignore-typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#define BIT64_MAX 18446744073709551615
|
||||||
|
#define BIT32_MAX 4294967295
|
||||||
|
#define BIT16_MAX 65535
|
||||||
|
#define BIT8_MAX 255
|
||||||
|
|
||||||
|
#ifndef UCHAR_MAX
|
||||||
|
#error Size of char data type is unknown. Define UCHAR_MAX.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef USHRT_MAX
|
||||||
|
#error Size of short data type is unknown. Define USHRT_MAX.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef UINT_MAX
|
||||||
|
#error Size of int data type is unknown. Define UINT_MAX.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ULONG_MAX
|
||||||
|
#error Size of long data type is unknown. Define ULONG_MAX.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if UCHAR_MAX == BIT8_MAX
|
||||||
|
typedef signed char Int8;
|
||||||
|
typedef unsigned char UInt8;
|
||||||
|
|
||||||
|
#else
|
||||||
|
#error Unable to determine suitable data type for 8-bit integers.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if UINT_MAX == BIT16_MAX
|
||||||
|
typedef signed int Int16;
|
||||||
|
typedef unsigned int UInt16;
|
||||||
|
|
||||||
|
#elif USHRT_MAX == BIT16_MAX
|
||||||
|
typedef signed short Int16;
|
||||||
|
typedef unsigned short UInt16;
|
||||||
|
|
||||||
|
#elif UCHAR_MAX == BIT16_MAX
|
||||||
|
typedef signed char Int16;
|
||||||
|
typedef unsigned char UInt16;
|
||||||
|
|
||||||
|
#else
|
||||||
|
#error Unable to determine suitable data type for 16-bit integers.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ULONG_MAX == BIT32_MAX
|
||||||
|
typedef signed long Int32;
|
||||||
|
typedef unsigned long UInt32;
|
||||||
|
|
||||||
|
#elif UINT_MAX == BIT32_MAX
|
||||||
|
typedef signed int Int32;
|
||||||
|
typedef unsigned int UInt32;
|
||||||
|
|
||||||
|
#elif USHRT_MAX == BIT32_MAX
|
||||||
|
typedef signed short Int32;
|
||||||
|
typedef unsigned short UInt32;
|
||||||
|
|
||||||
|
#elif UCHAR_MAX == BIT32_MAX
|
||||||
|
typedef signed char Int32;
|
||||||
|
typedef unsigned char UInt32;
|
||||||
|
|
||||||
|
#else
|
||||||
|
#error Unable to determine suitable data type for 32-bit integers.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* The ANSI C standard only guarantees a data size of up to 32 bits. */
|
||||||
|
|
||||||
|
#endif
|
222
src/include/Io.h
Normal file
222
src/include/Io.h
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_IO_H
|
||||||
|
#define CYTOPLASM_IO_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Io
|
||||||
|
* @Nd Source/sink-agnostic I/O for implementing custom streams.
|
||||||
|
* @Dd April 29 2023
|
||||||
|
* @Xr Stream Tls
|
||||||
|
*
|
||||||
|
* Many systems provide platform-specific means of implementing custom
|
||||||
|
* streams using file pointers. However, POSIX does not define a way
|
||||||
|
* of programmatically creating custom streams.
|
||||||
|
* .Nm
|
||||||
|
* therefore fills this gap in POSIX by mimicking all of the
|
||||||
|
* functionality of these platform-specific functions, but in pure
|
||||||
|
* POSIX C. It defines a number of callback funtions to be executed
|
||||||
|
* in place of the standard POSIX I/O functions, which are used to
|
||||||
|
* implement arbitrary streams that may not be to a file or socket.
|
||||||
|
* Additionally, streams can now be pipelined; the sink of one stream
|
||||||
|
* may be the source of another lower-level stream. Additionally, all
|
||||||
|
* streams, regardless of their source or sink, share the same API, so
|
||||||
|
* streams can be handled in a much more generic manner. This allows
|
||||||
|
* the HTTP client and server libraries to seemlessly support TLS and
|
||||||
|
* plain connections without having to handle each separately.
|
||||||
|
* .Pp
|
||||||
|
* .Nm
|
||||||
|
* was heavily inspired by GNU's
|
||||||
|
* .Fn fopencookie
|
||||||
|
* and BSD's
|
||||||
|
* .Fn funopen .
|
||||||
|
* It aims to combine the best of both of these functions into a single
|
||||||
|
* API that is intuitive and easy to use.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifndef IO_BUFFER
|
||||||
|
#define IO_BUFFER 4096
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An opaque structure analogous to a POSIX file descriptor.
|
||||||
|
*/
|
||||||
|
typedef struct Io Io;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read input from the source of a stream. This function should
|
||||||
|
* attempt to read the specified number of bytes of data from the
|
||||||
|
* given cookie into the given buffer. It should behave identically
|
||||||
|
* to the POSIX
|
||||||
|
* .Xr read 2
|
||||||
|
* system call, except instead of using an integer descriptor as the
|
||||||
|
* first parameter, a pointer to an implementation-defined cookie
|
||||||
|
* stores any information the function needs to read from the source.
|
||||||
|
*/
|
||||||
|
typedef ssize_t (IoReadFunc) (void *, void *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write output to a sink. This function should attempt to write the
|
||||||
|
* specified number of bytes of data from the given buffer into the
|
||||||
|
* stream described by the given cookie. It should behave identically
|
||||||
|
* to the POSIX
|
||||||
|
* .Xr write 2
|
||||||
|
* system call, except instead of using an integer descriptor as the
|
||||||
|
* first parameter, a pointer to an implementation-defined cookie
|
||||||
|
* stores any information the function needs to write to the sink.
|
||||||
|
*/
|
||||||
|
typedef ssize_t (IoWriteFunc) (void *, void *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repositions the offset of the stream described by the specified
|
||||||
|
* cookie. This function should behave identically to the POSIX
|
||||||
|
* .Xr lseek 2
|
||||||
|
* system call, except instead of using an integer descriptor as the
|
||||||
|
* first parameter, a pointer to an implementation-defined cookie
|
||||||
|
* stores any information the function needs to seek the stream.
|
||||||
|
*/
|
||||||
|
typedef off_t (IoSeekFunc) (void *, off_t, int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the given stream, making future reads or writes result in
|
||||||
|
* undefined behavior. This function should also free all memory
|
||||||
|
* associated with the cookie. It should behave identically to the
|
||||||
|
* .Xr close 2
|
||||||
|
* system call, except instead of using an integer descriptor for the
|
||||||
|
* parameter, a pointer to an implementation-defined cookie stores any
|
||||||
|
* information the function needs to close the stream.
|
||||||
|
*/
|
||||||
|
typedef int (IoCloseFunc) (void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple mechanism for grouping together a set of stream functions,
|
||||||
|
* to be passed to
|
||||||
|
* .Fn IoCreate .
|
||||||
|
*/
|
||||||
|
typedef struct IoFunctions
|
||||||
|
{
|
||||||
|
IoReadFunc *read;
|
||||||
|
IoWriteFunc *write;
|
||||||
|
IoSeekFunc *seek;
|
||||||
|
IoCloseFunc *close;
|
||||||
|
} IoFunctions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new stream using the specified cookie and the specified
|
||||||
|
* I/O functions.
|
||||||
|
*/
|
||||||
|
extern Io * IoCreate(void *, IoFunctions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the specified number of bytes from the specified stream into
|
||||||
|
* the specified buffer. This calls the stream's underlying IoReadFunc,
|
||||||
|
* which should behave identically to the POSIX
|
||||||
|
* .Xr read 2
|
||||||
|
* system call.
|
||||||
|
*/
|
||||||
|
extern ssize_t IoRead(Io *, void *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the specified number of bytes from the specified stream into
|
||||||
|
* the specified buffer. This calls the stream's underlying
|
||||||
|
* IoWriteFunc, which should behave identically to the POSIX
|
||||||
|
* .Xr write 2
|
||||||
|
* system call.
|
||||||
|
*/
|
||||||
|
extern ssize_t IoWrite(Io *, void *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seek the specified stream using the specified offset and whence
|
||||||
|
* value. This calls the stream's underlying IoSeekFunc, which should
|
||||||
|
* behave identically to the POSIX
|
||||||
|
* .Xr lseek 2
|
||||||
|
* system call.
|
||||||
|
*/
|
||||||
|
extern off_t IoSeek(Io *, off_t, int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the specified stream. This calls the stream's underlying
|
||||||
|
* IoCloseFunc, which should behave identically to the POSIX
|
||||||
|
* .Xr close 2
|
||||||
|
* system call.
|
||||||
|
*/
|
||||||
|
extern int IoClose(Io *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a formatted string to the given stream. This is a
|
||||||
|
* re-implementation of the standard library function
|
||||||
|
* .Xr vfprintf 3 ,
|
||||||
|
* and behaves identically.
|
||||||
|
*/
|
||||||
|
extern int IoVprintf(Io *, const char *, va_list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a formatted string to the given stream. This is a
|
||||||
|
* re-implementation of the standard library function
|
||||||
|
* .Xr fprintf 3 ,
|
||||||
|
* and behaves identically.
|
||||||
|
*/
|
||||||
|
extern int IoPrintf(Io *, const char *,...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read all the bytes from the first stream and write them to the
|
||||||
|
* second stream. Neither stream is closed upon the completion of this
|
||||||
|
* function. This can be used for quick and convenient buffered
|
||||||
|
* copying of data from one stream into another.
|
||||||
|
*/
|
||||||
|
extern ssize_t IoCopy(Io *, Io *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a POSIX file descriptor to take advantage of this API. The
|
||||||
|
* common use case for this function is when a regular file descriptor
|
||||||
|
* needs to be accessed by an application that uses this API to also
|
||||||
|
* access non-POSIX streams.
|
||||||
|
*/
|
||||||
|
extern Io * IoFd(int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open or create a file for reading or writing. The specified file
|
||||||
|
* name is opened for reading or writing as specified by the given
|
||||||
|
* flags and mode. This function is a simple convenience wrapper around
|
||||||
|
* the POSIX
|
||||||
|
* .Xr open 2
|
||||||
|
* system call that passes the opened file descriptor into
|
||||||
|
* .Fn IoFd .
|
||||||
|
*/
|
||||||
|
extern Io * IoOpen(const char *, int, mode_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a standard C file pointer to take advantage of this API. The
|
||||||
|
* common use case for this function is when a regular C file pointer
|
||||||
|
* needs to be accessed by an application that uses this API to also
|
||||||
|
* access custom streams.
|
||||||
|
*/
|
||||||
|
extern Io * IoFile(FILE *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_IO_H */
|
322
src/include/Json.h
Normal file
322
src/include/Json.h
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CYTOPLASM_JSON_H
|
||||||
|
#define CYTOPLASM_JSON_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Json
|
||||||
|
* @Nd A fully-featured JSON API.
|
||||||
|
* @Dd March 12 2023
|
||||||
|
* @Xr HashMap Array Stream
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* is a fully-featured JSON API for C using the array and hash map
|
||||||
|
* APIs. It can parse JSON, ans serialize an in-memory structure to
|
||||||
|
* JSON. It build on the foundation of Array and HashMap because that's
|
||||||
|
* all JSON really is, just arrays and maps.
|
||||||
|
* .Nm
|
||||||
|
* also provides a structure for encapsulating an arbitrary value and
|
||||||
|
* identifying its type, making it easy for a strictly-typed language
|
||||||
|
* like C to work with loosely-typed JSON data.
|
||||||
|
* .Nm
|
||||||
|
* is very strict and tries to adhere as closely as possible to the
|
||||||
|
* proper definition of JSON. It will fail on syntax errors of any
|
||||||
|
* kind, which is fine for a Matrix homeserver that can just return
|
||||||
|
* M_BAD_JSON if anything in here fails, but this behavior may not be
|
||||||
|
* suitable for other purposes.
|
||||||
|
* .Pp
|
||||||
|
* This JSON implementation focuses primarily on serialization and
|
||||||
|
* deserialization to and from streams. It does not provide facilities
|
||||||
|
* for handling JSON strings; it only writes JSON to output streams,
|
||||||
|
* and reads them from input streams. Of course, you can use the
|
||||||
|
* POSIX
|
||||||
|
* .Xr fmemopen 3
|
||||||
|
* and
|
||||||
|
* .Xr open_memstream 3
|
||||||
|
* functions if you want to deal with JSON strings, but JSON is
|
||||||
|
* intended to be an exchange format. Data should be converted to JSON
|
||||||
|
* right when it is leaving the program, and converted from JSON to the
|
||||||
|
* in-memory format as soon as it is coming in.
|
||||||
|
* .Pp
|
||||||
|
* JSON objects are represented as hash maps consisting entirely of
|
||||||
|
* JsonValue structures, and arrays are represented as arrays
|
||||||
|
* consisting entirely of JsonValue structures. When generating a
|
||||||
|
* JSON object, any attempt to stuff a value into a hash map or array
|
||||||
|
* without first encoding it as a JsonValue will result in undefined
|
||||||
|
* behavior.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <HashMap.h>
|
||||||
|
#include <Array.h>
|
||||||
|
#include <Stream.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#define JSON_DEFAULT -1
|
||||||
|
#define JSON_PRETTY 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This opaque structure encapsulates all the possible types that can
|
||||||
|
* be stored in JSON. It is managed entirely by the functions defined
|
||||||
|
* in this API. It is important to note that strings, integers, floats,
|
||||||
|
* booleans, and the NULL value are all stored by value, but objects
|
||||||
|
* and arrays are stored by reference. That is, it doesn't store these
|
||||||
|
* itself, just pointers to them, however, the data
|
||||||
|
* .Em is
|
||||||
|
* freed when using
|
||||||
|
* .Fn JsonFree .
|
||||||
|
*/
|
||||||
|
typedef struct JsonValue JsonValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are the types that can be used to identify a JsonValue
|
||||||
|
* and act on it accordingly.
|
||||||
|
*/
|
||||||
|
typedef enum JsonType
|
||||||
|
{
|
||||||
|
JSON_NULL, /* Maps to a C NULL */
|
||||||
|
JSON_OBJECT, /* Maps to a HashMap of JsonValues */
|
||||||
|
JSON_ARRAY, /* Maps to an Array of JsonValues */
|
||||||
|
JSON_STRING, /* Maps to a null-terminated C string */
|
||||||
|
JSON_INTEGER, /* Maps to a C long */
|
||||||
|
JSON_FLOAT, /* Maps to a C double */
|
||||||
|
JSON_BOOLEAN /* Maps to a C integer of either 0 or 1 */
|
||||||
|
} JsonType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the type of the specified JSON value.
|
||||||
|
*/
|
||||||
|
extern JsonType JsonValueType(JsonValue *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a JSON object as a JSON value that can be added to another
|
||||||
|
* object, or an array.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueObject(HashMap *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unwrap a JSON value that represents an object. This function will
|
||||||
|
* return NULL if the value is not actually an object.
|
||||||
|
*/
|
||||||
|
extern HashMap * JsonValueAsObject(JsonValue *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a JSON array as a JSON value that can be added to an object
|
||||||
|
* or another array.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueArray(Array *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unwrap a JSON value that represents an array. This function will
|
||||||
|
* return NULL if the value is not actually an array.
|
||||||
|
*/
|
||||||
|
extern Array * JsonValueAsArray(JsonValue *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a C string as a JSON value that can be added to an object or
|
||||||
|
* an array.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueString(char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unwrap a JSON value that represents a string. This function will
|
||||||
|
* return NULL if the value is not actually a string.
|
||||||
|
*/
|
||||||
|
extern char * JsonValueAsString(JsonValue *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a number as a JSON value that can be added to an object or
|
||||||
|
* an array.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueInteger(long);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unwrap a JSON value that represents a number. This function will
|
||||||
|
* return 0 if the value is not actually a number, which may be
|
||||||
|
* misleading. Check the type of the value before making assumptions
|
||||||
|
* about its value.
|
||||||
|
*/
|
||||||
|
extern long JsonValueAsInteger(JsonValue *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a floating point number as a JSON value that can be added
|
||||||
|
* to an object or an array.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueFloat(double);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unwrap a JSON value that represents a floating point number. This
|
||||||
|
* function will return 0 if the value is not actually a floating
|
||||||
|
* point number, which may be misleading. Check the type of the value
|
||||||
|
* before making assumptions about its type.
|
||||||
|
*/
|
||||||
|
extern double JsonValueAsFloat(JsonValue *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a C integer according to the way C treats integers in boolean
|
||||||
|
* expressions as a JSON value that can be added to an object or an
|
||||||
|
* array.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueBoolean(int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unwrap a JSON value that represents a boolean. This function will
|
||||||
|
* return 0 if the value is not actually a boolean, which may be
|
||||||
|
* misleading. Check the type of the value before making assumptions
|
||||||
|
* about its type.
|
||||||
|
*/
|
||||||
|
extern int JsonValueAsBoolean(JsonValue *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a special case that represents a JSON null. Because the
|
||||||
|
* Array and HashMap APIs do not accept NULL values, this function
|
||||||
|
* should be used to represent NULL in JSON. Even though a small
|
||||||
|
* amount of memory is allocated just to be a placeholder for nothing,
|
||||||
|
* this keeps the APIs clean.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueNull(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free the memory being used by a JSON value. Note that this will
|
||||||
|
* recursively free all Arrays, HashMaps, and other JsonValues that are
|
||||||
|
* reachable from the given value, including any strings attached to
|
||||||
|
* this value.
|
||||||
|
*/
|
||||||
|
extern void JsonValueFree(JsonValue *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively duplicate the given JSON value. This returns a new
|
||||||
|
* JSON value that is completely identical to the specified value, but
|
||||||
|
* in no way connected to it.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueDuplicate(JsonValue *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively duplicate the given JSON object. This returns a new
|
||||||
|
* JSON object that is completely identical to the specified object,
|
||||||
|
* but in no way connect to it.
|
||||||
|
*/
|
||||||
|
extern HashMap * JsonDuplicate(HashMap *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively free a JSON object by iterating over all of its values
|
||||||
|
* and freeing them using
|
||||||
|
* .Fn JsonValueFree .
|
||||||
|
*/
|
||||||
|
extern void JsonFree(HashMap *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode the given string in such a way that it can be safely
|
||||||
|
* embedded in a JSON stream. This entails:
|
||||||
|
* .Bl -bullet -offset indent
|
||||||
|
* .It
|
||||||
|
* Escaping quotes, backslashes, and other special characters using
|
||||||
|
* their backslash escape.
|
||||||
|
* .It
|
||||||
|
* Encoding bytes that are not UTF-8 using escapes.
|
||||||
|
* .It
|
||||||
|
* Wrapping the entire string in double quotes.
|
||||||
|
* .El
|
||||||
|
* .Pp
|
||||||
|
* This function is only provided via the public
|
||||||
|
* .Nm
|
||||||
|
* API so that it is accessible to custom JSON encoders, such as the
|
||||||
|
* CanonicalJson encoder. This will typically be used for encoding
|
||||||
|
* object keys; to encode values, just use
|
||||||
|
* .Fn JsonEncodeValue .
|
||||||
|
* .Pp
|
||||||
|
* This function returns the number of bytes written to the stream,
|
||||||
|
* or if the stream is NULL, the number of bytes that would have
|
||||||
|
* been written.
|
||||||
|
*/
|
||||||
|
extern int JsonEncodeString(const char *, Stream *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize a JSON value as it would appear in JSON output. This is
|
||||||
|
* a recursive function that also encodes all child values reachable
|
||||||
|
* from the given value. This function is exposed via the public
|
||||||
|
* .Nm
|
||||||
|
* API so that it is accessible to custom JSON encoders. Normal users
|
||||||
|
* that are not writing custom encoders should in most cases just use
|
||||||
|
* .Fn JsonEncode
|
||||||
|
* to encode an entire object.
|
||||||
|
* .Pp
|
||||||
|
* The third parameter is an integer that represents the indent level
|
||||||
|
* of the value to be printed, or a negative number if pretty-printing
|
||||||
|
* should be disabled and JSON should be printed as minimized as
|
||||||
|
* possible. To pretty-print a JSON object, set this to
|
||||||
|
* .Va JSON_PRETTY .
|
||||||
|
* To get minified output, set it to
|
||||||
|
* .Va JSON_DEFAULT .
|
||||||
|
* .Pp
|
||||||
|
* This function returns the number of bytes written to the stream,
|
||||||
|
* or if the stream is NULL, the number of bytes that would have
|
||||||
|
* been written.
|
||||||
|
*/
|
||||||
|
extern int JsonEncodeValue(JsonValue *, Stream *, int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a JSON object as it would appear in JSON output, writing it
|
||||||
|
* to the given output stream. This function is recursive; it will
|
||||||
|
* serialize everything accessible from the passed object. The third
|
||||||
|
* parameter has the same behavior as described above.
|
||||||
|
* .Pp
|
||||||
|
* This function returns the number of bytes written to the stream,
|
||||||
|
* or if the stream is NULL, the number of bytes that would have
|
||||||
|
* been written.
|
||||||
|
*/
|
||||||
|
extern int JsonEncode(HashMap *, Stream *, int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a JSON object from the given input stream and parse it into
|
||||||
|
* a hash map of JSON values.
|
||||||
|
*/
|
||||||
|
extern HashMap * JsonDecode(Stream *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience function that allows the caller to retrieve and
|
||||||
|
* arbitrarily deep keys within a JSON object. It takes a root JSON
|
||||||
|
* object, the number of levels deep to go, and then that number of
|
||||||
|
* keys as a varargs list. All keys must have objects as values, with
|
||||||
|
* the exception of the last one, which is the value that will be
|
||||||
|
* returned. Otherwise, NULL indicates the specified path doas not
|
||||||
|
* exist.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonGet(HashMap *, size_t,...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience function that allows the caller to set arbitrarily
|
||||||
|
* deep keys within a JSON object. It takes a root JSON object, the
|
||||||
|
* number of levels deep to go, and then that number of keys as a
|
||||||
|
* varargs list. All keys must have object as values, with the
|
||||||
|
* exception of the last one, which is the value that will be set.
|
||||||
|
* The value currently at that key, if any, will be returned.
|
||||||
|
* This function will create any intermediate objects as necessary to
|
||||||
|
* set the proper key.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonSet(HashMap *, JsonValue *, size_t,...);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_JSON_H */
|
196
src/include/Log.h
Normal file
196
src/include/Log.h
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CYTOPLASM_LOG_H
|
||||||
|
#define CYTOPLASM_LOG_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Log
|
||||||
|
* @Nd A simple logging framework for logging to multiple destinations.
|
||||||
|
* @Dd April 27 2023
|
||||||
|
* @Xr Stream
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* is a simple C logging library that allows for colorful outputs,
|
||||||
|
* timestamps, and custom log levels. It also features the ability to
|
||||||
|
* have multiple logs open at one time, although Cytoplasm primarily
|
||||||
|
* utilizes the global log. All logs are thread safe.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <syslog.h>
|
||||||
|
|
||||||
|
#include <Stream.h>
|
||||||
|
|
||||||
|
#define LOG_FLAG_COLOR (1 << 0)
|
||||||
|
#define LOG_FLAG_SYSLOG (1 << 1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A log is defined as a configuration that describes the properties
|
||||||
|
* of the log. This opaque structure can be manipulated by the
|
||||||
|
* functions defined in this API.
|
||||||
|
*/
|
||||||
|
typedef struct LogConfig LogConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new log configuration with sane defaults that can be used
|
||||||
|
* immediately with the logging functions.
|
||||||
|
*/
|
||||||
|
extern LogConfig * LogConfigCreate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the global log configuration, creating a new one with
|
||||||
|
* .Fn LogConfigCreate
|
||||||
|
* if necessary.
|
||||||
|
*/
|
||||||
|
extern LogConfig * LogConfigGlobal(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free the given log configuration. Note that this does not close the
|
||||||
|
* underlying stream associated with the log, if there is one. Also
|
||||||
|
* note that to avoid memory leaks, the global log configuration must
|
||||||
|
* also be freed, but it cannot be used after it is freed.
|
||||||
|
*/
|
||||||
|
extern void LogConfigFree(LogConfig *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current log level on the specified log configuration.
|
||||||
|
* This indicates that only messages at or above this level should be
|
||||||
|
* logged; all others are silently discarded. The passed log level
|
||||||
|
* should be one of the log levels defined by
|
||||||
|
* .Xr syslog 3 .
|
||||||
|
* Refer to that page for a complete list of acceptable log levels,
|
||||||
|
* and note that passing an invalid log level will result in undefined
|
||||||
|
* behavior.
|
||||||
|
*/
|
||||||
|
extern void LogConfigLevelSet(LogConfig *, int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cause the log output to be indented two more spaces than it was
|
||||||
|
* previously. This can be helpful when generating stack traces or
|
||||||
|
* other hierarchical output. This is a simple convenience wrapper
|
||||||
|
* around
|
||||||
|
* .Fn LogConfigIndentSet .
|
||||||
|
*/
|
||||||
|
extern void LogConfigIndent(LogConfig *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cause the log output to be indented two less spaces than it was
|
||||||
|
* previously. This is a simple convenience wrapper around
|
||||||
|
* .Fn LogConfigIndentSet .
|
||||||
|
*/
|
||||||
|
extern void LogConfigUnindent(LogConfig *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indent future log output using the specified config by some
|
||||||
|
* arbitrary amount.
|
||||||
|
*/
|
||||||
|
extern void LogConfigIndentSet(LogConfig *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the file stream that logging output should be written to. This
|
||||||
|
* defaults to standard output, but it can be set to standard error,
|
||||||
|
* or any other arbitrary stream. Passing a NULL value for the stream
|
||||||
|
* pointer sets the log output to the standard output. Note that the
|
||||||
|
* output stream is only used if
|
||||||
|
* .Va LOG_FLAG_SYSLOG
|
||||||
|
* is not set.
|
||||||
|
*/
|
||||||
|
extern void LogConfigOutputSet(LogConfig *, Stream *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a number of boolean options on a log configuration. This
|
||||||
|
* function uses bitwise operators, so multiple options can be set with
|
||||||
|
* a single function call using bitwise OR operators. The flags are
|
||||||
|
* defined as preprocessor macros, and are as follows:
|
||||||
|
* .Bl -tag -width Ds
|
||||||
|
* .It LOG_FLAG_COLOR
|
||||||
|
* When set, enable color-coded output on TTYs. Note that colors are
|
||||||
|
* implemented as ANSI escape sequences, and are not written to file
|
||||||
|
* streams that are not actually connected to a TTY, to prevent those
|
||||||
|
* sequences from being written to a file.
|
||||||
|
* .Xr isatty 3
|
||||||
|
* is checked before writing any terminal sequences.
|
||||||
|
* .It LOG_FLAG_SYSLOG
|
||||||
|
* When set, log output to the syslog using
|
||||||
|
* .Xr syslog 3 ,
|
||||||
|
* instead of logging to the file set by
|
||||||
|
* .Fn LogConfigOutputSet .
|
||||||
|
* This flag always overrides the stream set by that function,
|
||||||
|
* regardless of when it was set, even if it was set after this flag
|
||||||
|
* was set.
|
||||||
|
* .El
|
||||||
|
*/
|
||||||
|
extern void LogConfigFlagSet(LogConfig *, int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear a boolean flag from the specified log format. See above for
|
||||||
|
* the list of flags.
|
||||||
|
*/
|
||||||
|
extern void LogConfigFlagClear(LogConfig *, int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a custom timestamp to be prepended to each message if the
|
||||||
|
* output is not going to the system log. Consult your system's
|
||||||
|
* documentation for
|
||||||
|
* .Xr strftime 3 .
|
||||||
|
* A value of NULL disables the timestamp output before messages.
|
||||||
|
*/
|
||||||
|
extern void LogConfigTimeStampFormatSet(LogConfig *, char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function does the actual logging of messages using a
|
||||||
|
* specified configuration. It takes the configuration, the log
|
||||||
|
* level, a format string, and then a list of arguments, all in that
|
||||||
|
* order. This function only logs messages if their level is above
|
||||||
|
* or equal to the currently configured log level, making it easy to
|
||||||
|
* turn some messages on or off.
|
||||||
|
* .Pp
|
||||||
|
* This function has the same usage as
|
||||||
|
* .Xr vprintf 3 .
|
||||||
|
* Consult that page for the list of format specifiers and their
|
||||||
|
* arguments. This function is typically not used directly, see the
|
||||||
|
* other log functions for the most common use cases.
|
||||||
|
*/
|
||||||
|
extern void Logv(LogConfig *, int, const char *, va_list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a message using
|
||||||
|
* .Fn Logv .
|
||||||
|
* with the specified configuration. This function has the same usage
|
||||||
|
* as
|
||||||
|
* .Xr printf 3 .
|
||||||
|
*/
|
||||||
|
extern void LogTo(LogConfig *, int, const char *, ...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a message to the global log using
|
||||||
|
* .Fn Logv .
|
||||||
|
* This function has the same usage as
|
||||||
|
* .Xr printf 3 .
|
||||||
|
*/
|
||||||
|
extern void Log(int, const char *, ...);
|
||||||
|
|
||||||
|
#endif
|
225
src/include/Memory.h
Normal file
225
src/include/Memory.h
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_MEMORY_H
|
||||||
|
#define CYTOPLASM_MEMORY_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Memory
|
||||||
|
* @Nd Smart memory management.
|
||||||
|
* @Dd January 9 2023
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* is an API that allows for smart memory management and profiling. It
|
||||||
|
* wraps the standard library functions
|
||||||
|
* .Xr malloc 3 ,
|
||||||
|
* .Xr realloc 3 ,
|
||||||
|
* and
|
||||||
|
* .Xr free 3 ,
|
||||||
|
* and offers identical semantics, while providing functionality that
|
||||||
|
* the standard library doesn't have, such as getting statistics on the
|
||||||
|
* total memory allocated on the heap, and getting the size of a block
|
||||||
|
* given a pointer. Additionally, thanks to preprocessor macros, the
|
||||||
|
* exact file and line number at which an allocation, re-allocation, or
|
||||||
|
* free occured can be obtained given a pointer. Finally, all the
|
||||||
|
* blocks allocated on the heap can be iterated and evaluated, and a
|
||||||
|
* callback function can be executed every time a memory operation
|
||||||
|
* occurs.
|
||||||
|
* .Pp
|
||||||
|
* In the future, this API could include a garbage collector that
|
||||||
|
* automatically frees memory it detects as being no longer in use.
|
||||||
|
* However, this feature does not yet exist.
|
||||||
|
* .Pp
|
||||||
|
* A number of macros are available, which make the
|
||||||
|
* .Nm
|
||||||
|
* API much easier to use. They are as follows:
|
||||||
|
* .Bl -bullet -offset indent
|
||||||
|
* .It
|
||||||
|
* .Fn Malloc "x"
|
||||||
|
* .It
|
||||||
|
* .Fn Realloc "x" "y"
|
||||||
|
* .It
|
||||||
|
* .Fn Free "x"
|
||||||
|
* .El
|
||||||
|
* .Pp
|
||||||
|
* These macros expand to
|
||||||
|
* .Fn MemoryAllocate ,
|
||||||
|
* .Fn MemoryReallocate ,
|
||||||
|
* and
|
||||||
|
* .Fn MemoryFree
|
||||||
|
* with the second and third parameters set to __FILE__ and __LINE__.
|
||||||
|
* This allows
|
||||||
|
* .Nm
|
||||||
|
* to be used exactly how the standard library functions would be
|
||||||
|
* used. In fact, the functions to which these macros expand are not
|
||||||
|
* intended to be used directly; for the best results, use these
|
||||||
|
* macros.
|
||||||
|
*/
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These values are passed into the memory hook function to indicate
|
||||||
|
* the action that just happened.
|
||||||
|
*/
|
||||||
|
typedef enum MemoryAction
|
||||||
|
{
|
||||||
|
MEMORY_ALLOCATE,
|
||||||
|
MEMORY_REALLOCATE,
|
||||||
|
MEMORY_FREE,
|
||||||
|
MEMORY_BAD_POINTER
|
||||||
|
} MemoryAction;
|
||||||
|
|
||||||
|
#define Malloc(x) MemoryAllocate(x, __FILE__, __LINE__)
|
||||||
|
#define Realloc(x, s) MemoryReallocate(x, s, __FILE__, __LINE__)
|
||||||
|
#define Free(x) MemoryFree(x, __FILE__, __LINE__)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The memory information is opaque, but can be accessed using the
|
||||||
|
* functions defined by this API.
|
||||||
|
*/
|
||||||
|
typedef struct MemoryInfo MemoryInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate the specified number of bytes on the heap. This function
|
||||||
|
* has the same semantics as
|
||||||
|
* .Xr malloc 3 ,
|
||||||
|
* except that it takes the file name and line number at which the
|
||||||
|
* allocation occurred.
|
||||||
|
*/
|
||||||
|
extern void * MemoryAllocate(size_t, const char *, int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the size of the object pointed to by the given pointer
|
||||||
|
* to the given number of bytes. This function has the same semantics
|
||||||
|
* as
|
||||||
|
* .Xr realloc 3 ,
|
||||||
|
* except that it takes the file name and line number at which the
|
||||||
|
* reallocation occurred.
|
||||||
|
*/
|
||||||
|
extern void * MemoryReallocate(void *, size_t, const char *, int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free the memory at the given pointer. This function has the same
|
||||||
|
* semantics as
|
||||||
|
* .Xr free 3 ,
|
||||||
|
* except that it takes the file name and line number at which the
|
||||||
|
* free occurred.
|
||||||
|
*/
|
||||||
|
extern void MemoryFree(void *, const char *, int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total number of bytes that the program has allocated on the
|
||||||
|
* heap. This operation iterates over all heap allocations made with
|
||||||
|
* .Fn MemoryAllocate
|
||||||
|
* and then returns a total count, in bytes.
|
||||||
|
*/
|
||||||
|
extern size_t MemoryAllocated(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate over all heap allocations made with
|
||||||
|
* .Fn MemoryAllocate
|
||||||
|
* and call
|
||||||
|
* .Fn MemoryFree
|
||||||
|
* on them. This function immediately invalidates all pointers to
|
||||||
|
* blocks on the heap, and any subsequent attempt to read or write to
|
||||||
|
* data on the heap will result in undefined behavior. This is
|
||||||
|
* typically called at the end of the program, just before exit.
|
||||||
|
*/
|
||||||
|
extern void MemoryFreeAll(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch information about an allocation. This function takes a raw
|
||||||
|
* pointer, and if
|
||||||
|
* . Nm
|
||||||
|
* knows about the pointer, it returns a structure that can be used
|
||||||
|
* to obtain information about the block of memory that the pointer
|
||||||
|
* points to.
|
||||||
|
*/
|
||||||
|
extern MemoryInfo * MemoryInfoGet(void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size in bytes of the block of memory represented by the
|
||||||
|
* specified memory info structure.
|
||||||
|
*/
|
||||||
|
extern size_t MemoryInfoGetSize(MemoryInfo *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file name in which the block of memory represented by the
|
||||||
|
* specified memory info structure was allocated.
|
||||||
|
*/
|
||||||
|
extern const char * MemoryInfoGetFile(MemoryInfo *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the line number on which the block of memory represented by the
|
||||||
|
* specified memory info structure was allocated.
|
||||||
|
*/
|
||||||
|
extern int MemoryInfoGetLine(MemoryInfo *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a pointer to the block of memory represented by the specified
|
||||||
|
* memory info structure.
|
||||||
|
*/
|
||||||
|
extern void * MemoryInfoGetPointer(MemoryInfo *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function takes a pointer to a function that takes the memory
|
||||||
|
* info structure, as well as a void pointer for caller-provided
|
||||||
|
* arguments. It iterates over all the heap memory currently allocated
|
||||||
|
* at the time of calling, executing the function on each allocation.
|
||||||
|
*/
|
||||||
|
extern void MemoryIterate(void (*) (MemoryInfo *, void *), void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a function to be executed whenever a memory operation
|
||||||
|
* occurs. The MemoryAction argument specifies the operation that
|
||||||
|
* occurred on the block of memory represented by the memory info
|
||||||
|
* structure. The function also takes a void pointer to caller-provided
|
||||||
|
* arguments.
|
||||||
|
*/
|
||||||
|
extern void MemoryHook(void (*) (MemoryAction, MemoryInfo *, void *), void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read over the block of memory represented by the given memory info
|
||||||
|
* structure and generate a hexadecimal and ASCII string for each
|
||||||
|
* chunk of the block. This function takes a callback function that
|
||||||
|
* takes the following parameters in order:
|
||||||
|
* .Bl -bullet -offset indent
|
||||||
|
* .It
|
||||||
|
* The current offset from the beginning of the block of memory in
|
||||||
|
* bytes.
|
||||||
|
* .It
|
||||||
|
* A null-terminated string containing the next 16 bytes of the block
|
||||||
|
* encoded as space-separated hex values.
|
||||||
|
* .It
|
||||||
|
* A null-terminated string containing the ASCII representation of the
|
||||||
|
* same 16 bytes of memory. This ASCII representation is safe to print
|
||||||
|
* to a terminal or other text device, because non-printable characters
|
||||||
|
* are encoded as a . (period).
|
||||||
|
* .It
|
||||||
|
* Caller-passed pointer.
|
||||||
|
* .El
|
||||||
|
*/
|
||||||
|
extern void
|
||||||
|
MemoryHexDump(MemoryInfo *, void (*) (size_t, char *, char *, void *), void *);
|
||||||
|
|
||||||
|
#endif
|
105
src/include/Queue.h
Normal file
105
src/include/Queue.h
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_QUEUE_H
|
||||||
|
#define CYTOPLASM_QUEUE_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Queue
|
||||||
|
* @Nd A simple static queue data structure.
|
||||||
|
* @Dd November 25 2022
|
||||||
|
* @Xr Array HashMap
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* implements a simple queue data structure that is statically sized.
|
||||||
|
* This implementation does not actually store the values of the items
|
||||||
|
* in it; it only stores pointers to the data. As such, you will have
|
||||||
|
* to manually maintain data and make sure it remains valid as long as
|
||||||
|
* it is in the queue. The advantage of this is that
|
||||||
|
* .Nm
|
||||||
|
* doesn't have to copy data, and thus doesn't care how big the data
|
||||||
|
* is. Furthermore, any arbitrary data can be stored in the queue.
|
||||||
|
* .Pp
|
||||||
|
* This queue implementation operates on the heap. It is a circular
|
||||||
|
* queue, and it does not grow as it is used. Once the size is set,
|
||||||
|
* the queue never gets any bigger.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These functions operate on a queue structure that is opaque to the
|
||||||
|
* caller.
|
||||||
|
*/
|
||||||
|
typedef struct Queue Queue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate a new queue that is able to store the specified number of
|
||||||
|
* items in it.
|
||||||
|
*/
|
||||||
|
extern Queue * QueueCreate(size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free the memory associated with the specified queue structure. Note
|
||||||
|
* that this function does not free any of the values stored in the
|
||||||
|
* queue; it is the caller's job to manage memory for each item.
|
||||||
|
* Typically, the caller would dequeue all the items in the queue and
|
||||||
|
* deal with them before freeing the queue itself.
|
||||||
|
*/
|
||||||
|
extern void QueueFree(Queue *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push an element into the queue. This function returns a boolean
|
||||||
|
* value indicating whether or not the push succeeded. Pushing items
|
||||||
|
* into the queue will fail if the queue is full.
|
||||||
|
*/
|
||||||
|
extern int QueuePush(Queue *, void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pop an element out of the queue. This function returns NULL if the
|
||||||
|
* queue is empty. Otherwise, it returns a pointer to the item that is
|
||||||
|
* next up in the queue.
|
||||||
|
*/
|
||||||
|
extern void * QueuePop(Queue *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a pointer to the item that is next up in the queue without
|
||||||
|
* actually discarding it, such that the next call to
|
||||||
|
* .Fn QueuePeek
|
||||||
|
* or
|
||||||
|
* .Fn QueuePop
|
||||||
|
* will return the same pointer.
|
||||||
|
*/
|
||||||
|
extern void * QueuePeek(Queue *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether or not the queue is full.
|
||||||
|
*/
|
||||||
|
extern int QueueFull(Queue *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether or not the queue is empty.
|
||||||
|
*/
|
||||||
|
extern int QueueEmpty(Queue *);
|
||||||
|
|
||||||
|
#endif
|
81
src/include/Rand.h
Normal file
81
src/include/Rand.h
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CYTOPLASM_RAND_H
|
||||||
|
#define CYTOPLASM_RAND_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Rand
|
||||||
|
* @Nd Thread-safe random numbers.
|
||||||
|
* @Dd February 16 2023
|
||||||
|
* @Xr Util
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* is used for generating random numbers in a thread-safe way.
|
||||||
|
* Currently, one generator state is shared across all threads, which
|
||||||
|
* means that only one thread can generate random numbers at a time.
|
||||||
|
* This state is protected with a mutex to guarantee this behavior.
|
||||||
|
* In the future, a seed pool may be maintained to allow multiple
|
||||||
|
* threads to generate random numbers at the same time.
|
||||||
|
* .Pp
|
||||||
|
* The generator state is seeded on the first call to a function that
|
||||||
|
* needs it. The seed is determined by the current timestamp, the ID
|
||||||
|
* of the process, and the thread ID. These should all be sufficiently
|
||||||
|
* random sources, so the seed should be secure enough.
|
||||||
|
* .Pp
|
||||||
|
* .Nm
|
||||||
|
* currently uses a simple Mersenne Twister algorithm to generate
|
||||||
|
* random numbers. This algorithm was chosen because it is extremely
|
||||||
|
* popular and widespread. While it is likely not cryptographically
|
||||||
|
* secure, and does suffer some unfortunate pitfalls, this algorithm
|
||||||
|
* has stood the test of time and is simple enough to implement, so
|
||||||
|
* it was chosen over the alternatives.
|
||||||
|
* .Pp
|
||||||
|
* .Nm
|
||||||
|
* does not use any random number generator functions from the C
|
||||||
|
* standard library, since these are often flawed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a single random integer between 0 and the passed value.
|
||||||
|
*/
|
||||||
|
extern int RandInt(unsigned int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the number of integers specified by the second argument
|
||||||
|
* storing them into the buffer pointed to in the first argument.
|
||||||
|
* Ensure that each number is between 0 and the third argument.
|
||||||
|
* .Pp
|
||||||
|
* This function allows a caller to get multiple random numbers at once
|
||||||
|
* in a more efficient manner than repeatedly calling
|
||||||
|
* .Fn RandInt ,
|
||||||
|
* since each call to these functions
|
||||||
|
* has to lock and unlock a mutex. It is therefore better to obtain
|
||||||
|
* multiple random numbers in one pass if multiple are needed.
|
||||||
|
*/
|
||||||
|
extern void RandIntN(int *, size_t, unsigned int);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_RAND_H */
|
53
src/include/Runtime.h
Normal file
53
src/include/Runtime.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_RUNTIME_H
|
||||||
|
#define CYTOPLASM_RUNTIME_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Runtime
|
||||||
|
* @Nd Supporting functions for the Cytoplasm runtime.
|
||||||
|
* @Dd May 23 2023
|
||||||
|
* @Xr Memory
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* provides supporting functions for the Cytoplasm runtime. These
|
||||||
|
* functions are not intended to be called directly by programs,
|
||||||
|
* but are used internally. They're exposed via a header because
|
||||||
|
* the runtime stub needs to know their definitions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Array.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a memory report to a file in the current directory, using
|
||||||
|
* the provided string as the name of the program currently being
|
||||||
|
* executed. This function is to be called after all memory is
|
||||||
|
* supposed to have been freed. It iterates over all remaining
|
||||||
|
* memory and generates a text file containing all of the
|
||||||
|
* recorded information about each block, including a hex dump of
|
||||||
|
* the data stored in them.
|
||||||
|
*/
|
||||||
|
extern void GenerateMemoryReport(const char *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_RUNTIME_H */
|
50
src/include/Sha2.h
Normal file
50
src/include/Sha2.h
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CYTOPLASM_SHA2_H
|
||||||
|
#define CYTOPLASM_SHA2_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Sha2
|
||||||
|
* @Nd A simple implementation of the SHA2 hashing functions.
|
||||||
|
* @Dd December 19 2022
|
||||||
|
* @Xr Memory Base64
|
||||||
|
*
|
||||||
|
* This API defines simple functions for computing SHA2 hashes.
|
||||||
|
* At the moment, it only defines
|
||||||
|
* .Fn Sha256 ,
|
||||||
|
* which computes the SHA-256 hash of the given C string. It is
|
||||||
|
* not trivial to implement SHA-512 in ANSI C due to the lack of
|
||||||
|
* a 64-bit integer type, so that hash function has been omitted.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function takes a pointer to a NULL-terminated C string, and
|
||||||
|
* returns a string allocated on the heap using the Memory API, or
|
||||||
|
* NULL if there was an error allocating memory. The returned string
|
||||||
|
* should be freed when it is no longer needed.
|
||||||
|
*/
|
||||||
|
extern char * Sha256(char *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_SHA2_H */
|
113
src/include/Str.h
Normal file
113
src/include/Str.h
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_STR_H
|
||||||
|
#define CYTOPLASM_STR_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Str
|
||||||
|
* @Nd Functions for creating and manipulating strings.
|
||||||
|
* @Dd February 15 2023
|
||||||
|
* @Xr Memory
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* provides string-related functions. It is called
|
||||||
|
* .Nm ,
|
||||||
|
* not String, because some platforms (Windows) do not have
|
||||||
|
* case-sensitive filesystems, which poses a problem since
|
||||||
|
* .Pa string.h
|
||||||
|
* is a standard library header.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take a UTF-8 codepoint and encode it into a string buffer containing
|
||||||
|
* between 1 and 4 bytes. The string buffer is allocated on the heap,
|
||||||
|
* so it should be freed when it is no longer needed.
|
||||||
|
*/
|
||||||
|
extern char * StrUtf8Encode(unsigned long);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicate a null-terminated string, returning a new string on the
|
||||||
|
* heap. This is useful when a function takes in a string that it needs
|
||||||
|
* to store for long amounts of time, even perhaps after the original
|
||||||
|
* string is gone.
|
||||||
|
*/
|
||||||
|
extern char * StrDuplicate(const char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract part of a null-terminated string, returning a new string on
|
||||||
|
* the heap containing only the requested subsection. Like the
|
||||||
|
* substring functions included with most programming languages, the
|
||||||
|
* starting index is inclusive, and the ending index is exclusive.
|
||||||
|
*/
|
||||||
|
extern char * StrSubstr(const char *, size_t, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A varargs function that takes a number of null-terminated strings
|
||||||
|
* specified by the first argument, and returns a new string that
|
||||||
|
* contains their concatenation. It works similarly to
|
||||||
|
* .Xr strcat 3 ,
|
||||||
|
* but it takes care of allocating memory big enough to hold all the
|
||||||
|
* strings. Any string in the list may be NULL. If a NULL pointer is
|
||||||
|
* passed, it is treated like an empty string.
|
||||||
|
*/
|
||||||
|
extern char * StrConcat(size_t,...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a boolean value indicating whether or not the null-terminated
|
||||||
|
* string consists only of blank characters, as determined by
|
||||||
|
* .Xr isblank 3 .
|
||||||
|
*/
|
||||||
|
extern int StrBlank(const char *str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a string of the specified length, containing random
|
||||||
|
* lowercase and uppercase letters.
|
||||||
|
*/
|
||||||
|
extern char * StrRandom(size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the specified integer into a string, returning the string
|
||||||
|
* on the heap, or NULL if there was a memory allocation error. The
|
||||||
|
* returned string should be freed by the caller after it is no longer
|
||||||
|
* needed.
|
||||||
|
*/
|
||||||
|
extern char * StrInt(long);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two strings and determine whether or not they are equal.
|
||||||
|
* This is the most common use case of strcmp() in Cytoplasm, but
|
||||||
|
* strcmp() doesn't like NULL pointers, so these have to be checked
|
||||||
|
* explicitly and can cause problems if they aren't. This function,
|
||||||
|
* on the other hand, makes NULL pointers special cases. If both
|
||||||
|
* arguments are NULL, then they are considered equal. If only one
|
||||||
|
* argument is NULL, they are considered not equal. Otherwise, if
|
||||||
|
* no arguments are NULL, a regular strcmp() takes place and this
|
||||||
|
* function returns a boolean value indicating whether or not
|
||||||
|
* strcmp() returned 0.
|
||||||
|
*/
|
||||||
|
extern int StrEquals(const char *, const char *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_STR_H */
|
223
src/include/Stream.h
Normal file
223
src/include/Stream.h
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_STREAM_H
|
||||||
|
#define CYTOPLASM_STREAM_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Stream
|
||||||
|
* @Nd An abstraction over the Io API that implements standard C I/O.
|
||||||
|
* @Dd April 29 2023
|
||||||
|
* @Xr Io
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* implements an abstraction layer over the Io API. This layer buffers
|
||||||
|
* I/O and makes it much easier to work with, mimicking the standard
|
||||||
|
* C library and offering some more convenience features.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Io.h>
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An opaque structure analogous to C's FILE pointers.
|
||||||
|
*/
|
||||||
|
typedef struct Stream Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new stream using the specified Io for underlying I/O
|
||||||
|
* operations.
|
||||||
|
*/
|
||||||
|
extern Stream * StreamIo(Io * io);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new stream using the specified POSIX file descriptor.
|
||||||
|
* This is a convenience function for calling
|
||||||
|
* .Fn IoFd
|
||||||
|
* and then passing the result into
|
||||||
|
* .Fn StreamIo .
|
||||||
|
*/
|
||||||
|
extern Stream * StreamFd(int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new stream using the specified C FILE pointer. This is a
|
||||||
|
* convenience function for calling
|
||||||
|
* .Fn IoFile
|
||||||
|
* and then passing the result into
|
||||||
|
* .Fn StreamIo .
|
||||||
|
*/
|
||||||
|
extern Stream * StreamFile(FILE *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new stream using the specified path and mode. This is a
|
||||||
|
* convenience function for calling
|
||||||
|
* .Xr fopen 3
|
||||||
|
* and then passing the result into
|
||||||
|
* .Fn StreamFile .
|
||||||
|
*/
|
||||||
|
extern Stream * StreamOpen(const char *, const char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a stream that writes to the standard output.
|
||||||
|
*/
|
||||||
|
extern Stream * StreamStdout(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a stream that writes to the standard error.
|
||||||
|
*/
|
||||||
|
extern Stream * StreamStderr(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a stream that reads from the standard input.
|
||||||
|
*/
|
||||||
|
extern Stream * StreamStdin(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the stream. This flushes the buffers and closes the underlying
|
||||||
|
* Io. It is analogous to the standard
|
||||||
|
* .Xr fclose 3
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
extern int StreamClose(Stream *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a formatted string. This function is analogous to the standard
|
||||||
|
* .Xr vfprintf 3
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
extern int StreamVprintf(Stream *, const char *, va_list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a formatted string. This function is analogous to the
|
||||||
|
* standard
|
||||||
|
* .Xr fprintf 3
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
extern int StreamPrintf(Stream *, const char *,...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single character from a stream. This function is analogous to
|
||||||
|
* the standard
|
||||||
|
* .Xr fgetc 3
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
extern int StreamGetc(Stream *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a character back onto the input stream. This function is
|
||||||
|
* analogous to the standard
|
||||||
|
* .Xr ungetc 3
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
extern int StreamUngetc(Stream *, int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a single character to the stream. This function is analogous
|
||||||
|
* to the standard
|
||||||
|
* .Xr fputc 3
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
extern int StreamPutc(Stream *, int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a null-terminated string to the stream. This function is
|
||||||
|
* analogous to the standard
|
||||||
|
* .Xr fputs 3
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
extern int StreamPuts(Stream *, char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read at most the specified number of characters minus 1 from the
|
||||||
|
* specified stream and store them at the memory located at the
|
||||||
|
* specified pointer. This function is analogous to the standard
|
||||||
|
* .Xr fgets 3
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
extern char * StreamGets(Stream *, char *, int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the file position indicator for the specified stream. This
|
||||||
|
* function is analogous to the standard
|
||||||
|
* .Xr fseeko
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
extern off_t StreamSeek(Stream *, off_t, int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the end-of-file indicator for the given stream, returning a
|
||||||
|
* boolean value indicating whether or not it is set. This is analogous
|
||||||
|
* to the standard
|
||||||
|
* .Xr feof 3
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
extern int StreamEof(Stream *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the stream for an error condition, returning a boolean value
|
||||||
|
* indicating whether or not one is present. This is analogous to the
|
||||||
|
* standard
|
||||||
|
* .Xr ferror 3
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
extern int StreamError(Stream *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the error condition associated with the given stream, allowing
|
||||||
|
* future reads or writes to potentially be successful. This functio
|
||||||
|
* is analogous to the standard
|
||||||
|
* .Xr clearerr 3
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
extern void StreamClearError(Stream *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush all buffered data using the streams underlying write function.
|
||||||
|
* This function is analogous to the standard
|
||||||
|
* .Xr fflush 3
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
extern int StreamFlush(Stream *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read all the bytes from the first stream and write them to the
|
||||||
|
* second stream. This is analogous to
|
||||||
|
* .Fn IoCopy ,
|
||||||
|
* but it uses the internal buffers of the streams. It is probably
|
||||||
|
* less efficient than doing a
|
||||||
|
* .Fn IoCopy
|
||||||
|
* instead, but it is more convenient.
|
||||||
|
*/
|
||||||
|
extern ssize_t StreamCopy(Stream *, Stream *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file descriptor associated with the given stream, or -1 if
|
||||||
|
* the stream is not associated with any file descriptor. This function
|
||||||
|
* is analogous to the standard
|
||||||
|
* .Xr fileno 3
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
extern int StreamFileno(Stream *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_STREAM_H */
|
109
src/include/Tls.h
Normal file
109
src/include/Tls.h
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_TLS_H
|
||||||
|
#define CYTOPLASM_TLS_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Tls
|
||||||
|
* @Nd Interface to platform-dependent TLS libraries.
|
||||||
|
* @Dd April 29 2023
|
||||||
|
* @Xr Stream Io
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* provides an interface to platform-dependent TLS libraries. It allows
|
||||||
|
* Cytoplasm to support any TLS library with no changes to existing
|
||||||
|
* code. Support for additional TLS libraries is added by creating a
|
||||||
|
* new compilation unit that implements all the functions here, with
|
||||||
|
* the exception of a few, which are noted.
|
||||||
|
* .Pp
|
||||||
|
* Currently, Cytoplasm has support for the following TLS libraries:
|
||||||
|
* .Bl -bullet -offset indent
|
||||||
|
* .It
|
||||||
|
* LibreSSL
|
||||||
|
* .It
|
||||||
|
* OpenSSL
|
||||||
|
* .El
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Stream.h>
|
||||||
|
|
||||||
|
#define TLS_LIBRESSL 2
|
||||||
|
#define TLS_OPENSSL 3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new TLS client stream using the given file descriptor and
|
||||||
|
* the given server hostname. The hostname should be used to verify
|
||||||
|
* that the server actually is who it says it is.
|
||||||
|
* .Pp
|
||||||
|
* This function does not need to be implemented by the individual
|
||||||
|
* TLS support stubs.
|
||||||
|
*/
|
||||||
|
extern Stream * TlsClientStream(int, const char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new TLS server stream using the given certificate and key
|
||||||
|
* file, in the format natively supported by the TLS library.
|
||||||
|
* .Pp
|
||||||
|
* This function does not need to be implemented by the individual
|
||||||
|
* TLS support stubs.
|
||||||
|
*/
|
||||||
|
extern Stream * TlsServerStream(int, const char *, const char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a cookie that stores information about the given client
|
||||||
|
* connection. This cookie will be passed into the other functions
|
||||||
|
* defined by this API.
|
||||||
|
*/
|
||||||
|
extern void * TlsInitClient(int, const char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a cookie that stores information about the given
|
||||||
|
* server connection. This cookie will be passed into the other
|
||||||
|
* functions defined by this API.
|
||||||
|
*/
|
||||||
|
extern void * TlsInitServer(int, const char *, const char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from a TLS stream, decrypting it and storing the result in the
|
||||||
|
* specified buffer. This function takes the cookie, buffer, and
|
||||||
|
* number of decrypted bytes to read into it. See the documentation for
|
||||||
|
* .Fn IoRead .
|
||||||
|
*/
|
||||||
|
extern ssize_t TlsRead(void *, void *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write to a TLS stream, encrypting the buffer. This function takes
|
||||||
|
* the cookie, buffer, and number of unencrypted bytes to write to
|
||||||
|
* the stream. See the documentation for
|
||||||
|
* .Fn IoWrite .
|
||||||
|
*/
|
||||||
|
extern ssize_t TlsWrite(void *, void *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the TLS stream, also freeing all memory associated with the
|
||||||
|
* cookie.
|
||||||
|
*/
|
||||||
|
extern int TlsClose(void *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_TLS_H */
|
69
src/include/Uri.h
Normal file
69
src/include/Uri.h
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CYTOPLASM_URI_H
|
||||||
|
#define CYTOPLASM_URI_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Uri
|
||||||
|
* @Nd Parse a URI. Typically used to parse HTTP(s) URLs.
|
||||||
|
* @Dd April 29 2023
|
||||||
|
* @Xr Http
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* provides a simple mechanism for parsing URIs. This is an extremely
|
||||||
|
* basic parser that (ab)uses
|
||||||
|
* .Xr sscanf 3
|
||||||
|
* to parse URIs, so it may not be the most reliable, but it should
|
||||||
|
* work in most cases and on reasonable URIs that aren't too long, as
|
||||||
|
* the _MAX definitions are modest.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define URI_PROTO_MAX 8
|
||||||
|
#define URI_HOST_MAX 128
|
||||||
|
#define URI_PATH_MAX 256
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parsed URI is stored in this structure.
|
||||||
|
*/
|
||||||
|
typedef struct Uri
|
||||||
|
{
|
||||||
|
char proto[URI_PROTO_MAX];
|
||||||
|
char host[URI_HOST_MAX];
|
||||||
|
char path[URI_PATH_MAX];
|
||||||
|
unsigned short port;
|
||||||
|
} Uri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a URI string into the Uri structure as described above, or
|
||||||
|
* return NULL if there was a parsing error.
|
||||||
|
*/
|
||||||
|
extern Uri * UriParse(const char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free the memory associated with a Uri structure returned by
|
||||||
|
* .Fn UriParse .
|
||||||
|
*/
|
||||||
|
extern void UriFree(Uri *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_URI_H */
|
105
src/include/Util.h
Normal file
105
src/include/Util.h
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CYTOPLASM_UTIL_H
|
||||||
|
#define CYTOPLASM_UTIL_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Util
|
||||||
|
* @Nd Some misc. helper functions that don't need their own headers.
|
||||||
|
* @Dd February 15 2023
|
||||||
|
*
|
||||||
|
* This header holds a number of random functions related to strings,
|
||||||
|
* time, the filesystem, and other simple tasks that don't require a
|
||||||
|
* full separate API. For the most part, the functions here are
|
||||||
|
* entirely standalone, depending only on POSIX functions, however
|
||||||
|
* there are a few that depend explicitly on a few other APIs. Those
|
||||||
|
* are noted.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <Stream.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current timestamp in milliseconds since the Unix epoch. This
|
||||||
|
* uses
|
||||||
|
* .Xr gettimeofday 2
|
||||||
|
* and time_t, and converts it to a single number, which is then
|
||||||
|
* returned to the caller.
|
||||||
|
* .Pp
|
||||||
|
* A note on the 2038 problem: as long as sizeof(long) >= 8, that is,
|
||||||
|
* as long as the long data type is 64 bits or more, then everything
|
||||||
|
* should be fine. On most, if not, all, 64-bit systems, long is 64
|
||||||
|
* bits. I would expect Cytoplasm to break for 32 bit systems
|
||||||
|
* eventually, but we should have a ways to go before that happens.
|
||||||
|
* I didn't want to try to hack together some system to store larger
|
||||||
|
* numbers than the architecture supports. But we can always
|
||||||
|
* re-evaluate over the next few years.
|
||||||
|
*/
|
||||||
|
extern unsigned long UtilServerTs(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use
|
||||||
|
* .Xr stat 2
|
||||||
|
* to get the last modified time of the given file, or zero if there
|
||||||
|
* was an error getting the last modified time of a file. This is
|
||||||
|
* primarily useful for caching file data.
|
||||||
|
*/
|
||||||
|
extern unsigned long UtilLastModified(char *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function behaves just like the system call
|
||||||
|
* .Xr mkdir 2 ,
|
||||||
|
* but it creates any intermediate directories as necessary, unlike
|
||||||
|
* .Xr mkdir 2 .
|
||||||
|
*/
|
||||||
|
extern int UtilMkdir(const char *, const mode_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sleep the calling thread for the given number of milliseconds.
|
||||||
|
* POSIX does not have a very friendly way to sleep, so this wraps
|
||||||
|
* .Xr nanosleep 2
|
||||||
|
* to make its usage much, much simpler.
|
||||||
|
*/
|
||||||
|
extern int UtilSleepMillis(long);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function works identically to the POSIX
|
||||||
|
* .Xr getdelim 3 ,
|
||||||
|
* except that it assumes pointers were allocated with the Memory API
|
||||||
|
* and it reads from a Stream instead of a file pointer.
|
||||||
|
*/
|
||||||
|
extern ssize_t UtilGetDelim(char **, size_t *, int, Stream *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is just a special case of
|
||||||
|
* .Fn UtilGetDelim
|
||||||
|
* that sets the delimiter to the newline character.
|
||||||
|
*/
|
||||||
|
extern ssize_t UtilGetLine(char **, size_t *, Stream *);
|
||||||
|
|
||||||
|
#endif /* CYTOPLASM_UTIL_H */
|
556
tools/hdoc.c
Normal file
556
tools/hdoc.c
Normal file
|
@ -0,0 +1,556 @@
|
||||||
|
/*
|
||||||
|
* 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 <HashMap.h>
|
||||||
|
#include <Str.h>
|
||||||
|
#include <Memory.h>
|
||||||
|
#include <Args.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
typedef struct DocDecl
|
||||||
|
{
|
||||||
|
char docs[HEADER_EXPR_MAX];
|
||||||
|
HeaderDeclaration decl;
|
||||||
|
} DocDecl;
|
||||||
|
|
||||||
|
typedef struct DocTypedef
|
||||||
|
{
|
||||||
|
char docs[HEADER_EXPR_MAX];
|
||||||
|
char text[HEADER_EXPR_MAX];
|
||||||
|
} DocTypedef;
|
||||||
|
|
||||||
|
typedef struct DocGlobal
|
||||||
|
{
|
||||||
|
char docs[HEADER_EXPR_MAX];
|
||||||
|
HeaderGlobal global;
|
||||||
|
} DocGlobal;
|
||||||
|
|
||||||
|
static void
|
||||||
|
ParseMainBlock(HashMap * registers, Array * descr, char *comment)
|
||||||
|
{
|
||||||
|
char *line = strtok(comment, "\n");
|
||||||
|
|
||||||
|
while (line)
|
||||||
|
{
|
||||||
|
while (*line && (isspace(*line) || *line == '*'))
|
||||||
|
{
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!*line)
|
||||||
|
{
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*line == '@')
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
line++;
|
||||||
|
|
||||||
|
while (!isspace(line[i]))
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
line[i] = '\0';
|
||||||
|
|
||||||
|
Free(HashMapSet(registers, line, StrDuplicate(line + i + 1)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ArrayAdd(descr, StrDuplicate(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
next:
|
||||||
|
line = strtok(NULL, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
Main(Array * args)
|
||||||
|
{
|
||||||
|
HeaderExpr expr;
|
||||||
|
size_t i;
|
||||||
|
char *key;
|
||||||
|
char *val;
|
||||||
|
int exit = EXIT_SUCCESS;
|
||||||
|
ArgParseState arg;
|
||||||
|
|
||||||
|
HashMap *registers = HashMapCreate();
|
||||||
|
Array *descr = ArrayCreate();
|
||||||
|
|
||||||
|
Array *declarations = ArrayCreate();
|
||||||
|
DocDecl *decl = NULL;
|
||||||
|
|
||||||
|
Array *typedefs = ArrayCreate();
|
||||||
|
DocTypedef *type = NULL;
|
||||||
|
|
||||||
|
Array *globals = ArrayCreate();
|
||||||
|
DocGlobal *global = NULL;
|
||||||
|
|
||||||
|
char comment[HEADER_EXPR_MAX];
|
||||||
|
int isDocumented = 0;
|
||||||
|
|
||||||
|
Stream *in = NULL;
|
||||||
|
Stream *out = NULL;
|
||||||
|
|
||||||
|
int opt;
|
||||||
|
|
||||||
|
ArgParseStateInit(&arg);
|
||||||
|
while ((opt = ArgParse(&arg, args, "i:o:D:")) != -1)
|
||||||
|
{
|
||||||
|
switch (opt)
|
||||||
|
{
|
||||||
|
case 'i':
|
||||||
|
if (in)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StrEquals(arg.optArg, "-"))
|
||||||
|
{
|
||||||
|
in = StreamStdin();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int len = strlen(arg.optArg);
|
||||||
|
|
||||||
|
in = StreamOpen(arg.optArg, "r");
|
||||||
|
if (!in)
|
||||||
|
{
|
||||||
|
StreamPrintf(StreamStderr(), "Error: %s:%s",
|
||||||
|
arg.optArg, strerror(errno));
|
||||||
|
exit = EXIT_FAILURE;
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (arg.optArg[len - 1] != '.')
|
||||||
|
{
|
||||||
|
arg.optArg[len - 1] = '\0';
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
arg.optArg[len - 1] = '\0';
|
||||||
|
len--;
|
||||||
|
|
||||||
|
HashMapSet(registers, "Nm", StrDuplicate(arg.optArg));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'o':
|
||||||
|
if (out)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StrEquals(arg.optArg, "-"))
|
||||||
|
{
|
||||||
|
out = StreamStdout();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out = StreamOpen(arg.optArg, "w");
|
||||||
|
if (!out)
|
||||||
|
{
|
||||||
|
StreamPrintf(StreamStderr(), "Error: %s:%s",
|
||||||
|
arg.optArg, strerror(errno));
|
||||||
|
exit = EXIT_FAILURE;
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
val = arg.optArg;
|
||||||
|
while (*val && *val != '=')
|
||||||
|
{
|
||||||
|
val++;
|
||||||
|
}
|
||||||
|
if (!*val || *val != '=')
|
||||||
|
{
|
||||||
|
StreamPrintf(StreamStderr(), "Bad register definition: %s",
|
||||||
|
arg.optArg);
|
||||||
|
exit = EXIT_FAILURE;
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
*val = '\0';
|
||||||
|
val++;
|
||||||
|
HashMapSet(registers, arg.optArg, StrDuplicate(val));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in)
|
||||||
|
{
|
||||||
|
in = StreamStdin();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!out)
|
||||||
|
{
|
||||||
|
out = StreamStdout();
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&expr, 0, sizeof(expr));
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
HeaderParse(in, &expr);
|
||||||
|
|
||||||
|
switch (expr.type)
|
||||||
|
{
|
||||||
|
case HP_PREPROCESSOR_DIRECTIVE:
|
||||||
|
/* Ignore */
|
||||||
|
break;
|
||||||
|
case HP_EOF:
|
||||||
|
/* Done parsing */
|
||||||
|
goto last;
|
||||||
|
case HP_PARSE_ERROR:
|
||||||
|
case HP_SYNTAX_ERROR:
|
||||||
|
StreamPrintf(StreamStderr(), "Parse Error: (line %d) %s\n",
|
||||||
|
expr.data.error.lineNo, expr.data.error.msg);
|
||||||
|
exit = EXIT_FAILURE;
|
||||||
|
goto finish;
|
||||||
|
case HP_COMMENT:
|
||||||
|
if (expr.data.text[0] != '*')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strncmp(expr.data.text, "**", 2) == 0)
|
||||||
|
{
|
||||||
|
ParseMainBlock(registers, descr, expr.data.text);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
strncpy(comment, expr.data.text, sizeof(comment));
|
||||||
|
isDocumented = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HP_TYPEDEF:
|
||||||
|
if (HashMapGet(registers, "ignore-typedefs"))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDocumented)
|
||||||
|
{
|
||||||
|
StreamPrintf(StreamStderr(),
|
||||||
|
"Error: Undocumented typedef:\n%s\n",
|
||||||
|
expr.data.text);
|
||||||
|
exit = EXIT_FAILURE;
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
type = Malloc(sizeof(DocTypedef));
|
||||||
|
strncpy(type->docs, comment, sizeof(type->docs));
|
||||||
|
strncpy(type->text, expr.data.text, sizeof(type->text));
|
||||||
|
ArrayAdd(typedefs, type);
|
||||||
|
isDocumented = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HP_DECLARATION:
|
||||||
|
if (!isDocumented)
|
||||||
|
{
|
||||||
|
StreamPrintf(StreamStderr(),
|
||||||
|
"Error: %s() is undocumented.\n",
|
||||||
|
expr.data.declaration.name);
|
||||||
|
exit = EXIT_FAILURE;
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
decl = Malloc(sizeof(DocDecl));
|
||||||
|
decl->decl = expr.data.declaration;
|
||||||
|
decl->decl.args = ArrayCreate();
|
||||||
|
strncpy(decl->docs, comment, sizeof(decl->docs));
|
||||||
|
for (i = 0; i < ArraySize(expr.data.declaration.args); i++)
|
||||||
|
{
|
||||||
|
ArrayAdd(decl->decl.args, StrDuplicate(ArrayGet(expr.data.declaration.args, i)));
|
||||||
|
}
|
||||||
|
ArrayAdd(declarations, decl);
|
||||||
|
isDocumented = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HP_GLOBAL:
|
||||||
|
if (!isDocumented)
|
||||||
|
{
|
||||||
|
StreamPrintf(StreamStderr(),
|
||||||
|
"Error: Global %s is undocumented.\n",
|
||||||
|
expr.data.global.name);
|
||||||
|
exit = EXIT_FAILURE;
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
global = Malloc(sizeof(DocGlobal));
|
||||||
|
global->global = expr.data.global;
|
||||||
|
|
||||||
|
strncpy(global->docs, comment, sizeof(global->docs));
|
||||||
|
ArrayAdd(globals, global);
|
||||||
|
isDocumented = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HP_UNKNOWN:
|
||||||
|
if (HashMapGet(registers, "suppress-warnings"))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
StreamPrintf(StreamStderr(), "Warning: Unknown expression: %s\n",
|
||||||
|
expr.data.text);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
StreamPrintf(StreamStderr(), "Unknown header type: %d\n", expr.type);
|
||||||
|
StreamPrintf(StreamStderr(), "This is a programming error.\n");
|
||||||
|
exit = EXIT_FAILURE;
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
last:
|
||||||
|
val = HashMapGet(registers, "Nm");
|
||||||
|
if (!val)
|
||||||
|
{
|
||||||
|
HashMapSet(registers, "Nm", StrDuplicate("Unnamed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
val = HashMapGet(registers, "Dd");
|
||||||
|
if (!val)
|
||||||
|
{
|
||||||
|
time_t currentTime;
|
||||||
|
struct tm *timeInfo;
|
||||||
|
char tsBuf[1024];
|
||||||
|
|
||||||
|
currentTime = time(NULL);
|
||||||
|
timeInfo = localtime(¤tTime);
|
||||||
|
strftime(tsBuf, sizeof(tsBuf), "%B %d %Y", timeInfo);
|
||||||
|
|
||||||
|
val = tsBuf;
|
||||||
|
}
|
||||||
|
StreamPrintf(out, ".Dd $%s: %s $\n", "Mdocdate", val);
|
||||||
|
|
||||||
|
val = HashMapGet(registers, "Os");
|
||||||
|
if (val)
|
||||||
|
{
|
||||||
|
StreamPrintf(out, ".Os %s\n", val);
|
||||||
|
}
|
||||||
|
|
||||||
|
val = HashMapGet(registers, "Nm");
|
||||||
|
StreamPrintf(out, ".Dt %s 3\n", val);
|
||||||
|
StreamPrintf(out, ".Sh NAME\n");
|
||||||
|
StreamPrintf(out, ".Nm %s\n", val);
|
||||||
|
|
||||||
|
val = HashMapGet(registers, "Nd");
|
||||||
|
if (!val)
|
||||||
|
{
|
||||||
|
val = "No Description.";
|
||||||
|
}
|
||||||
|
StreamPrintf(out, ".Nd %s\n", val);
|
||||||
|
|
||||||
|
StreamPrintf(out, ".Sh SYNOPSIS\n");
|
||||||
|
val = HashMapGet(registers, "Nm");
|
||||||
|
StreamPrintf(out, ".In %s.h\n", val);
|
||||||
|
for (i = 0; i < ArraySize(declarations); i++)
|
||||||
|
{
|
||||||
|
size_t j;
|
||||||
|
|
||||||
|
decl = ArrayGet(declarations, i);
|
||||||
|
StreamPrintf(out, ".Ft %s\n", decl->decl.returnType);
|
||||||
|
StreamPrintf(out, ".Fn %s ", decl->decl.name);
|
||||||
|
for (j = 0; j < ArraySize(decl->decl.args); j++)
|
||||||
|
{
|
||||||
|
StreamPrintf(out, "\"%s\" ", ArrayGet(decl->decl.args, j));
|
||||||
|
}
|
||||||
|
StreamPutc(out, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ArraySize(globals))
|
||||||
|
{
|
||||||
|
StreamPrintf(out, ".Sh GLOBALS\n");
|
||||||
|
for (i = 0; i < ArraySize(globals); i++)
|
||||||
|
{
|
||||||
|
char *line;
|
||||||
|
global = ArrayGet(globals, i);
|
||||||
|
|
||||||
|
StreamPrintf(out, ".Ss %s %s\n", global->global.type, global->global.name);
|
||||||
|
|
||||||
|
line = strtok(global->docs, "\n");
|
||||||
|
while (line)
|
||||||
|
{
|
||||||
|
while (*line && (isspace(*line) || *line == '*'))
|
||||||
|
{
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*line)
|
||||||
|
{
|
||||||
|
StreamPrintf(out, "%s\n", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
line = strtok(NULL, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ArraySize(typedefs))
|
||||||
|
{
|
||||||
|
StreamPrintf(out, ".Sh TYPE DECLARATIONS\n");
|
||||||
|
for (i = 0; i < ArraySize(typedefs); i++)
|
||||||
|
{
|
||||||
|
char *line;
|
||||||
|
|
||||||
|
type = ArrayGet(typedefs, i);
|
||||||
|
StreamPrintf(out, ".Bd -literal -offset indent\n");
|
||||||
|
StreamPrintf(out, "%s\n", type->text);
|
||||||
|
StreamPrintf(out, ".Ed\n.Pp\n");
|
||||||
|
|
||||||
|
line = strtok(type->docs, "\n");
|
||||||
|
while (line)
|
||||||
|
{
|
||||||
|
while (*line && (isspace(*line) || *line == '*'))
|
||||||
|
{
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*line)
|
||||||
|
{
|
||||||
|
StreamPrintf(out, "%s\n", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
line = strtok(NULL, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamPrintf(out, ".Sh DESCRIPTION\n");
|
||||||
|
for (i = 0; i < ArraySize(descr); i++)
|
||||||
|
{
|
||||||
|
StreamPrintf(out, "%s\n", ArrayGet(descr, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < ArraySize(declarations); i++)
|
||||||
|
{
|
||||||
|
size_t j;
|
||||||
|
char *line;
|
||||||
|
|
||||||
|
decl = ArrayGet(declarations, i);
|
||||||
|
StreamPrintf(out, ".Ss %s %s(",
|
||||||
|
decl->decl.returnType, decl->decl.name);
|
||||||
|
for (j = 0; j < ArraySize(decl->decl.args); j++)
|
||||||
|
{
|
||||||
|
StreamPrintf(out, "%s", ArrayGet(decl->decl.args, j));
|
||||||
|
if (j < ArraySize(decl->decl.args) - 1)
|
||||||
|
{
|
||||||
|
StreamPuts(out, ", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StreamPuts(out, ")\n");
|
||||||
|
|
||||||
|
line = strtok(decl->docs, "\n");
|
||||||
|
while (line)
|
||||||
|
{
|
||||||
|
while (*line && (isspace(*line) || *line == '*'))
|
||||||
|
{
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*line)
|
||||||
|
{
|
||||||
|
StreamPrintf(out, "%s\n", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
line = strtok(NULL, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val = HashMapGet(registers, "Xr");
|
||||||
|
if (val)
|
||||||
|
{
|
||||||
|
char *xr = strtok(val, " ");
|
||||||
|
|
||||||
|
StreamPrintf(out, ".Sh SEE ALSO\n");
|
||||||
|
while (xr)
|
||||||
|
{
|
||||||
|
if (*xr)
|
||||||
|
{
|
||||||
|
StreamPrintf(out, ".Xr %s 3 ", xr);
|
||||||
|
}
|
||||||
|
|
||||||
|
xr = strtok(NULL, " ");
|
||||||
|
|
||||||
|
if (xr)
|
||||||
|
{
|
||||||
|
StreamPutc(out, ',');
|
||||||
|
}
|
||||||
|
StreamPutc(out, '\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish:
|
||||||
|
if (in != StreamStdin())
|
||||||
|
{
|
||||||
|
StreamClose(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out != StreamStdout())
|
||||||
|
{
|
||||||
|
StreamClose(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < ArraySize(declarations); i++)
|
||||||
|
{
|
||||||
|
Free(ArrayGet(declarations, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < ArraySize(typedefs); i++)
|
||||||
|
{
|
||||||
|
Free(ArrayGet(typedefs, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < ArraySize(globals); i++)
|
||||||
|
{
|
||||||
|
Free(ArrayGet(globals, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < ArraySize(descr); i++)
|
||||||
|
{
|
||||||
|
Free(ArrayGet(descr, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
while (HashMapIterate(registers, &key, (void **) &val))
|
||||||
|
{
|
||||||
|
Free(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMapFree(registers);
|
||||||
|
|
||||||
|
return exit;
|
||||||
|
}
|
Loading…
Reference in a new issue