forked from Telodendria/Telodendria
Use Makefile
s instead of a custom script (#38)
This pull request also requires the use of the external [Cytoplasm](/Telodendria/Cytoplasm) repository by removing the in-tree copy of Cytoplasm. The increased modularity requires a little more complex build process, but is overall better. Closes #19 The appropriate documentation has been updated. Closes #18 --- Please review the developer certificate of origin: 1. The contribution was created in whole or in part by me, and I have the right to submit it under the open source licenses of the Telodendria project; or 1. The contribution is based upon a previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the Telodendria project license; or 1. The contribution was provided directly to me by some other person who certified (1), (2), or (3), and I have not modified it. 1. I understand and agree that this project and the contribution are made public and that a record of the contribution—including all personal information I submit with it—is maintained indefinitely and may be redistributed consistent with this project or the open source licenses involved. - [x] I have read the Telodendria Project development certificate of origin, and I certify that I have permission to submit this patch under the conditions specified in it. Reviewed-on: Telodendria/Telodendria#38
This commit is contained in:
parent
6377689a83
commit
1fee47a628
135 changed files with 492 additions and 21408 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -1,13 +1,17 @@
|
|||
# Telodendria .gitignore
|
||||
|
||||
build
|
||||
out
|
||||
data
|
||||
Makefile
|
||||
|
||||
*-leaked.txt
|
||||
.env
|
||||
*.patch
|
||||
*.orig
|
||||
*.log
|
||||
vgcore.*
|
||||
*.core
|
||||
Cytoplasm/build
|
||||
Cytoplasm/out
|
||||
Cytoplasm/*-leaked.txt
|
||||
contrib/.vagrant
|
||||
src/Schema
|
||||
src/include/Schema
|
||||
|
|
28
Cytoplasm/.indent.pro
vendored
28
Cytoplasm/.indent.pro
vendored
|
@ -1,28 +0,0 @@
|
|||
-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
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
|
@ -1,78 +0,0 @@
|
|||
# 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:
|
||||
|
||||
```c
|
||||
#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:
|
||||
|
||||
```c
|
||||
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.
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Array.h>
|
||||
#include <HashMap.h>
|
||||
|
||||
#include <Log.h>
|
||||
|
||||
int
|
||||
Main(Array * args, HashMap * env)
|
||||
{
|
||||
size_t i;
|
||||
char *key;
|
||||
char *val;
|
||||
|
||||
Log(LOG_INFO, "Hello World!");
|
||||
Log(LOG_INFO, "Arguments: %lu", ArraySize(args));
|
||||
|
||||
for (i = 0; i < ArraySize(args); i++)
|
||||
{
|
||||
Log(LOG_INFO, " [%ld] %s", i, ArrayGet(args, i));
|
||||
}
|
||||
|
||||
Log(LOG_INFO, "Environment:");
|
||||
while (HashMapIterate(env, &key, (void **) &val))
|
||||
{
|
||||
Log(LOG_INFO, " %s = %s", key, val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,328 +0,0 @@
|
|||
#!/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.4.0}"
|
||||
|
||||
: "${CVS_TAG:=${NAME}-$(echo ${VERSION} | sed 's/\./_/g')}"
|
||||
|
||||
|
||||
: "${SRC:=src}"
|
||||
: "${TOOLS:=tools}"
|
||||
: "${EXAMPLES:=examples}"
|
||||
: "${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 [ "${DEBUG}" = "1" ]; then
|
||||
CFLAGS="${CFLAGS} -O0 -g"
|
||||
LD_EXTRA=""
|
||||
fi
|
||||
|
||||
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_libs() {
|
||||
echo "-lm -pthread ${TLS_LIBS}"
|
||||
}
|
||||
|
||||
recipe_build() {
|
||||
mkdir -p "${BUILD}" "${OUT}/bin" "${OUT}/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_examples() {
|
||||
for src in $(find "${EXAMPLES}" -name '*.c'); do
|
||||
out=$(basename "$src" .c)
|
||||
out="${OUT}/bin/$out"
|
||||
|
||||
if [ $(mod_time "$src") -ge $(mod_time "$out") ]; 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_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
|
|
@ -1,51 +0,0 @@
|
|||
.Dd $Mdocdate: May 21 2023 $
|
||||
.Dt HDOC 1
|
||||
.Os Cytoplasm
|
||||
.Sh NAME
|
||||
.Nm hdoc
|
||||
.Nd Generate documentation from a C header file.
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl i Ar file
|
||||
.Op Fl o Ar file
|
||||
.Op Fl D Ar key=value
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is an extremely simple documentation generator that generates
|
||||
a BSD man page from a specially-formatted C header file.
|
||||
See
|
||||
.Xr hdoc 5
|
||||
for details on the format of this header file.
|
||||
.Pp
|
||||
The options are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It Fl i Ar file
|
||||
The input C header file to read from. If this option is omitted,
|
||||
then the standard input is read.
|
||||
.It Fl o Ar file
|
||||
The output BSD man page file to write. If this option is omitted,
|
||||
then the standard output is written.
|
||||
.It Fl D Ar key=value
|
||||
Set the register
|
||||
.Ar key
|
||||
to
|
||||
.Ar value .
|
||||
.Nm
|
||||
registers are used in various places, and can be set in either
|
||||
the header file, or on the command line. Setting registers that
|
||||
should be the same in all headers is best done from the command
|
||||
line for maintainability purposes, but header-specific values
|
||||
should be set in the header file itself.
|
||||
.Pp
|
||||
Registers are explained in more detail in
|
||||
.Xr hdoc 5 .
|
||||
.El
|
||||
.Sh RETURN VALUE
|
||||
.Pp
|
||||
.Nm
|
||||
returns a success value if the header is well-formed and the
|
||||
man page is successfully generated. It returns an error code in
|
||||
all other scenarios.
|
||||
.Sh SEE ALSO
|
||||
.Xr hdoc 5 ,
|
||||
.Xr HeaderParser 3
|
|
@ -1,195 +0,0 @@
|
|||
.Dd $Mdocdate: May 21 2023 $
|
||||
.Dt HDOC 5
|
||||
.Os Cytoplasm
|
||||
.Sh NAME
|
||||
.Nm hdoc
|
||||
.Nd C header file format accepted by the documentation generator.
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
.Nm
|
||||
uses an extremely primitive parser to generate documentation from
|
||||
C header files. As such, the format accepted by
|
||||
.Nm
|
||||
is rather strict and may not be suitable for other projects beyond
|
||||
of Cytoplasm. This document describes what
|
||||
.Nm
|
||||
considers to be a valid C header file, and how that header file can
|
||||
be annotated to produce a nicely-formatted man page.
|
||||
.Pp
|
||||
At the very top level, a C header is a sequences of tokens that
|
||||
represent the following:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
An ANSI C89 comment.
|
||||
.It
|
||||
A preprocessor directive.
|
||||
.It
|
||||
A typedef declaration.
|
||||
.It
|
||||
A constant or other global variable declaration.
|
||||
.It
|
||||
A function declaration.
|
||||
.El
|
||||
.Pp
|
||||
Note that global variables and functions
|
||||
.Em must
|
||||
be marked with
|
||||
.Ar extern ,
|
||||
otherwise the parser will fail to recognize them. This is by
|
||||
design; a header should make everything extern by default,
|
||||
because it does not actually implement or declare anything.
|
||||
.Pp
|
||||
Preprocessor directives are completely ignored. Regular C
|
||||
comments are also ignored.
|
||||
.Nm
|
||||
is primarily concerned with type declarations, global
|
||||
variables, and functions. It also inspects specially-formatted
|
||||
C comments, which are used to annotate these elements of the
|
||||
header. The format of these comments is described here.
|
||||
.Pp
|
||||
There are two types of special comments recognized, the first
|
||||
of which is called the ``main'' comment block, as it documents
|
||||
the header itself, not the declarations contained in it. Main
|
||||
comment blocks also control the behavior of the parser and the
|
||||
resulting man page by setting registers. The format of the
|
||||
main comment block, which typically appears only once at the
|
||||
top of the header, although this is not a requirement, is as
|
||||
follows:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
The comment should start with a ``triple star,'' like this:
|
||||
.Bd -literal -offset indent
|
||||
/***
|
||||
*
|
||||
*/
|
||||
.Ed
|
||||
.It
|
||||
Any lines that start with a ``@'' are parser directives that
|
||||
set registers. The name of the register to set follows
|
||||
immediately after the ``@'' sigil, and continues until the first
|
||||
whitespace. The rest of the line is the value of the register.
|
||||
A list of registers recognized by
|
||||
.Nm
|
||||
is as follows. These registers control the man page output,
|
||||
and the last value set is the one that is used:
|
||||
.Bl -tag -width Ds
|
||||
.It \&Nm
|
||||
The name register. The name of the man page will be set to the
|
||||
contents of this register. It defaults to ``Unnmamed.''
|
||||
.It \&Dd
|
||||
The date register. The date of the man page will be set to the
|
||||
contents of this register. If left unset, it defaults to the
|
||||
current date.
|
||||
.It \&Os
|
||||
The operating system register. The Os value, which appears in
|
||||
the bottom left and right corners of the man page, will be set
|
||||
to the contents of this register. If left unset, it is not
|
||||
output.
|
||||
.It \&Nd
|
||||
The description register. The description of the man page will
|
||||
be set to the contents of this register. It defaults to
|
||||
``No description.''
|
||||
.It \&Xr
|
||||
The cross reference register. The SEE ALSO section of the man
|
||||
page will list the specified cross references, which are to be
|
||||
separated by a single space. The section shall be omitted,
|
||||
because it is set automatically to the same section that the
|
||||
current man page will belong to. This limitation may be removed
|
||||
in the future.
|
||||
.El
|
||||
.Pp
|
||||
These registers control the parser itself, modifying its
|
||||
behavior as soon as the appear in the file:
|
||||
.Bl -tag -width Ds
|
||||
.It ignore-typedefs
|
||||
Don't throw an error for an undocumented type declaration.
|
||||
The value doesn't matter; as soon as this register shows
|
||||
up, it's set. In most cases, it should not be used, however,
|
||||
it may be helpful in a few scenarios, such as when there are
|
||||
multiple typedefs that do the same thing, but are controlled
|
||||
by preprocessor macros.
|
||||
.It suppress-warnings
|
||||
Don't issue a warning for invalid or unrecognized top-level
|
||||
tokens. They will instead be ignored until the next
|
||||
recognized top-level token is found. The value doesn't
|
||||
matter; as soon as this register shows up, it is set. In most
|
||||
cases, it should not be used, however it may be helpful in a
|
||||
few scenarios, such as when function declarations are generated
|
||||
by preprocessor directives and thus don't follow the standard
|
||||
form.
|
||||
.El
|
||||
.It
|
||||
Any other lines in the main block are output to the DESCRIPTION
|
||||
section of the main page. This description may contain mdoc
|
||||
directives in it, as the lines are copied verbatim. If multiple
|
||||
main comment blocks appear in a single header, their description
|
||||
lines are appended in the order they appear.
|
||||
.El
|
||||
.Pp
|
||||
Declaration documentation comments are created as follows:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
The comment should start with a ``double star,'' like this:
|
||||
.Bd -literal -offset indent
|
||||
/**
|
||||
*
|
||||
*/
|
||||
.Ed
|
||||
.It
|
||||
The contents of the comment are copied verbatim into the output,
|
||||
so the comment may contain mdoc directives.
|
||||
.It
|
||||
The comment must appear before a declaration. If multiple
|
||||
documentation comments appear before a declaration, the last
|
||||
one before the declaration is used.
|
||||
.El
|
||||
.Pp
|
||||
The generated man page includes the name and description of the
|
||||
header, a synopsis section that lists all of the functions in
|
||||
the header, a description section that contains all the non-register
|
||||
lines of the main comment blocks, and then all of the documentation
|
||||
for each function, with the function prototype displayed as a
|
||||
subsection header, and the documentation displayed under it.
|
||||
.Sh EXAMPLES
|
||||
.Pp
|
||||
Consider the following simple C header:
|
||||
.Bd -literal -offset indent
|
||||
#include <stdio.h>
|
||||
|
||||
extern void SayHello(FILE *);
|
||||
.Ed
|
||||
.Pp
|
||||
To annotate this header in the manner
|
||||
.Nm
|
||||
expects, do something like this:
|
||||
.Bd -literal -offset indent
|
||||
/***
|
||||
* @Nm Hello
|
||||
* @Nd Say hello.
|
||||
* @Dd May 17 2023
|
||||
*
|
||||
* .Nm
|
||||
* provides functionality to write hello world messages
|
||||
* into standard C file descriptors.
|
||||
*
|
||||
* @Xr fputs fprintf
|
||||
*/
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* This function writes "hello world" to the given file
|
||||
* descriptor.
|
||||
* .Pp
|
||||
* There really isn't much more to be said about it.
|
||||
*/
|
||||
extern void SayHello(FILE *);
|
||||
.Ed
|
||||
.Pp
|
||||
This example shows how mdoc directives can be placed in
|
||||
documentation comments. Note that the triple-star comment
|
||||
documents the header itself, and the double-star comment
|
||||
documents the type declaration or function definition
|
||||
below it.
|
||||
.Sh SEE ALSO
|
||||
.Xr hdoc 5 ,
|
||||
.Xr HeaderParser 3
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,423 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <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 ? pi - 1 : 0, compare);
|
||||
ArrayQuickSort(array, pi + 1, high, compare);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArraySort(Array * array, int (*compare) (void *, void *))
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ArrayQuickSort(array, 0, array->size - 1, compare);
|
||||
}
|
||||
|
||||
Array *
|
||||
ArrayUnique(Array * array, int (*compare) (void *, void *))
|
||||
{
|
||||
Array *ret;
|
||||
|
||||
size_t i;
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = ArrayDuplicate(array);
|
||||
if (!ret)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ArraySize(ret) == 1)
|
||||
{
|
||||
/* There can't be any duplicates when there's only 1 value */
|
||||
return ret;
|
||||
}
|
||||
|
||||
ArraySort(ret, compare);
|
||||
|
||||
for (i = 1; i < ArraySize(ret); i++)
|
||||
{
|
||||
void *cur = ret->entries[i];
|
||||
void *prev = ret->entries[i - 1];
|
||||
|
||||
if (compare(cur, prev) == 0)
|
||||
{
|
||||
/* Remove the duplicate, and put i back where it was. */
|
||||
ArrayDelete(ret, i--);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayTrim(ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* 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 *
|
||||
ArrayReverse(Array * array)
|
||||
{
|
||||
Array *ret;
|
||||
|
||||
size_t i;
|
||||
size_t size;
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!array->size)
|
||||
{
|
||||
return ArrayCreate();
|
||||
}
|
||||
|
||||
ret = Malloc(sizeof(Array));
|
||||
|
||||
size = array->size;
|
||||
|
||||
ret->size = size;
|
||||
ret->allocated = size;
|
||||
ret->entries = Malloc(sizeof(void *) * size);
|
||||
|
||||
if (!ret->entries)
|
||||
{
|
||||
Free(ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
ret->entries[i] = array->entries[size - i - 1];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -1,244 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <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;
|
||||
}
|
|
@ -1,252 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Cron.h>
|
||||
|
||||
#include <UInt64.h>
|
||||
#include <Array.h>
|
||||
#include <Memory.h>
|
||||
#include <Util.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
struct Cron
|
||||
{
|
||||
UInt64 tick;
|
||||
Array *jobs;
|
||||
pthread_mutex_t lock;
|
||||
volatile unsigned int stop:1;
|
||||
pthread_t thread;
|
||||
};
|
||||
|
||||
typedef struct Job
|
||||
{
|
||||
UInt64 interval;
|
||||
UInt64 lastExec;
|
||||
JobFunc *func;
|
||||
void *args;
|
||||
} Job;
|
||||
|
||||
static Job *
|
||||
JobCreate(UInt32 interval, JobFunc * func, void *args)
|
||||
{
|
||||
Job *job;
|
||||
|
||||
if (!func)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
job = Malloc(sizeof(Job));
|
||||
if (!job)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
job->interval = UInt64Create(0, interval);
|
||||
job->lastExec = UInt64Create(0, 0);
|
||||
job->func = func;
|
||||
job->args = args;
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
static void *
|
||||
CronThread(void *args)
|
||||
{
|
||||
Cron *cron = args;
|
||||
|
||||
while (!cron->stop)
|
||||
{
|
||||
size_t i;
|
||||
UInt64 ts; /* tick start */
|
||||
UInt64 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 (UInt64Gt(UInt64Sub(ts, job->lastExec), job->interval))
|
||||
{
|
||||
job->func(job->args);
|
||||
job->lastExec = ts;
|
||||
}
|
||||
|
||||
if (UInt64Eq(job->interval, UInt64Create(0, 0)))
|
||||
{
|
||||
ArrayDelete(cron->jobs, i);
|
||||
Free(job);
|
||||
}
|
||||
}
|
||||
te = UtilServerTs();
|
||||
|
||||
pthread_mutex_unlock(&cron->lock);
|
||||
|
||||
/* Only sleep if the jobs didn't overrun the tick */
|
||||
if (UInt64Gt(cron->tick, UInt64Sub(te, ts)))
|
||||
{
|
||||
const UInt64 microTick = UInt64Create(0, 100);
|
||||
|
||||
UInt64 remainingTick = UInt64Sub(cron->tick, UInt64Sub(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 (UInt64Geq(remainingTick, microTick) && !cron->stop)
|
||||
{
|
||||
UtilSleepMillis(microTick);
|
||||
|
||||
remainingTick = UInt64Sub(remainingTick, microTick);
|
||||
}
|
||||
|
||||
if (UInt64Neq(remainingTick, UInt64Create(0, 0)) && !cron->stop)
|
||||
{
|
||||
UtilSleepMillis(remainingTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Cron *
|
||||
CronCreate(UInt32 tick)
|
||||
{
|
||||
Cron *cron = Malloc(sizeof(Cron));
|
||||
|
||||
if (!cron)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cron->jobs = ArrayCreate();
|
||||
if (!cron->jobs)
|
||||
{
|
||||
Free(cron);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cron->tick = UInt64Create(0, 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);
|
||||
}
|
|
@ -1,969 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Db.h>
|
||||
|
||||
#include <UInt64.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;
|
||||
|
||||
UInt64 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 */
|
||||
{
|
||||
UInt64 diskTs = UtilLastModified(file);
|
||||
|
||||
ref->fd = fd;
|
||||
ref->stream = stream;
|
||||
|
||||
if (UInt64Gt(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 (UInt64Neq(UtilLastModified(file), UInt64Create(0, 0)))
|
||||
{
|
||||
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 (UInt64Neq(UtilLastModified(file), UInt64Create(0, 0)))
|
||||
{
|
||||
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 = UInt64Neq(UtilLastModified(file), UInt64Create(0, 0));
|
||||
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
|
||||
Free(file);
|
||||
ArrayFree(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Array *
|
||||
DbList(Db * db, size_t nArgs,...)
|
||||
{
|
||||
Array *result;
|
||||
Array *path;
|
||||
DIR *files;
|
||||
struct dirent *file;
|
||||
char *dir;
|
||||
va_list ap;
|
||||
|
||||
if (!db || !nArgs)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
result = ArrayCreate();
|
||||
if (!result)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
va_start(ap, nArgs);
|
||||
path = ArrayFromVarArgs(nArgs, ap);
|
||||
dir = DbDirName(db, path, 0);
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
files = opendir(dir);
|
||||
if (!files)
|
||||
{
|
||||
ArrayFree(path);
|
||||
ArrayFree(result);
|
||||
Free(dir);
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
return NULL;
|
||||
}
|
||||
while ((file = readdir(files)))
|
||||
{
|
||||
size_t namlen = strlen(file->d_name);
|
||||
|
||||
if (namlen > 5)
|
||||
{
|
||||
int nameOffset = namlen - 5;
|
||||
|
||||
if (StrEquals(file->d_name + nameOffset, ".json"))
|
||||
{
|
||||
file->d_name[nameOffset] = '\0';
|
||||
ArrayAdd(result, StrDuplicate(file->d_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(files);
|
||||
|
||||
ArrayFree(path);
|
||||
Free(dir);
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
DbListFree(Array * arr)
|
||||
{
|
||||
StringArrayFree(arr);
|
||||
}
|
||||
|
||||
HashMap *
|
||||
DbJson(DbRef * ref)
|
||||
{
|
||||
return ref ? ref->json : NULL;
|
||||
}
|
||||
|
||||
int
|
||||
DbJsonSet(DbRef * ref, HashMap * json)
|
||||
{
|
||||
if (!ref || !json)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
JsonFree(ref->json);
|
||||
ref->json = JsonDuplicate(json);
|
||||
return 1;
|
||||
}
|
|
@ -1,347 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <Graph.h>
|
||||
|
||||
#include <Memory.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
struct Graph
|
||||
{
|
||||
size_t n;
|
||||
Edge *matrix;
|
||||
};
|
||||
|
||||
Graph *
|
||||
GraphCreate(size_t n)
|
||||
{
|
||||
Graph *g;
|
||||
|
||||
if (!n)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g = Malloc(sizeof(Graph));
|
||||
if (!g)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g->n = n;
|
||||
|
||||
g->matrix = Malloc((n * n) * sizeof(Edge));
|
||||
if (!g->matrix)
|
||||
{
|
||||
Free(g);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(g->matrix, 0, (n * n) * sizeof(Edge));
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
Graph *
|
||||
GraphCreateWithEdges(size_t n, Edge * matrix)
|
||||
{
|
||||
Graph *g = GraphCreate(n);
|
||||
|
||||
if (!g)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(g->matrix, matrix, (n * n) * sizeof(Edge));
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
void
|
||||
GraphFree(Graph * g)
|
||||
{
|
||||
if (!g)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Free(g->matrix);
|
||||
Free(g);
|
||||
}
|
||||
|
||||
Edge
|
||||
GraphEdgeGet(Graph * g, Node n1, Node n2)
|
||||
{
|
||||
if (n1 >= g->n || n2 >= g->n)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return g->matrix[(g->n * n1) + n2];
|
||||
}
|
||||
|
||||
Edge
|
||||
GraphEdgeSet(Graph * g, Node n1, Node n2, Edge e)
|
||||
{
|
||||
int oldVal;
|
||||
|
||||
if (n1 >= g->n || n2 >= g->n)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (e < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
oldVal = g->matrix[(g->n * n1) + n2];
|
||||
|
||||
g->matrix[(g->n * n1) + n2] = e;
|
||||
|
||||
return oldVal;
|
||||
}
|
||||
|
||||
size_t
|
||||
GraphCountNodes(Graph * g)
|
||||
{
|
||||
return g ? g->n : 0;
|
||||
}
|
||||
|
||||
Node *
|
||||
GraphBreadthFirstSearch(Graph * G, Node s, size_t * n)
|
||||
{
|
||||
Node *visited;
|
||||
Node *queue;
|
||||
Node *result;
|
||||
size_t queueSize;
|
||||
Node i;
|
||||
|
||||
if (!G || !n)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*n = 0;
|
||||
|
||||
result = Malloc(G->n * sizeof(Node));
|
||||
if (!result)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (s >= G->n)
|
||||
{
|
||||
Free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
visited = Malloc(G->n * sizeof(Node));
|
||||
memset(visited, 0, G->n * sizeof(Node));
|
||||
queue = Malloc(G->n * sizeof(Node));
|
||||
queueSize = 0;
|
||||
|
||||
visited[s] = 1;
|
||||
|
||||
queueSize++;
|
||||
queue[queueSize - 1] = s;
|
||||
|
||||
while (queueSize)
|
||||
{
|
||||
s = queue[queueSize - 1];
|
||||
queueSize--;
|
||||
|
||||
result[*n] = s;
|
||||
(*n)++;
|
||||
|
||||
for (i = 0; i < G->n; i++)
|
||||
{
|
||||
if (GraphEdgeGet(G, s, i) && !visited[i])
|
||||
{
|
||||
visited[i] = 1;
|
||||
|
||||
queueSize++;
|
||||
queue[queueSize - 1] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Free(visited);
|
||||
Free(queue);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
GraphDepthFirstSearchRecursive(Graph * G, Node s, Node * result, size_t * n,
|
||||
Node * visited)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
visited[s] = 1;
|
||||
|
||||
result[*n] = s;
|
||||
(*n)++;
|
||||
|
||||
for (i = 0; i < G->n; i++)
|
||||
{
|
||||
if (GraphEdgeGet(G, s, i) && !visited[i])
|
||||
{
|
||||
GraphDepthFirstSearchRecursive(G, i, result, n, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Node *
|
||||
GraphDepthFirstSearch(Graph * G, Node s, size_t * n)
|
||||
{
|
||||
Node *visited;
|
||||
Node *result;
|
||||
|
||||
if (!G || !n)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
result = Malloc(G->n * sizeof(Node));
|
||||
if (!result)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*n = 0;
|
||||
|
||||
if (s >= G->n)
|
||||
{
|
||||
Free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
visited = Malloc(G->n * sizeof(Node));
|
||||
memset(visited, 0, G->n * sizeof(Node));
|
||||
|
||||
GraphDepthFirstSearchRecursive(G, s, result, n, visited);
|
||||
|
||||
Free(visited);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
GraphTopologicalSortRecursive(Graph * G, Node s, Node * visited,
|
||||
Node * stack, size_t * stackSize)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
visited[s] = 1;
|
||||
|
||||
for (i = 0; i < G->n; i++)
|
||||
{
|
||||
if (GraphEdgeGet(G, s, i) && !visited[i])
|
||||
{
|
||||
GraphTopologicalSortRecursive(G, i, visited, stack, stackSize);
|
||||
}
|
||||
}
|
||||
|
||||
stack[*stackSize] = s;
|
||||
(*stackSize)++;
|
||||
}
|
||||
|
||||
Node *
|
||||
GraphTopologicalSort(Graph * G, size_t * n)
|
||||
{
|
||||
Node *visited;
|
||||
Node *stack;
|
||||
Node *result;
|
||||
|
||||
size_t i;
|
||||
size_t stackSize;
|
||||
|
||||
if (!G || !n)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*n = 0;
|
||||
|
||||
result = Malloc(G->n * sizeof(Node));
|
||||
if (!result)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
visited = Malloc(G->n * sizeof(Node));
|
||||
memset(visited, 0, G->n * sizeof(Node));
|
||||
stack = Malloc(G->n * sizeof(Node));
|
||||
memset(stack, 0, G->n * sizeof(Node));
|
||||
|
||||
stackSize = 0;
|
||||
|
||||
for (i = 0; i < G->n; i++)
|
||||
{
|
||||
if (!visited[i])
|
||||
{
|
||||
GraphTopologicalSortRecursive(G, i, visited, stack, &stackSize);
|
||||
}
|
||||
}
|
||||
|
||||
Free(visited);
|
||||
|
||||
while (stackSize)
|
||||
{
|
||||
stackSize--;
|
||||
result[*n] = stack[stackSize];
|
||||
(*n)++;
|
||||
}
|
||||
|
||||
Free(stack);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Graph *
|
||||
GraphTranspose(Graph * G)
|
||||
{
|
||||
Graph *T = Malloc(sizeof(Graph));
|
||||
size_t i, j;
|
||||
|
||||
T->n = G->n;
|
||||
T->matrix = Malloc((G->n * G->n) * sizeof(Edge));
|
||||
|
||||
memset(T->matrix, 0, (T->n * T->n) * sizeof(Edge));
|
||||
|
||||
for (i = 0; i < G->n; i++)
|
||||
{
|
||||
for (j = 0; j < G->n; j++)
|
||||
{
|
||||
if (GraphEdgeGet(G, i, j))
|
||||
{
|
||||
GraphEdgeSet(T, j, i, GraphEdgeGet(G, i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return T;
|
||||
}
|
|
@ -1,456 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <HashMap.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Str.h>
|
||||
#include <Array.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);
|
||||
}
|
||||
}
|
||||
|
||||
Array *
|
||||
HashMapKeys(HashMap * map)
|
||||
{
|
||||
Array *arr;
|
||||
|
||||
char *key;
|
||||
void *val;
|
||||
|
||||
if (!map)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
arr = ArrayCreate();
|
||||
if (!arr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (HashMapIterate(map, &key, &val))
|
||||
{
|
||||
ArrayAdd(arr, key);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
Array *
|
||||
HashMapValues(HashMap * map)
|
||||
{
|
||||
Array *arr;
|
||||
|
||||
char *key;
|
||||
void *val;
|
||||
|
||||
if (!map)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
arr = ArrayCreate();
|
||||
if (!arr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (HashMapIterate(map, &key, &val))
|
||||
{
|
||||
ArrayAdd(arr, val);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
|
@ -1,665 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <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 */
|
||||
Free(word);
|
||||
}
|
||||
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 - 1);
|
||||
}
|
||||
|
||||
Free(word);
|
||||
}
|
||||
}
|
|
@ -1,642 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <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;
|
||||
}
|
|
@ -1,302 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <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 = HTTP_STATUS_UNKNOWN;
|
||||
|
||||
char *line = NULL;
|
||||
ssize_t lineLen;
|
||||
size_t lineSize = 0;
|
||||
char *tmp;
|
||||
|
||||
if (!context)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* Line must contain at least "HTTP/x.x xxx" */
|
||||
if (lineLen < 12)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!(strncmp(line, "HTTP/1.0", 8) == 0 ||
|
||||
strncmp(line, "HTTP/1.1", 8) == 0))
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
tmp = line + 9;
|
||||
|
||||
while (isspace((unsigned char) *tmp) && *tmp != '\0')
|
||||
{
|
||||
tmp++;
|
||||
}
|
||||
|
||||
if (!*tmp)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
status = atoi(tmp);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
status = HTTP_STATUS_UNKNOWN;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
context->responseHeaders = HttpParseHeaders(context->stream);
|
||||
if (!context->responseHeaders)
|
||||
{
|
||||
status = HTTP_STATUS_UNKNOWN;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
finish:
|
||||
Free(line);
|
||||
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);
|
||||
}
|
|
@ -1,301 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <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 = NULL;
|
||||
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 */
|
||||
char *substr;
|
||||
regmatch_t cpmatch;
|
||||
|
||||
for (i = 1; i < REG_MAX_SUB; i++)
|
||||
{
|
||||
cpmatch = pmatch[i];
|
||||
substr = StrSubstr(pathPart, cpmatch.rm_so, cpmatch.rm_eo);
|
||||
if (pmatch[i].rm_so == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ArrayAdd(matches, substr);
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
|
@ -1,755 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <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;
|
||||
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)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!config->handler)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifndef TLS_IMPL
|
||||
if (config->flags & HTTP_FLAG_TLS)
|
||||
{
|
||||
Log(LOG_ERR, "TLS support is disabled.");
|
||||
errno = EINVAL;
|
||||
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->sd = -1;
|
||||
|
||||
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 > -1)
|
||||
{
|
||||
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;
|
||||
|
||||
UInt64 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(UInt64Create(0, 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 || UInt64Gt(UInt64Sub(UtilServerTs(), firstRead), UInt64Create(0, 1000 * 30)))
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
UtilSleepMillis(UInt64Create(0, 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;
|
||||
|
||||
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)
|
||||
{
|
||||
close(connFd);
|
||||
pthread_mutex_unlock(&server->connQueueMutex);
|
||||
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;
|
||||
}
|
|
@ -1,399 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Int64.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <Log.h>
|
||||
|
||||
#ifdef INT64_NATIVE
|
||||
#define Int64Sign(x) ((int) (((UInt64) (x)) >> 63))
|
||||
#else
|
||||
#define Int64Sign(x) ((int) ((x).i[1] >> 31))
|
||||
#endif
|
||||
|
||||
size_t
|
||||
Int64Str(Int64 x, int base, char *out, size_t len)
|
||||
{
|
||||
static const char symbols[] = "0123456789ABCDEF";
|
||||
|
||||
size_t i = len - 1;
|
||||
size_t j = 0;
|
||||
|
||||
int neg = Int64Sign(x);
|
||||
|
||||
Int64 base64 = Int64Create(0, base);
|
||||
|
||||
/* We only have symbols up to base 16 */
|
||||
if (base < 2 || base > 16)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This algorithm doesn't work on INT64_MIN.
|
||||
*
|
||||
* But it works on all other integers in the range, so we
|
||||
* just scoot the range in by one for now. It's a hack and
|
||||
* I'm not a huge fan of it, but this function is mostly
|
||||
* used in Json, which shouldn't have a range this large
|
||||
* anyway (Json is limited to -2^53 -> 2^53-1).
|
||||
*
|
||||
* Proper fixes are always welcome.
|
||||
*/
|
||||
if (Int64Eq(x, Int64Create(0x80000000, 0x00000000)))
|
||||
{
|
||||
x = Int64Add(x, Int64Create(0, 1));
|
||||
}
|
||||
#if 0
|
||||
else if (Int64Eq(x, Int64Create(0x7FFFFFFF, 0xFFFFFFFF)))
|
||||
{
|
||||
x = Int64Sub(x, Int64Create(0, 1));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (base != 2 && base != 8 && base != 16 && neg)
|
||||
{
|
||||
x = Int64Neg(x);
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
Int64 mod = Int64Rem(x, base64);
|
||||
Int32 low = Int64Low(mod);
|
||||
|
||||
out[i] = symbols[low];
|
||||
i--;
|
||||
x = Int64Div(x, base64);
|
||||
} while (Int64Gt(x, Int64Create(0, 0)));
|
||||
|
||||
if (base != 2 && base != 8 && base != 16)
|
||||
{
|
||||
/*
|
||||
* Binary, octal, and hexadecimal are known to
|
||||
* be bit representations. Everything else (notably
|
||||
* decimal) should include the negative sign.
|
||||
*/
|
||||
if (neg)
|
||||
{
|
||||
out[i] = '-';
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
while (++i < len)
|
||||
{
|
||||
out[j++] = out[i];
|
||||
}
|
||||
|
||||
out[j] = '\0';
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
#ifndef INT64_NATIVE
|
||||
|
||||
/* No native 64-bit support, add our own */
|
||||
|
||||
Int64
|
||||
Int64Create(UInt32 high, UInt32 low)
|
||||
{
|
||||
Int64 x;
|
||||
|
||||
x.i[0] = low;
|
||||
x.i[1] = high;
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
Int64
|
||||
Int64Add(Int64 x, Int64 y)
|
||||
{
|
||||
Int64 z = Int64Create(0, 0);
|
||||
int carry;
|
||||
|
||||
z.i[0] = x.i[0] + y.i[0];
|
||||
carry = z.i[0] < x.i[0];
|
||||
z.i[1] = x.i[1] + y.i[1] + carry;
|
||||
|
||||
return z;
|
||||
}
|
||||
|
||||
Int64
|
||||
Int64Sub(Int64 x, Int64 y)
|
||||
{
|
||||
return Int64Add(x, Int64Neg(y));
|
||||
}
|
||||
|
||||
Int64
|
||||
Int64Mul(Int64 x, Int64 y)
|
||||
{
|
||||
Int64 z = Int64Create(0, 0);
|
||||
|
||||
int xneg = Int64Sign(x);
|
||||
int yneg = Int64Sign(y);
|
||||
|
||||
if (xneg)
|
||||
{
|
||||
x = Int64Neg(x);
|
||||
}
|
||||
|
||||
if (yneg)
|
||||
{
|
||||
y = Int64Neg(y);
|
||||
}
|
||||
|
||||
/* while (y > 0) */
|
||||
while (Int64Gt(y, Int64Create(0, 0)))
|
||||
{
|
||||
/* if (y & 1 != 0) */
|
||||
if (Int64Neq(Int64And(y, Int64Create(0, 1)), Int64Create(0, 0)))
|
||||
{
|
||||
z = Int64Add(z, x);
|
||||
}
|
||||
|
||||
x = Int64Sll(x, 1);
|
||||
y = Int64Sra(y, 1);
|
||||
}
|
||||
|
||||
if (xneg != yneg)
|
||||
{
|
||||
z = Int64Neg(z);
|
||||
}
|
||||
|
||||
return z;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Int64 q;
|
||||
Int64 r;
|
||||
} Int64Ldiv;
|
||||
|
||||
static Int64Ldiv
|
||||
Int64LongDivision(Int64 n, Int64 d)
|
||||
{
|
||||
Int64Ldiv o;
|
||||
|
||||
int i;
|
||||
|
||||
int nneg = Int64Sign(n);
|
||||
int dneg = Int64Sign(d);
|
||||
|
||||
o.q = Int64Create(0, 0);
|
||||
o.r = Int64Create(0, 0);
|
||||
|
||||
if (Int64Eq(d, Int64Create(0, 0)))
|
||||
{
|
||||
raise(SIGFPE);
|
||||
return o;
|
||||
}
|
||||
|
||||
if (nneg)
|
||||
{
|
||||
n = Int64Neg(n);
|
||||
}
|
||||
|
||||
if (dneg)
|
||||
{
|
||||
d = Int64Neg(d);
|
||||
}
|
||||
|
||||
for (i = 63; i >= 0; i--)
|
||||
{
|
||||
Int64 bit = Int64And(Int64Sra(n, i), Int64Create(0, 1));
|
||||
|
||||
o.r = Int64Sll(o.r, 1);
|
||||
o.r = Int64Or(o.r, bit);
|
||||
|
||||
if (Int64Geq(o.r, d))
|
||||
{
|
||||
o.r = Int64Sub(o.r, d);
|
||||
o.q = Int64Or(o.q, Int64Sll(Int64Create(0, 1), i));
|
||||
}
|
||||
}
|
||||
|
||||
if (nneg != dneg)
|
||||
{
|
||||
o.r = Int64Neg(o.r);
|
||||
o.q = Int64Neg(o.q);
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
Int64
|
||||
Int64Div(Int64 x, Int64 y)
|
||||
{
|
||||
return Int64LongDivision(x, y).q;
|
||||
}
|
||||
|
||||
Int64
|
||||
Int64Rem(Int64 x, Int64 y)
|
||||
{
|
||||
return Int64LongDivision(x, y).r;
|
||||
}
|
||||
|
||||
Int64
|
||||
Int64Sll(Int64 x, int y)
|
||||
{
|
||||
Int64 z;
|
||||
|
||||
if (!y)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
z = Int64Create(0, 0);
|
||||
|
||||
if (y < 32)
|
||||
{
|
||||
z.i[1] = (x.i[0] >> (32 - y)) | (x.i[1] << y);
|
||||
z.i[0] = x.i[0] << y;
|
||||
}
|
||||
else
|
||||
{
|
||||
z.i[1] = x.i[0] << (y - 32);
|
||||
}
|
||||
|
||||
return z;
|
||||
}
|
||||
|
||||
Int64
|
||||
Int64Sra(Int64 x, int y)
|
||||
{
|
||||
Int64 z;
|
||||
|
||||
int neg = Int64Sign(x);
|
||||
|
||||
if (!y)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
z = Int64Create(0, 0);
|
||||
|
||||
if (y < 32)
|
||||
{
|
||||
z.i[0] = (x.i[1] << (32 - y)) | (x.i[0] >> y);
|
||||
z.i[1] = x.i[1] >> y;
|
||||
}
|
||||
else
|
||||
{
|
||||
z.i[0] = x.i[1] >> (y - 32);
|
||||
}
|
||||
|
||||
if (neg)
|
||||
{
|
||||
Int64 mask = Int64Create(0xFFFFFFFF, 0xFFFFFFFF);
|
||||
|
||||
z = Int64Or(Int64Sll(mask, (64 - y)), z);
|
||||
}
|
||||
|
||||
return z;
|
||||
}
|
||||
|
||||
Int64
|
||||
Int64And(Int64 x, Int64 y)
|
||||
{
|
||||
return Int64Create(x.i[1] & y.i[1], x.i[0] & y.i[0]);
|
||||
}
|
||||
|
||||
Int64
|
||||
Int64Or(Int64 x, Int64 y)
|
||||
{
|
||||
return Int64Create(x.i[1] | y.i[1], x.i[0] | y.i[0]);
|
||||
}
|
||||
|
||||
Int64
|
||||
Int64Xor(Int64 x, Int64 y)
|
||||
{
|
||||
return Int64Create(x.i[1] ^ y.i[1], x.i[0] ^ y.i[0]);
|
||||
}
|
||||
|
||||
Int64
|
||||
Int64Not(Int64 x)
|
||||
{
|
||||
return Int64Create(~(x.i[1]), ~(x.i[0]));
|
||||
}
|
||||
|
||||
int
|
||||
Int64Eq(Int64 x, Int64 y)
|
||||
{
|
||||
return x.i[0] == y.i[0] && x.i[1] == y.i[1];
|
||||
}
|
||||
|
||||
int
|
||||
Int64Lt(Int64 x, Int64 y)
|
||||
{
|
||||
int xneg = Int64Sign(x);
|
||||
int yneg = Int64Sign(y);
|
||||
|
||||
if (xneg != yneg)
|
||||
{
|
||||
return xneg > yneg;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (xneg)
|
||||
{
|
||||
/* Both negative */
|
||||
return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Both positive */
|
||||
return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
Int64Gt(Int64 x, Int64 y)
|
||||
{
|
||||
int xneg = Int64Sign(x);
|
||||
int yneg = Int64Sign(y);
|
||||
|
||||
if (xneg != yneg)
|
||||
{
|
||||
return xneg < yneg;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (xneg)
|
||||
{
|
||||
/* Both negative */
|
||||
return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Both positive */
|
||||
return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,212 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <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;
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <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);
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#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);
|
||||
}
|
1435
Cytoplasm/src/Json.c
1435
Cytoplasm/src/Json.c
File diff suppressed because it is too large
Load diff
|
@ -1,393 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Log.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Util.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));
|
||||
pthread_mutex_init(&config->lock, NULL);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pthread_mutex_destroy(&config->lock);
|
||||
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, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StreamPrintf(config->out, "(%lu) ", UtilThreadNo());
|
||||
|
||||
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);
|
||||
}
|
|
@ -1,625 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Memory.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <Int.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;
|
||||
};
|
||||
|
||||
#define MEM_BOUND_TYPE UInt32
|
||||
#define MEM_BOUND 0xDEADBEEF
|
||||
|
||||
#define MEM_BOUND_LOWER(p) *((MEM_BOUND_TYPE *) p)
|
||||
#define MEM_BOUND_UPPER(p, x) *(((MEM_BOUND_TYPE *) (((UInt8 *) p) + x)) + 1)
|
||||
#define MEM_SIZE_ACTUAL(x) (((x) * sizeof(UInt8)) + (2 * sizeof(MEM_BOUND_TYPE)))
|
||||
|
||||
static pthread_mutex_t lock;
|
||||
static void (*hook) (MemoryAction, MemoryInfo *, void *) = MemoryDefaultHook;
|
||||
static void *hookArgs = NULL;
|
||||
|
||||
static MemoryInfo **allocations = NULL;
|
||||
static size_t allocationsSize = 0;
|
||||
static size_t allocationsLen = 0;
|
||||
|
||||
int
|
||||
MemoryRuntimeInit(void)
|
||||
{
|
||||
pthread_mutexattr_t attr;
|
||||
int ret = 0;
|
||||
|
||||
if (pthread_mutexattr_init(&attr) != 0)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
ret = pthread_mutex_init(&lock, &attr);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
|
||||
ret = (ret == 0);
|
||||
|
||||
finish:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
MemoryRuntimeDestroy(void)
|
||||
{
|
||||
MemoryFreeAll();
|
||||
return pthread_mutex_destroy(&lock) == 0;
|
||||
}
|
||||
|
||||
static size_t
|
||||
MemoryHash(void *p)
|
||||
{
|
||||
return (((size_t) p) >> 2 * 7) % allocationsSize;
|
||||
}
|
||||
|
||||
static int
|
||||
MemoryInsert(MemoryInfo * a)
|
||||
{
|
||||
size_t hash;
|
||||
|
||||
if (!allocations)
|
||||
{
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
MemoryCheck(MemoryInfo * a)
|
||||
{
|
||||
if (MEM_BOUND_LOWER(a->pointer) != MEM_BOUND ||
|
||||
MEM_BOUND_UPPER(a->pointer, a->size - (2 * sizeof(MEM_BOUND_TYPE))) != MEM_BOUND)
|
||||
{
|
||||
if (hook)
|
||||
{
|
||||
hook(MEMORY_CORRUPTED, a, hookArgs);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void *
|
||||
MemoryAllocate(size_t size, const char *file, int line)
|
||||
{
|
||||
void *p;
|
||||
MemoryInfo *a;
|
||||
|
||||
MemoryIterate(NULL, NULL);
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
a = malloc(sizeof(MemoryInfo));
|
||||
if (!a)
|
||||
{
|
||||
pthread_mutex_unlock(&lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
p = malloc(MEM_SIZE_ACTUAL(size));
|
||||
if (!p)
|
||||
{
|
||||
free(a);
|
||||
pthread_mutex_unlock(&lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(p, 0, MEM_SIZE_ACTUAL(size));
|
||||
MEM_BOUND_LOWER(p) = MEM_BOUND;
|
||||
MEM_BOUND_UPPER(p, size) = MEM_BOUND;
|
||||
|
||||
a->size = MEM_SIZE_ACTUAL(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 ((MEM_BOUND_TYPE *) p) + 1;
|
||||
}
|
||||
|
||||
void *
|
||||
MemoryReallocate(void *p, size_t size, const char *file, int line)
|
||||
{
|
||||
MemoryInfo *a;
|
||||
void *new = NULL;
|
||||
|
||||
MemoryIterate(NULL, NULL);
|
||||
|
||||
if (!p)
|
||||
{
|
||||
return MemoryAllocate(size, file, line);
|
||||
}
|
||||
|
||||
a = MemoryInfoGet(p);
|
||||
if (a)
|
||||
{
|
||||
pthread_mutex_lock(&lock);
|
||||
new = realloc(a->pointer, MEM_SIZE_ACTUAL(size));
|
||||
if (new)
|
||||
{
|
||||
MemoryDelete(a);
|
||||
a->size = MEM_SIZE_ACTUAL(size);
|
||||
a->file = file;
|
||||
a->line = line;
|
||||
|
||||
a->pointer = new;
|
||||
MemoryInsert(a);
|
||||
|
||||
MEM_BOUND_LOWER(a->pointer) = MEM_BOUND;
|
||||
MEM_BOUND_UPPER(a->pointer, size) = MEM_BOUND;
|
||||
|
||||
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 ((MEM_BOUND_TYPE *) new) + 1;
|
||||
}
|
||||
|
||||
void
|
||||
MemoryFree(void *p, const char *file, int line)
|
||||
{
|
||||
MemoryInfo *a;
|
||||
|
||||
MemoryIterate(NULL, NULL);
|
||||
|
||||
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 += MemoryInfoGetSize(allocations[i]);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
p = ((MEM_BOUND_TYPE *) p) - 1;
|
||||
hash = MemoryHash(p);
|
||||
|
||||
count = 0;
|
||||
while (count <= allocationsSize)
|
||||
{
|
||||
if (!allocations[hash] || allocations[hash]->pointer != p)
|
||||
{
|
||||
hash = (hash + 1) % allocationsSize;
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryCheck(allocations[hash]);
|
||||
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 ? a->size - (2 * sizeof(MEM_BOUND_TYPE)) : 0;
|
||||
}
|
||||
|
||||
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 ((MEM_BOUND_TYPE *) a->pointer) + 1;
|
||||
}
|
||||
|
||||
void
|
||||
MemoryIterate(void (*iterFunc) (MemoryInfo *, void *), void *args)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
for (i = 0; i < allocationsSize; i++)
|
||||
{
|
||||
if (allocations[i])
|
||||
{
|
||||
MemoryCheck(allocations[i]);
|
||||
if (iterFunc)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
static size_t
|
||||
HexPtr(unsigned long ptr, char * |