forked from Telodendria/Cytoplasm
Import new Cytoplasm library based off of code from Telodendria.
Telodendria doesn't use this library yet, but it will soon.
This commit is contained in:
commit
40eac30b5c
60 changed files with 15048 additions and 0 deletions
3
.cvsignore
Normal file
3
.cvsignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
build
|
||||
out
|
||||
*-leaked.txt
|
28
.indent.pro
vendored
Normal file
28
.indent.pro
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
-bad
|
||||
-bap
|
||||
-bbb
|
||||
-nbc
|
||||
-bl
|
||||
-c36
|
||||
-cd36
|
||||
-ncdb
|
||||
-nce
|
||||
-ci8
|
||||
-cli1
|
||||
-d0
|
||||
-di1
|
||||
-ndj
|
||||
-ei
|
||||
-fc1
|
||||
-i4
|
||||
-ip
|
||||
-l72
|
||||
-lc72
|
||||
-lp
|
||||
-npcs
|
||||
-psl
|
||||
-sc
|
||||
-nsob
|
||||
-nut
|
||||
-nv
|
||||
|
24
LICENSE.txt
Normal file
24
LICENSE.txt
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Cytoplasm (libcytoplasm)
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
158
README.txt
Normal file
158
README.txt
Normal file
|
@ -0,0 +1,158 @@
|
|||
Cytoplasm (libcytoplasm)
|
||||
========================================================================
|
||||
|
||||
Cytoplasm is a general-purpose C library and runtime stub for
|
||||
creating high-level (particularly networked and multi-threaded) C
|
||||
applications. It allows applications to take advantage of the speed,
|
||||
flexibility, and simplicity of the C programming language, while
|
||||
providing helpful code to perform various complex tasks. Cytoplasm
|
||||
provides high-level data structures, a basic logging facility, an
|
||||
HTTP client and server, and more.
|
||||
|
||||
Cytoplasm aims not to only do one thing well, but to do many things
|
||||
good enough. The primary target of Cytoplasm is simple, yet higher
|
||||
level C applications that have to perform relatively complex tasks,
|
||||
but don't want to pull in a large number of dependencies.
|
||||
|
||||
Cytoplasm is extremely opinionated on the way programs using it are
|
||||
written. It strives to create a comprehensive and tightly-integrated
|
||||
programming environment, while also maintaining C programming
|
||||
correctness. It doesn't do any macro magic or make C look like
|
||||
anything other than C. It is written entirely in C89, and depends
|
||||
only on a POSIX environment. This differentiates it from other
|
||||
general-purpose libraries that often require modern compilers and
|
||||
non-standard language and environment features. Cytoplasm is intended
|
||||
to be extremely portable and simple, while still providing some of
|
||||
the functionality expected in higher-level programming languages
|
||||
in a platform-agnostic manner. In the case of TLS, Cytoplasm wraps
|
||||
low-level TLS libraries to offer a single, unified interface to TLS
|
||||
so that programs do not have to care about the underlying
|
||||
implementation.
|
||||
|
||||
Cytoplasm is probably not suitable for embedded programming. It makes
|
||||
liberal use of the heap, and while data structures are designed to
|
||||
conserve memory where possible and practical, minimal memory usage
|
||||
is not really a design goal for Cytoplasm, although Cytoplasm takes
|
||||
care not to use any more memory than it absolutely needs. Cytoplasm
|
||||
also wraps a few standard libraries with additional logic and
|
||||
checking. While this ensures better runtime safetly, this inevitably
|
||||
adds a little overhead.
|
||||
|
||||
Originally a part of Telodendria (https://telodendria.io), a Matrix
|
||||
homeserver written in C, Cytoplasm was split off into its own project
|
||||
due to the desire of some Telodendria developers to use Telodendria's
|
||||
code in other projects. Cytoplasm is still a Telodendria project,
|
||||
and is maintained along side of Telodendria itself, even living in
|
||||
the same CVS module, but it is designed specifically to be distributed
|
||||
and used totally independent of Telodendria.
|
||||
|
||||
The name "Cytoplasm" was chosen for a few reasons. It plays off the
|
||||
precedent set up by the Matrix organization in naming projects after
|
||||
the parts of a neuron. It also speaks to the function of Cytoplasm.
|
||||
The cytoplasm of a cell is the supporting material. It is what gives
|
||||
the cell its shape, and it facilitates the movement of materials
|
||||
to the other cell parts. Likewise, Cytoplasm aims to provide a
|
||||
support mechanism for C applications that have to perform complex
|
||||
tasks.
|
||||
|
||||
Cytoplasm also starts with a C, which I think is a nice touch for C
|
||||
libraries. It's also fun to say and unique enough that searching for
|
||||
"libcytoplasm" should bring you to this project and not some other
|
||||
one.
|
||||
|
||||
Building
|
||||
------------------------------------------------------------------------
|
||||
|
||||
If your operating system or software distribution provides a pre-built
|
||||
package of Cytoplasm, you should prefer to use that instead of
|
||||
building it from source.
|
||||
|
||||
Cytoplasm aims to have zero dependencies beyond what is mandated
|
||||
by POSIX. You only need the standard math and pthread libraries to
|
||||
build it. TLS support can optionally be enabled by setting the
|
||||
TLS_IMPL environment variable. The supported TLS implementations
|
||||
are as follows:
|
||||
|
||||
* OpenSSL
|
||||
* LibreSSL
|
||||
|
||||
If TLS support is not enabled, all APIs that use it should fall
|
||||
back to non-TLS behavior in a sensible manner. For example, if TLS
|
||||
support is not enabled, then the HTTP client API will simply return
|
||||
an error if a TLS connection is requested.
|
||||
|
||||
Cytoplasm uses a custom build script instead of Make, for the sake
|
||||
of portability. To build everything, just run the script:
|
||||
|
||||
$ sh make.sh
|
||||
|
||||
This will produce the following out/ directory:
|
||||
|
||||
out/
|
||||
lib/
|
||||
libcytoplasm.so - The Cytoplasm shared library.
|
||||
libcytoplasm.a - The Cytoplasm static library.
|
||||
cytoplasm.o - The Cytoplasm runtime stub.
|
||||
bin/
|
||||
hdoc - The documentation generator tool.
|
||||
man/ - All Cytoplasm API documentation.
|
||||
|
||||
Usage
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Cytoplasm provides the typical .so and .a files, which can be used
|
||||
to link programs with it in the usual way. However, Cytoplasm also
|
||||
provides a minimal runtime environment that is intended to be used
|
||||
with the library. As such, it also provides a runtime stub, which
|
||||
is intended to be linked in with programs using Cytoplasm. This
|
||||
stub is responsible for setting up and tearing down some Cytoplasm
|
||||
APIs. While it isn't required by any means, it makes Cytoplasm a
|
||||
lot easier to use for programmers by abstracting out all of the
|
||||
common logic that most programs will want to use.
|
||||
|
||||
Here is the canonical Hello World written with Cytoplasm:
|
||||
|
||||
#include <Log.h>
|
||||
|
||||
int Main(void)
|
||||
{
|
||||
Log(LOG_INFO, "Hello World!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
If this file is Hello.c, then you can compile it by doing something
|
||||
like this:
|
||||
|
||||
$ cc -o hello Hello.c cytoplasm.o -lcytoplasm
|
||||
|
||||
This command assumes that the runtime stub resides in the current
|
||||
working directory, and that libcytoplasm.so is in your library path.
|
||||
If you're using the version of Cytoplasm installed by your operating
|
||||
system or software distribution, consult the documentation for the
|
||||
location of the runtime stub. It may be located in
|
||||
/usr/local/libexec or some other similar location. If you've built
|
||||
Cytoplasm from source and wish to link to it from there, you may wish
|
||||
to do something like this:
|
||||
|
||||
$ export CYTOPLASM=/path/to/Cytoplasm/out/lib
|
||||
$ cc -o hello Hello.c "${CYTOPLASM}/cytoplasm.o" \
|
||||
"-L${CYTOPLASM}" -lcytoplasm
|
||||
|
||||
As you may have noticed, C programs using Cytoplasm's runtime stub
|
||||
don't write the main() function. Instead, they use Main(). The main()
|
||||
function is provided by the runtime stub. The full form of Main()
|
||||
expected by the stub is as follows:
|
||||
|
||||
int Main(Array *args, HashMap *env);
|
||||
|
||||
The first argument is a Cytoplasm array of the command line
|
||||
arguments, and the second is a Cytoplasm hash map of environment
|
||||
variables. Most linkers will let programs omit the env argument,
|
||||
or both arguments if you don't need either. The return value of
|
||||
Main() is returned to the operating system.
|
||||
|
||||
Note that both arguments to Main may be treated like any other
|
||||
array or hash map. However, do not invoke ArrayFree() or HashMapFree()
|
||||
on the passed pointers, because memory is cleaned up after Main()
|
||||
returns.
|
||||
|
301
make.sh
Executable file
301
make.sh
Executable file
|
@ -0,0 +1,301 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
addprefix() {
|
||||
prefix="$1"
|
||||
shift
|
||||
for thing in "$@"; do
|
||||
echo "${prefix}${thing}"
|
||||
done
|
||||
|
||||
unset prefix
|
||||
unset thing
|
||||
}
|
||||
|
||||
: "${NAME:=Cytoplasm}"
|
||||
: "${LIB_NAME:=$(echo ${NAME} | tr '[A-Z]' '[a-z]')}"
|
||||
: "${VERSION:=0.3.0}"
|
||||
|
||||
: "${CVS_TAG:=${NAME}-$(echo ${VERSION} | sed 's/\./_/g')}"
|
||||
|
||||
|
||||
: "${SRC:=src}"
|
||||
: "${TOOLS:=tools}"
|
||||
: "${BUILD:=build}"
|
||||
: "${OUT:=out}"
|
||||
: "${STUB:=RtStub}"
|
||||
: "${LICENSE:=LICENSE.txt}"
|
||||
|
||||
: "${CC:=cc}"
|
||||
: "${AR:=ar}"
|
||||
|
||||
: "${DEFINES:=-D_DEFAULT_SOURCE -DLIB_NAME=\"${NAME}\" -DLIB_VERSION=\"${VERSION}\"}"
|
||||
: "${INCLUDE:=${SRC}/include}"
|
||||
|
||||
: "${CFLAGS:=-Wall -Wextra -pedantic -std=c89 -O3 -pipe}"
|
||||
: "${LD_EXTRA:=-flto -fdata-sections -ffunction-sections -s -Wl,-gc-sections}"
|
||||
: "${LDFLAGS:=-lm -pthread}"
|
||||
|
||||
if [ -n "${TLS_IMPL}" ]; then
|
||||
case "${TLS_IMPL}" in
|
||||
"LIBRESSL")
|
||||
TLS_LIBS="-ltls -lcrypto -lssl"
|
||||
;;
|
||||
"OPENSSL")
|
||||
TLS_LIBS="-lcrypto -lssl"
|
||||
;;
|
||||
*)
|
||||
echo "Unrecognized TLS implementation: ${TLS_IMPL}"
|
||||
echo "Consult Tls.h for supported implementations."
|
||||
echo "Note that the TLS_ prefix is omitted in TLS_IMPL."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
DEFINES="${DEFINES} -DTLS_IMPL=TLS_${TLS_IMPL}"
|
||||
LDFLAGS="${LDFLAGS} ${TLS_LIBS}"
|
||||
fi
|
||||
|
||||
CFLAGS="${CFLAGS} ${DEFINES} $(addprefix -I$(pwd)/ ${INCLUDE})"
|
||||
LDFLAGS="${LDFLAGS} ${LD_EXTRA}"
|
||||
|
||||
# Check the modificiation time of a file. This is used to do
|
||||
# incremental builds; we only want to rebuild files that have
|
||||
# have changed.
|
||||
mod_time() {
|
||||
if [ -n "$1" ] && [ -f "$1" ]; then
|
||||
case "$(uname)" in
|
||||
Linux | CYGWIN_NT* | Haiku)
|
||||
stat -c %Y "$1"
|
||||
;;
|
||||
*BSD | DragonFly | Minix)
|
||||
stat -f %m "$1"
|
||||
;;
|
||||
*)
|
||||
# Platform unknown, force rebuilding the whole
|
||||
# project every time.
|
||||
echo "0"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo "0"
|
||||
fi
|
||||
}
|
||||
|
||||
# Substitute shell variables in a stream with their actual value
|
||||
# in this shell.
|
||||
setsubst() {
|
||||
SED="/tmp/sed-$RANDOM.txt"
|
||||
|
||||
(
|
||||
set | while IFS='=' read -r var val; do
|
||||
val=$(echo "$val" | cut -d "'" -f 2- | rev | cut -d "'" -f 2- | rev)
|
||||
echo "s|\\\${$var}|$val|g"
|
||||
done
|
||||
|
||||
echo "s|\\\${[a-zA-Z_]*}||g"
|
||||
echo "s|'''|'|g"
|
||||
) >"$SED"
|
||||
|
||||
sed -f "$SED" $@
|
||||
rm "$SED"
|
||||
}
|
||||
|
||||
recipe_docs() {
|
||||
export LD_LIBRARY_PATH="${OUT}/lib"
|
||||
mkdir -p "${OUT}/man/man3"
|
||||
|
||||
for header in $(find ${INCLUDE} -name '*.h'); do
|
||||
basename=$(basename "$header")
|
||||
man=$(echo "${OUT}/man/man3/$basename" | sed -e 's/\.h$/\.3/')
|
||||
|
||||
if [ $(mod_time "$header") -ge $(mod_time "$man") ]; then
|
||||
echo "DOC $basename"
|
||||
if ! "${OUT}/bin/hdoc" -D "Os=${NAME}" -i "$header" -o "$man"; then
|
||||
rm "$man"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if which makewhatis 2>&1 > /dev/null; then
|
||||
makewhatis "${OUT}/man"
|
||||
fi
|
||||
}
|
||||
|
||||
recipe_build() {
|
||||
mkdir -p "${BUILD}" ${OUT}/{bin,lib}
|
||||
cd "${SRC}"
|
||||
|
||||
echo "CC = ${CC}"
|
||||
echo "CFLAGS = ${CFLAGS}"
|
||||
echo "LDFLAGS = ${LDFLAGS}"
|
||||
echo
|
||||
|
||||
do_rebuild=0
|
||||
objs=""
|
||||
for src in $(find . -name '*.c' | cut -d '/' -f 2-); do
|
||||
obj=$(echo "${BUILD}/$src" | sed -e 's/\.c$/\.o/')
|
||||
|
||||
if [ $(basename "$obj" .o) != "${STUB}" ]; then
|
||||
objs="$objs $obj"
|
||||
fi
|
||||
|
||||
if [ $(mod_time "$src") -ge $(mod_time "../$obj") ]; then
|
||||
echo "CC $(basename $obj)"
|
||||
obj_dir=$(dirname "../$obj")
|
||||
mkdir -p "$obj_dir"
|
||||
if ! $CC $CFLAGS -fPIC -c -o "../$obj" "$src"; then
|
||||
exit 1
|
||||
fi
|
||||
do_rebuild=1
|
||||
fi
|
||||
done
|
||||
|
||||
cd ..
|
||||
|
||||
if [ $do_rebuild -eq 1 ] || [ ! -f "${OUT}/lib/lib${LIB_NAME}.a" ]; then
|
||||
echo "AR lib${LIB_NAME}.a"
|
||||
if ! $AR rcs "${OUT}/lib/lib${LIB_NAME}.a" $objs; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $do_rebuild -eq 1 ] || [ ! -f "${OUT}/lib/lib${LIB_NAME}.so" ]; then
|
||||
echo "LD lib${LIB_NAME}.so"
|
||||
if ! $CC -shared -o "${OUT}/lib/lib${LIB_NAME}.so" $objs ${LDFLAGS}; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
cp "${BUILD}/${STUB}.o" "${OUT}/lib/${LIB_NAME}.o"
|
||||
|
||||
for src in $(find "${TOOLS}" -name '*.c'); do
|
||||
out=$(basename "$src" .c)
|
||||
out="${OUT}/bin/$out"
|
||||
|
||||
if [ $(mod_time "$src") -ge $(mod_time "$out") ] || [ $do_rebuild -eq 1 ]; then
|
||||
echo "CC $(basename $out)"
|
||||
mkdir -p "$(dirname $out)"
|
||||
if ! $CC $CFLAGS -o "$out" "$src" "${OUT}/lib/${LIB_NAME}.o" "-L${OUT}/lib" "-l${LIB_NAME}" ${LDFLAGS}; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
recipe_docs
|
||||
}
|
||||
|
||||
recipe_clean() {
|
||||
rm -r "${BUILD}" "${OUT}"
|
||||
}
|
||||
|
||||
# Update copyright comments in sources and header files.
|
||||
recipe_license() {
|
||||
find . -name '*.[ch]' | while IFS= read -r src; do
|
||||
if [ -t 1 ]; then
|
||||
printf "LICENSE %s%*c\r" $(basename "$src") "16" " "
|
||||
fi
|
||||
srcHeader=$(grep -n -m 1 '^ \*/' "$src" | cut -d ':' -f 1)
|
||||
head "-n$srcHeader" "$src" |
|
||||
diff -u -p - "${LICENSE}" |
|
||||
patch "$src" | grep -v "^Hmm"
|
||||
done
|
||||
if [ -t 1 ]; then
|
||||
printf "%*c\n" "50" " "
|
||||
fi
|
||||
}
|
||||
|
||||
# Format source code files by running indent(1) on them.
|
||||
recipe_format() {
|
||||
find . -name '*.c' | while IFS= read -r src; do
|
||||
if [ -t 1 ]; then
|
||||
printf "FMT %s%*c\r" $(basename "$src") "16" " "
|
||||
fi
|
||||
if indent "$src"; then
|
||||
rm $(basename "$src").BAK
|
||||
fi
|
||||
done
|
||||
if [ -t 1 ]; then
|
||||
printf "%*c\n" "50" " "
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate a release tarball, checksum and sign it, and push it to
|
||||
# the web root.
|
||||
recipe_release() {
|
||||
# Tag the release at this point in time.
|
||||
cvs tag "$CVS_TAG"
|
||||
|
||||
mkdir -p "${OUT}/release"
|
||||
cd "${OUT}/release"
|
||||
|
||||
# Generate the release tarball.
|
||||
cvs export "-r$CVS_TAG" "${NAME}"
|
||||
mv "${NAME}" "${NAME}-v${VERSION}"
|
||||
tar -czf "${NAME}-v${VERSION}.tar.gz" "${NAME}-v${VERSION}"
|
||||
rm -r "${NAME}-v${VERSION}"
|
||||
|
||||
# Checksum the release tarball.
|
||||
sha256 "${NAME}-v${VERSION}.tar.gz" > "${NAME}-v${VERSION}.tar.gz.sha256"
|
||||
|
||||
# Sign the release tarball.
|
||||
if [ ! -z "${SIGNIFY_SECRET}" ]; then
|
||||
signify -S -s "${SIGNIFY_SECRET}" \
|
||||
-m "${NAME}-v${VERSION}.tar.gz" \
|
||||
-x "${NAME}-v${VERSION}.tar.gz.sig"
|
||||
else
|
||||
echo "Warning: SIGNIFY_SECRET not net."
|
||||
echo "The built tarball will not be signed."
|
||||
fi
|
||||
}
|
||||
|
||||
recipe_patch() {
|
||||
# If the user has not set their MXID, try to deduce one from
|
||||
# their system.
|
||||
if [ -z "$MXID" ]; then
|
||||
MXID="@${USER}:$(hostname)"
|
||||
fi
|
||||
|
||||
# If the user has not set their EDITOR, use a safe default.
|
||||
# (vi should be available on any POSIX system)
|
||||
if [ -z "$EDITOR" ]; then
|
||||
EDITOR=vi
|
||||
fi
|
||||
|
||||
NORMALIZED_MXID=$(echo "$MXID" | sed -e 's/@//g' -e 's/:/-/g')
|
||||
PATCH_FILE="${NORMALIZED_MXID}_$(date +%s).patch"
|
||||
|
||||
# Generate the actual patch file
|
||||
# Here we write some nice headers, and then do a cvs diff.
|
||||
(
|
||||
printf 'From: "%s" <%s>\n' "$DISPLAY_NAME" "$MXID"
|
||||
echo "Date: $(date)"
|
||||
echo "Subject: "
|
||||
echo
|
||||
cvs -q diff -uNp $PATCHSET | grep -v '^\? '
|
||||
) >"$PATCH_FILE"
|
||||
|
||||
"$EDITOR" "$PATCH_FILE"
|
||||
echo "$PATCH_FILE"
|
||||
}
|
||||
|
||||
recipe_diff() {
|
||||
tmp_patch="/tmp/${NAME}-$(date +%s).patch"
|
||||
cvs -q diff -uNp $PATCHSET >"$tmp_patch"
|
||||
if [ -z "$PAGER" ]; then
|
||||
PAGER="less -F"
|
||||
fi
|
||||
|
||||
$PAGER "$tmp_patch"
|
||||
rm "$tmp_patch"
|
||||
}
|
||||
|
||||
# Execute the user-specified recipes.
|
||||
for recipe in $@; do
|
||||
recipe_$recipe
|
||||
done
|
||||
|
||||
# If no recipe was provided, run a build.
|
||||
if [ -z "$1" ]; then
|
||||
recipe_build
|
||||
fi
|
118
src/Args.c
Normal file
118
src/Args.c
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Args.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Log.h>
|
||||
#include <Str.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
void
|
||||
ArgParseStateInit(ArgParseState * state)
|
||||
{
|
||||
state->optPos = 1;
|
||||
state->optErr = 1;
|
||||
state->optInd = 1;
|
||||
state->optOpt = 0;
|
||||
state->optArg = NULL;
|
||||
}
|
||||
|
||||
int
|
||||
ArgParse(ArgParseState * state, Array * args, const char *optStr)
|
||||
{
|
||||
const char *arg;
|
||||
|
||||
arg = ArrayGet(args, state->optInd);
|
||||
|
||||
if (arg && StrEquals(arg, "--"))
|
||||
{
|
||||
state->optInd++;
|
||||
return -1;
|
||||
}
|
||||
else if (!arg || arg[0] != '-' || !isalnum((unsigned char) arg[1]))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *opt = strchr(optStr, arg[state->optPos]);
|
||||
|
||||
state->optOpt = arg[state->optPos];
|
||||
|
||||
if (!opt)
|
||||
{
|
||||
if (state->optErr && *optStr != ':')
|
||||
{
|
||||
Log(LOG_ERR, "Illegal option: %c", ArrayGet(args, 0), state->optOpt);
|
||||
}
|
||||
if (!arg[++state->optPos])
|
||||
{
|
||||
state->optInd++;
|
||||
state->optPos = 1;
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
else if (opt[1] == ':')
|
||||
{
|
||||
if (arg[state->optPos + 1])
|
||||
{
|
||||
state->optArg = (char *) arg + state->optPos + 1;
|
||||
state->optInd++;
|
||||
state->optPos = 1;
|
||||
return state->optOpt;
|
||||
}
|
||||
else if (ArrayGet(args, state->optInd + 1))
|
||||
{
|
||||
state->optArg = (char *) ArrayGet(args, state->optInd + 1);
|
||||
state->optInd += 2;
|
||||
state->optPos = 1;
|
||||
return state->optOpt;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state->optErr && *optStr != ':')
|
||||
{
|
||||
Log(LOG_ERR, "Option requires an argument: %c", state->optOpt);
|
||||
}
|
||||
if (!arg[++state->optPos])
|
||||
{
|
||||
state->optInd++;
|
||||
state->optPos = 1;
|
||||
}
|
||||
return *optStr == ':' ? ':' : '?';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!arg[++state->optPos])
|
||||
{
|
||||
state->optInd++;
|
||||
state->optPos = 1;
|
||||
}
|
||||
return state->optOpt;
|
||||
}
|
||||
}
|
||||
}
|
339
src/Array.c
Normal file
339
src/Array.c
Normal file
|
@ -0,0 +1,339 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Array.h>
|
||||
|
||||
#ifndef ARRAY_BLOCK
|
||||
#define ARRAY_BLOCK 16
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <Memory.h>
|
||||
|
||||
struct Array
|
||||
{
|
||||
void **entries; /* An array of void pointers, to
|
||||
* store any data */
|
||||
size_t allocated; /* Elements allocated on the heap */
|
||||
size_t size; /* Elements actually filled */
|
||||
};
|
||||
|
||||
int
|
||||
ArrayAdd(Array * array, void *value)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ArrayInsert(array, array->size, value);
|
||||
}
|
||||
|
||||
Array *
|
||||
ArrayCreate(void)
|
||||
{
|
||||
Array *array = Malloc(sizeof(Array));
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
array->size = 0;
|
||||
array->allocated = ARRAY_BLOCK;
|
||||
array->entries = Malloc(sizeof(void *) * ARRAY_BLOCK);
|
||||
|
||||
if (!array->entries)
|
||||
{
|
||||
Free(array);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
void *
|
||||
ArrayDelete(Array * array, size_t index)
|
||||
{
|
||||
size_t i;
|
||||
void *element;
|
||||
|
||||
if (!array || array->size <= index)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
element = array->entries[index];
|
||||
|
||||
for (i = index; i < array->size - 1; i++)
|
||||
{
|
||||
array->entries[i] = array->entries[i + 1];
|
||||
}
|
||||
|
||||
array->size--;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
void
|
||||
ArrayFree(Array * array)
|
||||
{
|
||||
if (array)
|
||||
{
|
||||
Free(array->entries);
|
||||
Free(array);
|
||||
}
|
||||
}
|
||||
|
||||
void *
|
||||
ArrayGet(Array * array, size_t index)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (index >= array->size)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return array->entries[index];
|
||||
}
|
||||
|
||||
|
||||
extern int
|
||||
ArrayInsert(Array * array, size_t index, void *value)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!array || !value || index > array->size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (array->size >= array->allocated)
|
||||
{
|
||||
void **tmp;
|
||||
size_t newSize = array->allocated + ARRAY_BLOCK;
|
||||
|
||||
tmp = array->entries;
|
||||
|
||||
array->entries = Realloc(array->entries,
|
||||
sizeof(void *) * newSize);
|
||||
|
||||
if (!array->entries)
|
||||
{
|
||||
array->entries = tmp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
array->allocated = newSize;
|
||||
}
|
||||
|
||||
for (i = array->size; i > index; i--)
|
||||
{
|
||||
array->entries[i] = array->entries[i - 1];
|
||||
}
|
||||
|
||||
array->size++;
|
||||
|
||||
array->entries[index] = value;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
extern void *
|
||||
ArraySet(Array * array, size_t index, void *value)
|
||||
{
|
||||
void *oldValue;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
return ArrayDelete(array, index);
|
||||
}
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (index >= array->size)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
oldValue = array->entries[index];
|
||||
array->entries[index] = value;
|
||||
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
size_t
|
||||
ArraySize(Array * array)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return array->size;
|
||||
}
|
||||
|
||||
int
|
||||
ArrayTrim(Array * array)
|
||||
{
|
||||
void **tmp;
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
tmp = array->entries;
|
||||
|
||||
array->entries = Realloc(array->entries,
|
||||
sizeof(void *) * array->size);
|
||||
|
||||
if (!array->entries)
|
||||
{
|
||||
array->entries = tmp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
ArraySwap(Array * array, size_t i, size_t j)
|
||||
{
|
||||
void *p = array->entries[i];
|
||||
|
||||
array->entries[i] = array->entries[j];
|
||||
array->entries[j] = p;
|
||||
}
|
||||
|
||||
static size_t
|
||||
ArrayPartition(Array * array, size_t low, size_t high, int (*compare) (void *, void *))
|
||||
{
|
||||
void *pivot = array->entries[high];
|
||||
size_t i = low - 1;
|
||||
size_t j;
|
||||
|
||||
for (j = low; j <= high - 1; j++)
|
||||
{
|
||||
if (compare(array->entries[j], pivot) < 0)
|
||||
{
|
||||
i++;
|
||||
ArraySwap(array, i, j);
|
||||
}
|
||||
}
|
||||
ArraySwap(array, i + 1, high);
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
static void
|
||||
ArrayQuickSort(Array * array, size_t low, size_t high, int (*compare) (void *, void *))
|
||||
{
|
||||
if (low < high)
|
||||
{
|
||||
size_t pi = ArrayPartition(array, low, high, compare);
|
||||
|
||||
ArrayQuickSort(array, low, pi - 1, compare);
|
||||
ArrayQuickSort(array, pi + 1, high, compare);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArraySort(Array * array, int (*compare) (void *, void *))
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ArrayQuickSort(array, 0, array->size, compare);
|
||||
}
|
||||
|
||||
/* Even though the following operations could be done using only the
|
||||
* public Array API defined above, I opted for low-level struct
|
||||
* manipulation because it allows much more efficient copying; we only
|
||||
* allocate what we for sure need upfront, and don't have to
|
||||
* re-allocate during the operation. */
|
||||
|
||||
Array *
|
||||
ArrayFromVarArgs(size_t n, va_list ap)
|
||||
{
|
||||
size_t i;
|
||||
Array *arr = Malloc(sizeof(Array));
|
||||
|
||||
if (!arr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
arr->size = n;
|
||||
arr->allocated = n;
|
||||
arr->entries = Malloc(sizeof(void *) * arr->allocated);
|
||||
|
||||
if (!arr->entries)
|
||||
{
|
||||
Free(arr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
arr->entries[i] = va_arg(ap, void *);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
Array *
|
||||
ArrayDuplicate(Array * arr)
|
||||
{
|
||||
size_t i;
|
||||
Array *arr2 = Malloc(sizeof(Array));
|
||||
|
||||
if (!arr2)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
arr2->size = arr->size;
|
||||
arr2->allocated = arr->size;
|
||||
arr2->entries = Malloc(sizeof(void *) * arr->allocated);
|
||||
|
||||
if (!arr2->entries)
|
||||
{
|
||||
Free(arr2);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < arr2->size; i++)
|
||||
{
|
||||
arr2->entries[i] = arr->entries[i];
|
||||
}
|
||||
|
||||
return arr2;
|
||||
}
|
244
src/Base64.c
Normal file
244
src/Base64.c
Normal file
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Base64.h>
|
||||
|
||||
#include <Memory.h>
|
||||
|
||||
static const char Base64EncodeMap[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
static const int Base64DecodeMap[] = {
|
||||
62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58,
|
||||
59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5,
|
||||
6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
||||
21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
|
||||
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
|
||||
43, 44, 45, 46, 47, 48, 49, 50, 51
|
||||
};
|
||||
|
||||
size_t
|
||||
Base64EncodedSize(size_t inputSize)
|
||||
{
|
||||
size_t size = inputSize;
|
||||
|
||||
if (inputSize % 3)
|
||||
{
|
||||
size += 3 - (inputSize % 3);
|
||||
}
|
||||
|
||||
size /= 3;
|
||||
size *= 4;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t
|
||||
Base64DecodedSize(const char *base64, size_t len)
|
||||
{
|
||||
size_t ret;
|
||||
size_t i;
|
||||
|
||||
if (!base64)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = len / 4 * 3;
|
||||
|
||||
for (i = len; i > 0; i--)
|
||||
{
|
||||
if (base64[i] == '=')
|
||||
{
|
||||
ret--;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *
|
||||
Base64Encode(const char *input, size_t len)
|
||||
{
|
||||
char *out;
|
||||
size_t outLen;
|
||||
size_t i, j, v;
|
||||
|
||||
if (!input || !len)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
outLen = Base64EncodedSize(len);
|
||||
out = Malloc(outLen + 1);
|
||||
if (!out)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
out[outLen] = '\0';
|
||||
|
||||
for (i = 0, j = 0; i < len; i += 3, j += 4)
|
||||
{
|
||||
v = input[i];
|
||||
v = i + 1 < len ? v << 8 | input[i + 1] : v << 8;
|
||||
v = i + 2 < len ? v << 8 | input[i + 2] : v << 8;
|
||||
|
||||
out[j] = Base64EncodeMap[(v >> 18) & 0x3F];
|
||||
out[j + 1] = Base64EncodeMap[(v >> 12) & 0x3F];
|
||||
|
||||
if (i + 1 < len)
|
||||
{
|
||||
out[j + 2] = Base64EncodeMap[(v >> 6) & 0x3F];
|
||||
}
|
||||
else
|
||||
{
|
||||
out[j + 2] = '=';
|
||||
}
|
||||
if (i + 2 < len)
|
||||
{
|
||||
out[j + 3] = Base64EncodeMap[v & 0x3F];
|
||||
}
|
||||
else
|
||||
{
|
||||
out[j + 3] = '=';
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static int
|
||||
Base64IsValidChar(char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
(c >= 'a' && c <= 'z') ||
|
||||
(c == '+') ||
|
||||
(c == '/') ||
|
||||
(c == '=');
|
||||
}
|
||||
|
||||
char *
|
||||
Base64Decode(const char *input, size_t len)
|
||||
{
|
||||
size_t i, j;
|
||||
int v;
|
||||
size_t outLen;
|
||||
char *out;
|
||||
|
||||
if (!input)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
outLen = Base64DecodedSize(input, len);
|
||||
if (len % 4)
|
||||
{
|
||||
/* Invalid length; must have incorrect padding */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Scan for invalid characters. */
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
if (!Base64IsValidChar(input[i]))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
out = Malloc(outLen + 1);
|
||||
if (!out)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out[outLen] = '\0';
|
||||
|
||||
for (i = 0, j = 0; i < len; i += 4, j += 3)
|
||||
{
|
||||
v = Base64DecodeMap[input[i] - 43];
|
||||
v = (v << 6) | Base64DecodeMap[input[i + 1] - 43];
|
||||
v = input[i + 2] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 2] - 43];
|
||||
v = input[i + 3] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 3] - 43];
|
||||
|
||||
out[j] = (v >> 16) & 0xFF;
|
||||
if (input[i + 2] != '=')
|
||||
out[j + 1] = (v >> 8) & 0xFF;
|
||||
if (input[i + 3] != '=')
|
||||
out[j + 2] = v & 0xFF;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
extern void
|
||||
Base64Unpad(char *base64, size_t length)
|
||||
{
|
||||
if (!base64)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (base64[length - 1] == '=')
|
||||
{
|
||||
length--;
|
||||
}
|
||||
|
||||
base64[length] = '\0';
|
||||
}
|
||||
|
||||
extern int
|
||||
Base64Pad(char **base64Ptr, size_t length)
|
||||
{
|
||||
char *tmp;
|
||||
size_t newSize;
|
||||
size_t i;
|
||||
|
||||
if (length % 4 == 0)
|
||||
{
|
||||
return length; /* Success: no padding needed */
|
||||
}
|
||||
|
||||
newSize = length + (4 - (length % 4));
|
||||
|
||||
tmp = Realloc(*base64Ptr, newSize + 100);;
|
||||
if (!tmp)
|
||||
{
|
||||
return 0; /* Memory error */
|
||||
}
|
||||
*base64Ptr = tmp;
|
||||
|
||||
for (i = length; i < newSize; i++)
|
||||
{
|
||||
(*base64Ptr)[i] = '=';
|
||||
}
|
||||
|
||||
(*base64Ptr)[newSize] = '\0';
|
||||
|
||||
return newSize;
|
||||
}
|
249
src/Cron.c
Normal file
249
src/Cron.c
Normal file
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Cron.h>
|
||||
|
||||
#include <Array.h>
|
||||
#include <Memory.h>
|
||||
#include <Util.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
struct Cron
|
||||
{
|
||||
unsigned long tick;
|
||||
Array *jobs;
|
||||
pthread_mutex_t lock;
|
||||
volatile unsigned int stop:1;
|
||||
pthread_t thread;
|
||||
};
|
||||
|
||||
typedef struct Job
|
||||
{
|
||||
unsigned long interval;
|
||||
unsigned long lastExec;
|
||||
JobFunc *func;
|
||||
void *args;
|
||||
} Job;
|
||||
|
||||
static Job *
|
||||
JobCreate(long interval, JobFunc * func, void *args)
|
||||
{
|
||||
Job *job;
|
||||
|
||||
if (!func)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
job = Malloc(sizeof(Job));
|
||||
if (!job)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
job->interval = interval;
|
||||
job->lastExec = 0;
|
||||
job->func = func;
|
||||
job->args = args;
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
static void *
|
||||
CronThread(void *args)
|
||||
{
|
||||
Cron *cron = args;
|
||||
|
||||
while (!cron->stop)
|
||||
{
|
||||
size_t i;
|
||||
unsigned long ts; /* tick start */
|
||||
unsigned long te; /* tick end */
|
||||
|
||||
pthread_mutex_lock(&cron->lock);
|
||||
|
||||
ts = UtilServerTs();
|
||||
|
||||
for (i = 0; i < ArraySize(cron->jobs); i++)
|
||||
{
|
||||
Job *job = ArrayGet(cron->jobs, i);
|
||||
|
||||
if (ts - job->lastExec > job->interval)
|
||||
{
|
||||
job->func(job->args);
|
||||
job->lastExec = ts;
|
||||
}
|
||||
|
||||
if (!job->interval)
|
||||
{
|
||||
ArrayDelete(cron->jobs, i);
|
||||
Free(job);
|
||||
}
|
||||
}
|
||||
te = UtilServerTs();
|
||||
|
||||
pthread_mutex_unlock(&cron->lock);
|
||||
|
||||
/* Only sleep if the jobs didn't overrun the tick */
|
||||
if (cron->tick > (te - ts))
|
||||
{
|
||||
const unsigned long microTick = 100;
|
||||
unsigned long remainingTick = cron->tick - (te - ts);
|
||||
|
||||
/* Only sleep for microTick ms at a time because if the job
|
||||
* scheduler is supposed to stop before the tick is up, we
|
||||
* don't want to be stuck in a long sleep */
|
||||
while (remainingTick >= microTick && !cron->stop)
|
||||
{
|
||||
UtilSleepMillis(microTick);
|
||||
remainingTick -= microTick;
|
||||
}
|
||||
|
||||
if (remainingTick && !cron->stop)
|
||||
{
|
||||
UtilSleepMillis(remainingTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Cron *
|
||||
CronCreate(unsigned long tick)
|
||||
{
|
||||
Cron *cron = Malloc(sizeof(Cron));
|
||||
|
||||
if (!cron)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cron->jobs = ArrayCreate();
|
||||
if (!cron->jobs)
|
||||
{
|
||||
Free(cron);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cron->tick = tick;
|
||||
cron->stop = 1;
|
||||
|
||||
pthread_mutex_init(&cron->lock, NULL);
|
||||
|
||||
return cron;
|
||||
}
|
||||
|
||||
void
|
||||
CronOnce(Cron * cron, JobFunc * func, void *args)
|
||||
{
|
||||
Job *job;
|
||||
|
||||
if (!cron || !func)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
job = JobCreate(0, func, args);
|
||||
if (!job)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&cron->lock);
|
||||
ArrayAdd(cron->jobs, job);
|
||||
pthread_mutex_unlock(&cron->lock);
|
||||
}
|
||||
|
||||
void
|
||||
CronEvery(Cron * cron, unsigned long interval, JobFunc * func, void *args)
|
||||
{
|
||||
Job *job;
|
||||
|
||||
if (!cron || !func)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
job = JobCreate(interval, func, args);
|
||||
if (!job)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&cron->lock);
|
||||
ArrayAdd(cron->jobs, job);
|
||||
pthread_mutex_unlock(&cron->lock);
|
||||
}
|
||||
|
||||
void
|
||||
CronStart(Cron * cron)
|
||||
{
|
||||
if (!cron || !cron->stop)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cron->stop = 0;
|
||||
|
||||
pthread_create(&cron->thread, NULL, CronThread, cron);
|
||||
}
|
||||
|
||||
void
|
||||
CronStop(Cron * cron)
|
||||
{
|
||||
if (!cron || cron->stop)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cron->stop = 1;
|
||||
|
||||
pthread_join(cron->thread, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
CronFree(Cron * cron)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!cron)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CronStop(cron);
|
||||
|
||||
pthread_mutex_lock(&cron->lock);
|
||||
for (i = 0; i < ArraySize(cron->jobs); i++)
|
||||
{
|
||||
Free(ArrayGet(cron->jobs, i));
|
||||
}
|
||||
|
||||
ArrayFree(cron->jobs);
|
||||
pthread_mutex_unlock(&cron->lock);
|
||||
pthread_mutex_destroy(&cron->lock);
|
||||
|
||||
Free(cron);
|
||||
}
|
968
src/Db.c
Normal file
968
src/Db.c
Normal file
|
@ -0,0 +1,968 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Db.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Json.h>
|
||||
#include <Util.h>
|
||||
#include <Str.h>
|
||||
#include <Stream.h>
|
||||
#include <Log.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
struct Db
|
||||
{
|
||||
char *dir;
|
||||
pthread_mutex_t lock;
|
||||
|
||||
size_t cacheSize;
|
||||
size_t maxCache;
|
||||
HashMap *cache;
|
||||
|
||||
/*
|
||||
* The cache uses a double linked list (see DbRef
|
||||
* below) to know which objects are most and least
|
||||
* recently used. The following diagram helps me
|
||||
* know what way all the pointers go, because it
|
||||
* can get very confusing sometimes. For example,
|
||||
* there's nothing stopping "next" from pointing to
|
||||
* least recent, and "prev" from pointing to most
|
||||
* recent, so hopefully this clarifies the pointer
|
||||
* terminology used when dealing with the linked
|
||||
* list:
|
||||
*
|
||||
* mostRecent leastRecent
|
||||
* | prev prev | prev
|
||||
* +---+ ---> +---+ ---> +---+ ---> NULL
|
||||
* |ref| |ref| |ref|
|
||||
* NULL <--- +---+ <--- +---+ <--- +---+
|
||||
* next next next
|
||||
*/
|
||||
DbRef *mostRecent;
|
||||
DbRef *leastRecent;
|
||||
};
|
||||
|
||||
struct DbRef
|
||||
{
|
||||
HashMap *json;
|
||||
|
||||
unsigned long ts;
|
||||
size_t size;
|
||||
|
||||
Array *name;
|
||||
|
||||
DbRef *prev;
|
||||
DbRef *next;
|
||||
|
||||
int fd;
|
||||
Stream *stream;
|
||||
};
|
||||
|
||||
static void
|
||||
StringArrayFree(Array * arr)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ArraySize(arr); i++)
|
||||
{
|
||||
Free(ArrayGet(arr, i));
|
||||
}
|
||||
|
||||
ArrayFree(arr);
|
||||
}
|
||||
|
||||
static ssize_t DbComputeSize(HashMap *);
|
||||
|
||||
static ssize_t
|
||||
DbComputeSizeOfValue(JsonValue * val)
|
||||
{
|
||||
MemoryInfo *a;
|
||||
ssize_t total = 0;
|
||||
|
||||
size_t i;
|
||||
|
||||
union
|
||||
{
|
||||
char *str;
|
||||
Array *arr;
|
||||
} u;
|
||||
|
||||
if (!val)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
a = MemoryInfoGet(val);
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
|
||||
switch (JsonValueType(val))
|
||||
{
|
||||
case JSON_OBJECT:
|
||||
total += DbComputeSize(JsonValueAsObject(val));
|
||||
break;
|
||||
case JSON_ARRAY:
|
||||
u.arr = JsonValueAsArray(val);
|
||||
a = MemoryInfoGet(u.arr);
|
||||
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
|
||||
for (i = 0; i < ArraySize(u.arr); i++)
|
||||
{
|
||||
total += DbComputeSizeOfValue(ArrayGet(u.arr, i));
|
||||
}
|
||||
break;
|
||||
case JSON_STRING:
|
||||
u.str = JsonValueAsString(val);
|
||||
a = MemoryInfoGet(u.str);
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
break;
|
||||
case JSON_NULL:
|
||||
case JSON_INTEGER:
|
||||
case JSON_FLOAT:
|
||||
case JSON_BOOLEAN:
|
||||
default:
|
||||
/* These don't use any extra heap space */
|
||||
break;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
DbComputeSize(HashMap * json)
|
||||
{
|
||||
char *key;
|
||||
JsonValue *val;
|
||||
MemoryInfo *a;
|
||||
size_t total;
|
||||
|
||||
if (!json)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
total = 0;
|
||||
|
||||
a = MemoryInfoGet(json);
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
|
||||
while (HashMapIterate(json, &key, (void **) &val))
|
||||
{
|
||||
a = MemoryInfoGet(key);
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
|
||||
total += DbComputeSizeOfValue(val);
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static char *
|
||||
DbHashKey(Array * args)
|
||||
{
|
||||
size_t i;
|
||||
char *str = NULL;
|
||||
|
||||
for (i = 0; i < ArraySize(args); i++)
|
||||
{
|
||||
char *tmp = StrConcat(2, str, ArrayGet(args, i));
|
||||
|
||||
Free(str);
|
||||
str = tmp;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *
|
||||
DbDirName(Db * db, Array * args, size_t strip)
|
||||
{
|
||||
size_t i;
|
||||
char *str = StrConcat(2, db->dir, "/");
|
||||
|
||||
for (i = 0; i < ArraySize(args) - strip; i++)
|
||||
{
|
||||
char *tmp;
|
||||
|
||||
tmp = StrConcat(3, str, ArrayGet(args, i), "/");
|
||||
|
||||
Free(str);
|
||||
|
||||
str = tmp;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *
|
||||
DbFileName(Db * db, Array * args)
|
||||
{
|
||||
size_t i;
|
||||
char *str = StrConcat(2, db->dir, "/");
|
||||
|
||||
for (i = 0; i < ArraySize(args); i++)
|
||||
{
|
||||
char *tmp;
|
||||
char *arg = StrDuplicate(ArrayGet(args, i));
|
||||
size_t j = 0;
|
||||
|
||||
/* Sanitize name to prevent directory traversal attacks */
|
||||
while (arg[j])
|
||||
{
|
||||
switch (arg[j])
|
||||
{
|
||||
case '/':
|
||||
arg[j] = '_';
|
||||
break;
|
||||
case '.':
|
||||
arg[j] = '-';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
tmp = StrConcat(3, str, arg,
|
||||
(i < ArraySize(args) - 1) ? "/" : ".json");
|
||||
|
||||
Free(arg);
|
||||
Free(str);
|
||||
|
||||
str = tmp;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static void
|
||||
DbCacheEvict(Db * db)
|
||||
{
|
||||
DbRef *ref = db->leastRecent;
|
||||
DbRef *tmp;
|
||||
|
||||
while (ref && db->cacheSize > db->maxCache)
|
||||
{
|
||||
char *hash;
|
||||
|
||||
JsonFree(ref->json);
|
||||
|
||||
hash = DbHashKey(ref->name);
|
||||
HashMapDelete(db->cache, hash);
|
||||
Free(hash);
|
||||
|
||||
StringArrayFree(ref->name);
|
||||
|
||||
db->cacheSize -= ref->size;
|
||||
|
||||
if (ref->next)
|
||||
{
|
||||
ref->next->prev = ref->prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
db->mostRecent = ref->prev;
|
||||
}
|
||||
|
||||
if (ref->prev)
|
||||
{
|
||||
ref->prev->next = ref->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
db->leastRecent = ref->next;
|
||||
}
|
||||
|
||||
tmp = ref->next;
|
||||
Free(ref);
|
||||
|
||||
ref = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
Db *
|
||||
DbOpen(char *dir, size_t cache)
|
||||
{
|
||||
Db *db;
|
||||
pthread_mutexattr_t attr;
|
||||
|
||||
if (!dir)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
db = Malloc(sizeof(Db));
|
||||
if (!db)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
db->dir = dir;
|
||||
db->maxCache = cache;
|
||||
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
pthread_mutex_init(&db->lock, &attr);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
|
||||
db->mostRecent = NULL;
|
||||
db->leastRecent = NULL;
|
||||
db->cacheSize = 0;
|
||||
|
||||
if (db->maxCache)
|
||||
{
|
||||
db->cache = HashMapCreate();
|
||||
if (!db->cache)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
db->cache = NULL;
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
void
|
||||
DbMaxCacheSet(Db * db, size_t cache)
|
||||
{
|
||||
if (!db)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
db->maxCache = cache;
|
||||
if (db->maxCache && !db->cache)
|
||||
{
|
||||
db->cache = HashMapCreate();
|
||||
db->cacheSize = 0;
|
||||
}
|
||||
|
||||
DbCacheEvict(db);
|
||||
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
}
|
||||
|
||||
void
|
||||
DbClose(Db * db)
|
||||
{
|
||||
if (!db)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
DbMaxCacheSet(db, 0);
|
||||
DbCacheEvict(db);
|
||||
HashMapFree(db->cache);
|
||||
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
pthread_mutex_destroy(&db->lock);
|
||||
|
||||
Free(db);
|
||||
}
|
||||
|
||||
static DbRef *
|
||||
DbLockFromArr(Db * db, Array * args)
|
||||
{
|
||||
char *file;
|
||||
char *hash;
|
||||
DbRef *ref;
|
||||
struct flock lock;
|
||||
|
||||
int fd;
|
||||
Stream *stream;
|
||||
|
||||
if (!db || !args)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ref = NULL;
|
||||
hash = NULL;
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
/* Check if the item is in the cache */
|
||||
hash = DbHashKey(args);
|
||||
ref = HashMapGet(db->cache, hash);
|
||||
file = DbFileName(db, args);
|
||||
|
||||
fd = open(file, O_RDWR);
|
||||
if (fd == -1)
|
||||
{
|
||||
if (ref)
|
||||
{
|
||||
HashMapDelete(db->cache, hash);
|
||||
JsonFree(ref->json);
|
||||
StringArrayFree(ref->name);
|
||||
|
||||
db->cacheSize -= ref->size;
|
||||
|
||||
if (ref->next)
|
||||
{
|
||||
ref->next->prev = ref->prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
db->mostRecent = ref->prev;
|
||||
}
|
||||
|
||||
if (ref->prev)
|
||||
{
|
||||
ref->prev->next = ref->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
db->leastRecent = ref->next;
|
||||
}
|
||||
|
||||
if (!db->leastRecent)
|
||||
{
|
||||
db->leastRecent = db->mostRecent;
|
||||
}
|
||||
|
||||
Free(ref);
|
||||
}
|
||||
ref = NULL;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
stream = StreamFd(fd);
|
||||
|
||||
lock.l_start = 0;
|
||||
lock.l_len = 0;
|
||||
lock.l_type = F_WRLCK;
|
||||
lock.l_whence = SEEK_SET;
|
||||
|
||||
/* Lock the file on the disk */
|
||||
if (fcntl(fd, F_SETLK, &lock) < 0)
|
||||
{
|
||||
StreamClose(stream);
|
||||
ref = NULL;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (ref) /* In cache */
|
||||
{
|
||||
unsigned long diskTs = UtilLastModified(file);
|
||||
|
||||
ref->fd = fd;
|
||||
ref->stream = stream;
|
||||
|
||||
if (diskTs > ref->ts)
|
||||
{
|
||||
/* File was modified on disk since it was cached */
|
||||
HashMap *json = JsonDecode(ref->stream);
|
||||
|
||||
if (!json)
|
||||
{
|
||||
StreamClose(ref->stream);
|
||||
ref = NULL;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
JsonFree(ref->json);
|
||||
ref->json = json;
|
||||
ref->ts = diskTs;
|
||||
ref->size = DbComputeSize(ref->json);
|
||||
}
|
||||
|
||||
/* Float this ref to mostRecent */
|
||||
if (ref->next)
|
||||
{
|
||||
ref->next->prev = ref->prev;
|
||||
|
||||
if (!ref->prev)
|
||||
{
|
||||
db->leastRecent = ref->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
ref->prev->next = ref->next;
|
||||
}
|
||||
|
||||
ref->prev = db->mostRecent;
|
||||
ref->next = NULL;
|
||||
if (db->mostRecent)
|
||||
{
|
||||
db->mostRecent->next = ref;
|
||||
}
|
||||
db->mostRecent = ref;
|
||||
}
|
||||
|
||||
/* If there is no least recent, this is the only thing in the
|
||||
* cache, so it is also least recent. */
|
||||
if (!db->leastRecent)
|
||||
{
|
||||
db->leastRecent = ref;
|
||||
}
|
||||
|
||||
/* The file on disk may be larger than what we have in memory,
|
||||
* which may require items in cache to be evicted. */
|
||||
DbCacheEvict(db);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array *name;
|
||||
size_t i;
|
||||
|
||||
/* Not in cache; load from disk */
|
||||
|
||||
ref = Malloc(sizeof(DbRef));
|
||||
if (!ref)
|
||||
{
|
||||
StreamClose(stream);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
ref->json = JsonDecode(stream);
|
||||
|
||||
if (!ref->json)
|
||||
{
|
||||
Free(ref);
|
||||
StreamClose(stream);
|
||||
ref = NULL;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
ref->fd = fd;
|
||||
ref->stream = stream;
|
||||
|
||||
name = ArrayCreate();
|
||||
for (i = 0; i < ArraySize(args); i++)
|
||||
{
|
||||
ArrayAdd(name, StrDuplicate(ArrayGet(args, i)));
|
||||
}
|
||||
ref->name = name;
|
||||
|
||||
if (db->cache)
|
||||
{
|
||||
ref->ts = UtilServerTs();
|
||||
ref->size = DbComputeSize(ref->json);
|
||||
HashMapSet(db->cache, hash, ref);
|
||||
db->cacheSize += ref->size;
|
||||
|
||||
ref->next = NULL;
|
||||
ref->prev = db->mostRecent;
|
||||
if (db->mostRecent)
|
||||
{
|
||||
db->mostRecent->next = ref;
|
||||
}
|
||||
db->mostRecent = ref;
|
||||
|
||||
if (!db->leastRecent)
|
||||
{
|
||||
db->leastRecent = ref;
|
||||
}
|
||||
|
||||
/* Adding this item to the cache may case it to grow too
|
||||
* large, requiring some items to be evicted */
|
||||
DbCacheEvict(db);
|
||||
}
|
||||
}
|
||||
|
||||
finish:
|
||||
if (!ref)
|
||||
{
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
}
|
||||
|
||||
Free(file);
|
||||
Free(hash);
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
DbRef *
|
||||
DbCreate(Db * db, size_t nArgs,...)
|
||||
{
|
||||
Stream *fp;
|
||||
char *file;
|
||||
char *dir;
|
||||
va_list ap;
|
||||
Array *args;
|
||||
DbRef *ret;
|
||||
|
||||
if (!db)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
va_start(ap, nArgs);
|
||||
args = ArrayFromVarArgs(nArgs, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (!args)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
file = DbFileName(db, args);
|
||||
|
||||
if (UtilLastModified(file))
|
||||
{
|
||||
Free(file);
|
||||
ArrayFree(args);
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dir = DbDirName(db, args, 1);
|
||||
if (UtilMkdir(dir, 0750) < 0)
|
||||
{
|
||||
Free(file);
|
||||
ArrayFree(args);
|
||||
Free(dir);
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Free(dir);
|
||||
|
||||
fp = StreamOpen(file, "w");
|
||||
Free(file);
|
||||
if (!fp)
|
||||
{
|
||||
ArrayFree(args);
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
StreamPuts(fp, "{}");
|
||||
StreamClose(fp);
|
||||
|
||||
/* DbLockFromArr() will lock again for us */
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
|
||||
ret = DbLockFromArr(db, args);
|
||||
|
||||
ArrayFree(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
DbDelete(Db * db, size_t nArgs,...)
|
||||
{
|
||||
va_list ap;
|
||||
Array *args;
|
||||
char *file;
|
||||
char *hash;
|
||||
int ret = 1;
|
||||
DbRef *ref;
|
||||
|
||||
if (!db)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
va_start(ap, nArgs);
|
||||
args = ArrayFromVarArgs(nArgs, ap);
|
||||
va_end(ap);
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
hash = DbHashKey(args);
|
||||
file = DbFileName(db, args);
|
||||
|
||||
ref = HashMapGet(db->cache, hash);
|
||||
if (ref)
|
||||
{
|
||||
HashMapDelete(db->cache, hash);
|
||||
JsonFree(ref->json);
|
||||
StringArrayFree(ref->name);
|
||||
|
||||
db->cacheSize -= ref->size;
|
||||
|
||||
if (ref->next)
|
||||
{
|
||||
ref->next->prev = ref->prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
db->mostRecent = ref->prev;
|
||||
}
|
||||
|
||||
if (ref->prev)
|
||||
{
|
||||
ref->prev->next = ref->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
db->leastRecent = ref->next;
|
||||
}
|
||||
|
||||
if (!db->leastRecent)
|
||||
{
|
||||
db->leastRecent = db->mostRecent;
|
||||
}
|
||||
|
||||
Free(ref);
|
||||
}
|
||||
|
||||
Free(hash);
|
||||
|
||||
if (UtilLastModified(file))
|
||||
{
|
||||
ret = remove(file) == 0;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
|
||||
ArrayFree(args);
|
||||
Free(file);
|
||||
return ret;
|
||||
}
|
||||
|
||||
DbRef *
|
||||
DbLock(Db * db, size_t nArgs,...)
|
||||
{
|
||||
va_list ap;
|
||||
Array *args;
|
||||
DbRef *ret;
|
||||
|
||||
va_start(ap, nArgs);
|
||||
args = ArrayFromVarArgs(nArgs, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (!args)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = DbLockFromArr(db, args);
|
||||
|
||||
ArrayFree(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
DbUnlock(Db * db, DbRef * ref)
|
||||
{
|
||||
int destroy;
|
||||
|
||||
if (!db || !ref)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
lseek(ref->fd, 0L, SEEK_SET);
|
||||
if (ftruncate(ref->fd, 0) < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
Log(LOG_ERR, "Failed to truncate file on disk.");
|
||||
Log(LOG_ERR, "Error on fd %d: %s", ref->fd, strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
JsonEncode(ref->json, ref->stream, JSON_DEFAULT);
|
||||
StreamClose(ref->stream);
|
||||
|
||||
if (db->cache)
|
||||
{
|
||||
char *key = DbHashKey(ref->name);
|
||||
|
||||
if (HashMapGet(db->cache, key))
|
||||
{
|
||||
db->cacheSize -= ref->size;
|
||||
ref->size = DbComputeSize(ref->json);
|
||||
db->cacheSize += ref->size;
|
||||
|
||||
/* If this ref has grown significantly since we last
|
||||
* computed its size, it may have filled the cache and
|
||||
* require some items to be evicted. */
|
||||
DbCacheEvict(db);
|
||||
|
||||
destroy = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
destroy = 1;
|
||||
}
|
||||
|
||||
Free(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
destroy = 1;
|
||||
}
|
||||
|
||||
if (destroy)
|
||||
{
|
||||
JsonFree(ref->json);
|
||||
StringArrayFree(ref->name);
|
||||
|
||||
Free(ref);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
DbExists(Db * db, size_t nArgs,...)
|
||||
{
|
||||
va_list ap;
|
||||
Array *args;
|
||||
char *file;
|
||||
int ret;
|
||||
|
||||
va_start(ap, nArgs);
|
||||
args = ArrayFromVarArgs(nArgs, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (!args)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
file = DbFileName(db, args);
|
||||
ret = UtilLastModified(file);
|
||||
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
|
||||
Free(file);
|
||||
ArrayFree(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Array *
|
||||
DbList(Db * db, size_t nArgs,...)
|
||||
{
|
||||
Array *result;
|
||||
Array *path;
|
||||
DIR *files;
|
||||
struct dirent *file;
|
||||
char *dir;
|
||||
va_list ap;
|
||||
|
||||
if (!db || !nArgs)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
result = ArrayCreate();
|
||||
if (!result)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
va_start(ap, nArgs);
|
||||
path = ArrayFromVarArgs(nArgs, ap);
|
||||
dir = DbDirName(db, path, 0);
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
files = opendir(dir);
|
||||
if (!files)
|
||||
{
|
||||
ArrayFree(path);
|
||||
ArrayFree(result);
|
||||
Free(dir);
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
return NULL;
|
||||
}
|
||||
while ((file = readdir(files)))
|
||||
{
|
||||
size_t namlen = strlen(file->d_name);
|
||||
|
||||
if (namlen > 5)
|
||||
{
|
||||
int nameOffset = namlen - 5;
|
||||
|
||||
if (StrEquals(file->d_name + nameOffset, ".json"))
|
||||
{
|
||||
file->d_name[nameOffset] = '\0';
|
||||
ArrayAdd(result, StrDuplicate(file->d_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(files);
|
||||
|
||||
ArrayFree(path);
|
||||
Free(dir);
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
DbListFree(Array * arr)
|
||||
{
|
||||
StringArrayFree(arr);
|
||||
}
|
||||
|
||||
HashMap *
|
||||
DbJson(DbRef * ref)
|
||||
{
|
||||
return ref ? ref->json : NULL;
|
||||
}
|
||||
|
||||
int
|
||||
DbJsonSet(DbRef * ref, HashMap * json)
|
||||
{
|
||||
if (!ref || !json)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
JsonFree(ref->json);
|
||||
ref->json = JsonDuplicate(json);
|
||||
return 1;
|
||||
}
|
401
src/HashMap.c
Normal file
401
src/HashMap.c
Normal file
|
@ -0,0 +1,401 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <HashMap.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Str.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct HashMapBucket
|
||||
{
|
||||
unsigned long hash;
|
||||
char *key;
|
||||
void *value;
|
||||
} HashMapBucket;
|
||||
|
||||
struct HashMap
|
||||
{
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
HashMapBucket **entries;
|
||||
|
||||
unsigned long (*hashFunc) (const char *);
|
||||
|
||||
float maxLoad;
|
||||
size_t iterator;
|
||||
};
|
||||
|
||||
static unsigned long
|
||||
HashMapHashKey(const char *key)
|
||||
{
|
||||
unsigned long hash = 2166136261u;
|
||||
size_t i = 0;
|
||||
|
||||
while (key[i])
|
||||
{
|
||||
hash ^= (unsigned char) key[i];
|
||||
hash *= 16777619;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static int
|
||||
HashMapGrow(HashMap * map)
|
||||
{
|
||||
size_t oldCapacity;
|
||||
size_t i;
|
||||
HashMapBucket **newEntries;
|
||||
|
||||
if (!map)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
oldCapacity = map->capacity;
|
||||
map->capacity *= 2;
|
||||
|
||||
newEntries = Malloc(map->capacity * sizeof(HashMapBucket *));
|
||||
if (!newEntries)
|
||||
{
|
||||
map->capacity /= 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(newEntries, 0, map->capacity * sizeof(HashMapBucket *));
|
||||
|
||||
for (i = 0; i < oldCapacity; i++)
|
||||
{
|
||||
/* If there is a value here, and it isn't a tombstone */
|
||||
if (map->entries[i] && map->entries[i]->hash)
|
||||
{
|
||||
/* Copy it to the new entries array */
|
||||
size_t index = map->entries[i]->hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (newEntries[index])
|
||||
{
|
||||
if (!newEntries[index]->hash)
|
||||
{
|
||||
Free(newEntries[index]);
|
||||
newEntries[index] = map->entries[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newEntries[index] = map->entries[i];
|
||||
break;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Either NULL or a tombstone */
|
||||
Free(map->entries[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Free(map->entries);
|
||||
map->entries = newEntries;
|
||||
return 1;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HashMapCreate(void)
|
||||
{
|
||||
HashMap *map = Malloc(sizeof(HashMap));
|
||||
|
||||
if (!map)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
map->maxLoad = 0.75;
|
||||
map->count = 0;
|
||||
map->capacity = 16;
|
||||
map->iterator = 0;
|
||||
map->hashFunc = HashMapHashKey;
|
||||
|
||||
map->entries = Malloc(map->capacity * sizeof(HashMapBucket *));
|
||||
if (!map->entries)
|
||||
{
|
||||
Free(map);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(map->entries, 0, map->capacity * sizeof(HashMapBucket *));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
void *
|
||||
HashMapDelete(HashMap * map, const char *key)
|
||||
{
|
||||
unsigned long hash;
|
||||
size_t index;
|
||||
|
||||
if (!map || !key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hash = map->hashFunc(key);
|
||||
index = hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[index];
|
||||
|
||||
if (!bucket)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (bucket->hash == hash)
|
||||
{
|
||||
bucket->hash = 0;
|
||||
return bucket->value;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
HashMapFree(HashMap * map)
|
||||
{
|
||||
if (map)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < map->capacity; i++)
|
||||
{
|
||||
if (map->entries[i])
|
||||
{
|
||||
Free(map->entries[i]->key);
|
||||
Free(map->entries[i]);
|
||||
}
|
||||
}
|
||||
Free(map->entries);
|
||||
Free(map);
|
||||
}
|
||||
}
|
||||
|
||||
void *
|
||||
HashMapGet(HashMap * map, const char *key)
|
||||
{
|
||||
unsigned long hash;
|
||||
size_t index;
|
||||
|
||||
if (!map || !key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hash = map->hashFunc(key);
|
||||
index = hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[index];
|
||||
|
||||
if (!bucket)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (bucket->hash == hash)
|
||||
{
|
||||
return bucket->value;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
HashMapIterateReentrant(HashMap * map, char **key, void **value, size_t * i)
|
||||
{
|
||||
if (!map)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (*i >= map->capacity)
|
||||
{
|
||||
*i = 0;
|
||||
*key = NULL;
|
||||
*value = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (*i < map->capacity)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[*i];
|
||||
|
||||
*i = *i + 1;
|
||||
|
||||
if (bucket && bucket->hash)
|
||||
{
|
||||
*key = bucket->key;
|
||||
*value = bucket->value;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
*i = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
HashMapIterate(HashMap * map, char **key, void **value)
|
||||
{
|
||||
if (!map)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return HashMapIterateReentrant(map, key, value, &map->iterator);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HashMapMaxLoadSet(HashMap * map, float load)
|
||||
{
|
||||
if (!map || (load > 1.0 || load <= 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
map->maxLoad = load;
|
||||
}
|
||||
|
||||
void
|
||||
HashMapFunctionSet(HashMap * map, unsigned long (*hashFunc) (const char *))
|
||||
{
|
||||
if (!map || !hashFunc)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
map->hashFunc = hashFunc;
|
||||
}
|
||||
|
||||
void *
|
||||
HashMapSet(HashMap * map, char *key, void *value)
|
||||
{
|
||||
unsigned long hash;
|
||||
size_t index;
|
||||
|
||||
if (!map || !key || !value)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
key = StrDuplicate(key);
|
||||
if (!key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (map->count + 1 > map->capacity * map->maxLoad)
|
||||
{
|
||||
HashMapGrow(map);
|
||||
}
|
||||
|
||||
hash = map->hashFunc(key);
|
||||
index = hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[index];
|
||||
|
||||
if (!bucket)
|
||||
{
|
||||
bucket = Malloc(sizeof(HashMapBucket));
|
||||
if (!bucket)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bucket->hash = hash;
|
||||
bucket->key = key;
|
||||
bucket->value = value;
|
||||
map->entries[index] = bucket;
|
||||
map->count++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!bucket->hash)
|
||||
{
|
||||
bucket->hash = hash;
|
||||
Free(bucket->key);
|
||||
bucket->key = key;
|
||||
bucket->value = value;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bucket->hash == hash)
|
||||
{
|
||||
void *oldValue = bucket->value;
|
||||
|
||||
Free(bucket->key);
|
||||
bucket->key = key;
|
||||
|
||||
bucket->value = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
HashMapIterateFree(char *key, void *value)
|
||||
{
|
||||
if (key)
|
||||
{
|
||||
Free(key);
|
||||
}
|
||||
|
||||
if (value)
|
||||
{
|
||||
Free(value);
|
||||
}
|
||||
}
|
664
src/HeaderParser.c
Normal file
664
src/HeaderParser.c
Normal file
|
@ -0,0 +1,664 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <HeaderParser.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Str.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
static int
|
||||
HeaderConsumeWhitespace(HeaderExpr * expr)
|
||||
{
|
||||
int c;
|
||||
|
||||
while (1)
|
||||
{
|
||||
c = StreamGetc(expr->state.stream);
|
||||
|
||||
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
|
||||
{
|
||||
expr->type = HP_EOF;
|
||||
expr->data.error.msg = "End of stream reached.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isspace(c))
|
||||
{
|
||||
if (c == '\n')
|
||||
{
|
||||
expr->state.lineNo++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static char *
|
||||
HeaderConsumeWord(HeaderExpr * expr)
|
||||
{
|
||||
char *str = Malloc(16 * sizeof(char));
|
||||
int len = 16;
|
||||
int i;
|
||||
int c;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c = HeaderConsumeWhitespace(expr);
|
||||
|
||||
i = 0;
|
||||
str[i] = c;
|
||||
i++;
|
||||
|
||||
while (!isspace(c = StreamGetc(expr->state.stream)))
|
||||
{
|
||||
if (i >= len)
|
||||
{
|
||||
len *= 2;
|
||||
str = Realloc(str, len * sizeof(char));
|
||||
}
|
||||
|
||||
str[i] = c;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i >= len)
|
||||
{
|
||||
len++;
|
||||
str = Realloc(str, len * sizeof(char));
|
||||
}
|
||||
|
||||
str[i] = '\0';
|
||||
|
||||
if (c != EOF)
|
||||
{
|
||||
StreamUngetc(expr->state.stream, c);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *
|
||||
HeaderConsumeAlnum(HeaderExpr * expr)
|
||||
{
|
||||
char *str = Malloc(16 * sizeof(char));
|
||||
int len = 16;
|
||||
int i;
|
||||
int c;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c = HeaderConsumeWhitespace(expr);
|
||||
|
||||
i = 0;
|
||||
str[i] = c;
|
||||
i++;
|
||||
|
||||
while (isalnum(c = StreamGetc(expr->state.stream)))
|
||||
{
|
||||
if (i >= len)
|
||||
{
|
||||
len *= 2;
|
||||
str = Realloc(str, len * sizeof(char));
|
||||
}
|
||||
|
||||
str[i] = c;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i >= len)
|
||||
{
|
||||
len++;
|
||||
str = Realloc(str, len * sizeof(char));
|
||||
}
|
||||
|
||||
str[i] = '\0';
|
||||
|
||||
if (c != EOF)
|
||||
{
|
||||
StreamUngetc(expr->state.stream, c);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *
|
||||
HeaderConsumeArg(HeaderExpr * expr)
|
||||
{
|
||||
char *str = Malloc(16 * sizeof(char));
|
||||
int len = 16;
|
||||
int i;
|
||||
int c;
|
||||
int block = 0;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c = HeaderConsumeWhitespace(expr);
|
||||
|
||||
i = 0;
|
||||
str[i] = c;
|
||||
i++;
|
||||
|
||||
while (((c = StreamGetc(expr->state.stream)) != ',' && c != ')') || block > 0)
|
||||
{
|
||||
if (i >= len)
|
||||
{
|
||||
len *= 2;
|
||||
str = Realloc(str, len * sizeof(char));
|
||||
}
|
||||
|
||||
str[i] = c;
|
||||
i++;
|
||||
|
||||
if (c == '(')
|
||||
{
|
||||
block++;
|
||||
}
|
||||
else if (c == ')')
|
||||
{
|
||||
block--;
|
||||
}
|
||||
else if (c == '\n')
|
||||
{
|
||||
expr->state.lineNo++;
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= len)
|
||||
{
|
||||
len++;
|
||||
str = Realloc(str, len * sizeof(char));
|
||||
}
|
||||
|
||||
str[i] = '\0';
|
||||
|
||||
if (c != EOF)
|
||||
{
|
||||
StreamUngetc(expr->state.stream, c);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void
|
||||
HeaderParse(Stream * stream, HeaderExpr * expr)
|
||||
{
|
||||
int c;
|
||||
|
||||
if (!expr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stream)
|
||||
{
|
||||
expr->type = HP_PARSE_ERROR;
|
||||
expr->data.error.msg = "NULL pointer to stream.";
|
||||
expr->data.error.lineNo = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (expr->type == HP_DECLARATION && expr->data.declaration.args)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ArraySize(expr->data.declaration.args); i++)
|
||||
{
|
||||
Free(ArrayGet(expr->data.declaration.args, i));
|
||||
}
|
||||
|
||||
ArrayFree(expr->data.declaration.args);
|
||||
}
|
||||
|
||||
expr->state.stream = stream;
|
||||
if (!expr->state.lineNo)
|
||||
{
|
||||
expr->state.lineNo = 1;
|
||||
}
|
||||
|
||||
c = HeaderConsumeWhitespace(expr);
|
||||
|
||||
if (StreamEof(stream) || StreamError(stream))
|
||||
{
|
||||
expr->type = HP_EOF;
|
||||
expr->data.error.msg = "End of stream reached.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '/')
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
c = StreamGetc(expr->state.stream);
|
||||
if (c != '*')
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Expected comment opening.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
expr->type = HP_COMMENT;
|
||||
while (1)
|
||||
{
|
||||
if (i >= HEADER_EXPR_MAX - 1)
|
||||
{
|
||||
expr->type = HP_PARSE_ERROR;
|
||||
expr->data.error.msg = "Memory limit exceeded while parsing comment.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
c = StreamGetc(expr->state.stream);
|
||||
|
||||
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Unterminated comment.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '*')
|
||||
{
|
||||
c = StreamGetc(expr->state.stream);
|
||||
if (c == '/')
|
||||
{
|
||||
expr->data.text[i] = '\0';
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
expr->data.text[i] = '*';
|
||||
i++;
|
||||
expr->data.text[i] = c;
|
||||
i++;
|
||||
if (c == '\n')
|
||||
{
|
||||
expr->state.lineNo++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
expr->data.text[i] = c;
|
||||
i++;
|
||||
|
||||
if (c == '\n')
|
||||
{
|
||||
expr->state.lineNo++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (c == '#')
|
||||
{
|
||||
int i = 0;
|
||||
char *word;
|
||||
|
||||
expr->type = HP_PREPROCESSOR_DIRECTIVE;
|
||||
expr->data.text[i] = '#';
|
||||
i++;
|
||||
|
||||
word = HeaderConsumeWord(expr);
|
||||
|
||||
strncpy(expr->data.text + i, word, HEADER_EXPR_MAX - i - 1);
|
||||
i += strlen(word);
|
||||
|
||||
if (StrEquals(word, "include") ||
|
||||
StrEquals(word, "undef") ||
|
||||
StrEquals(word, "ifdef") ||
|
||||
StrEquals(word, "ifndef"))
|
||||
{
|
||||
/* Read one more word */
|
||||
Free(word);
|
||||
word = HeaderConsumeWord(expr);
|
||||
|
||||
if (i + strlen(word) + 1 >= HEADER_EXPR_MAX)
|
||||
{
|
||||
expr->type = HP_PARSE_ERROR;
|
||||
expr->data.error.msg = "Memory limit reached parsing preprocessor directive.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy(expr->data.text + i + 1, word, HEADER_EXPR_MAX - i - 1);
|
||||
expr->data.text[i] = ' ';
|
||||
}
|
||||
|
||||
Free(word);
|
||||
}
|
||||
else if (StrEquals(word, "define") ||
|
||||
StrEquals(word, "if") ||
|
||||
StrEquals(word, "elif") ||
|
||||
StrEquals(word, "error"))
|
||||
{
|
||||
int pC = 0;
|
||||
|
||||
Free(word);
|
||||
expr->data.text[i] = ' ';
|
||||
i++;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (i >= HEADER_EXPR_MAX - 1)
|
||||
{
|
||||
expr->type = HP_PARSE_ERROR;
|
||||
expr->data.error.msg = "Memory limit reached parsing preprocessor directive.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
c = StreamGetc(expr->state.stream);
|
||||
|
||||
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Unterminated preprocessor directive.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
{
|
||||
expr->state.lineNo++;
|
||||
if (pC != '\\')
|
||||
{
|
||||
expr->data.text[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
expr->data.text[i] = c;
|
||||
i++;
|
||||
|
||||
pC = c;
|
||||
}
|
||||
}
|
||||
else if (StrEquals(word, "else") ||
|
||||
StrEquals(word, "endif"))
|
||||
{
|
||||
/* Read no more words, that's the whole directive */
|
||||
}
|
||||
else
|
||||
{
|
||||
Free(word);
|
||||
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Unknown preprocessor directive.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char *word;
|
||||
|
||||
StreamUngetc(expr->state.stream, c);
|
||||
word = HeaderConsumeWord(expr);
|
||||
|
||||
if (StrEquals(word, "typedef"))
|
||||
{
|
||||
int block = 0;
|
||||
int i = 0;
|
||||
|
||||
expr->type = HP_TYPEDEF;
|
||||
strncpy(expr->data.text, word, HEADER_EXPR_MAX - 1);
|
||||
i += strlen(word);
|
||||
expr->data.text[i] = ' ';
|
||||
i++;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (i >= HEADER_EXPR_MAX - 1)
|
||||
{
|
||||
expr->type = HP_PARSE_ERROR;
|
||||
expr->data.error.msg = "Memory limit exceeded while parsing typedef.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
c = StreamGetc(expr->state.stream);
|
||||
|
||||
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Unterminated typedef.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
expr->data.text[i] = c;
|
||||
i++;
|
||||
|
||||
if (c == '{')
|
||||
{
|
||||
block++;
|
||||
}
|
||||
else if (c == '}')
|
||||
{
|
||||
block--;
|
||||
}
|
||||
else if (c == '\n')
|
||||
{
|
||||
expr->state.lineNo++;
|
||||
}
|
||||
|
||||
if (block <= 0 && c == ';')
|
||||
{
|
||||
expr->data.text[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (StrEquals(word, "extern"))
|
||||
{
|
||||
int wordLimit = sizeof(expr->data.declaration.returnType) - 8;
|
||||
int wordLen;
|
||||
|
||||
Free(word);
|
||||
|
||||
word = HeaderConsumeWord(expr);
|
||||
wordLen = strlen(word);
|
||||
if (wordLen > wordLimit)
|
||||
{
|
||||
expr->type = HP_PARSE_ERROR;
|
||||
expr->data.error.msg = "Return of declaration exceeds length limit.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = wordLen;
|
||||
|
||||
expr->type = HP_GLOBAL;
|
||||
strncpy(expr->data.global.type, word, wordLimit);
|
||||
|
||||
if (StrEquals(word, "struct") ||
|
||||
StrEquals(word, "enum") ||
|
||||
StrEquals(word, "const") ||
|
||||
StrEquals(word, "unsigned"))
|
||||
{
|
||||
Free(word);
|
||||
word = HeaderConsumeWord(expr);
|
||||
wordLen = strlen(word);
|
||||
expr->data.global.type[i] = ' ';
|
||||
|
||||
strncpy(expr->data.global.type + i + 1, word, wordLen + 1);
|
||||
i += wordLen + 1;
|
||||
}
|
||||
|
||||
Free(word);
|
||||
|
||||
c = HeaderConsumeWhitespace(expr);
|
||||
if (c == '*')
|
||||
{
|
||||
expr->data.global.type[i] = ' ';
|
||||
|
||||
i++;
|
||||
expr->data.global.type[i] = '*';
|
||||
|
||||
i++;
|
||||
while ((c = HeaderConsumeWhitespace(expr)) == '*')
|
||||
{
|
||||
expr->data.global.type[i] = c;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
StreamUngetc(expr->state.stream, c);
|
||||
word = HeaderConsumeAlnum(expr);
|
||||
|
||||
wordLen = strlen(word);
|
||||
wordLimit = sizeof(expr->data.declaration.name) - 1;
|
||||
|
||||
if (wordLen > wordLimit)
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Function name too long.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy(expr->data.global.name, word, wordLimit);
|
||||
Free(word);
|
||||
word = NULL;
|
||||
|
||||
c = HeaderConsumeWhitespace(expr);
|
||||
|
||||
if (c == ';')
|
||||
{
|
||||
/* That's the end of the global. */
|
||||
}
|
||||
else if (c == '[')
|
||||
{
|
||||
/* Looks like we have an array. Slurp all the
|
||||
* dimensions */
|
||||
int i = wordLen;
|
||||
|
||||
expr->data.global.name[i] = '[';
|
||||
|
||||
i++;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (i >= HEADER_EXPR_MAX - wordLen)
|
||||
{
|
||||
expr->type = HP_PARSE_ERROR;
|
||||
expr->data.error.msg = "Memory limit exceeded while parsing global array.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
c = StreamGetc(expr->state.stream);
|
||||
|
||||
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Unterminated global array.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == ';')
|
||||
{
|
||||
expr->data.global.name[i] = '\0';
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
expr->data.global.name[i] = c;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (c == '(')
|
||||
{
|
||||
expr->type = HP_DECLARATION;
|
||||
expr->data.declaration.args = ArrayCreate();
|
||||
do
|
||||
{
|
||||
word = HeaderConsumeArg(expr);
|
||||
ArrayAdd(expr->data.declaration.args, word);
|
||||
word = NULL;
|
||||
}
|
||||
while ((!StreamEof(expr->state.stream)) && ((c = HeaderConsumeWhitespace(expr)) != ')'));
|
||||
|
||||
if (StreamEof(expr->state.stream))
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "End of file reached before ')'.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
c = HeaderConsumeWhitespace(expr);
|
||||
if (c != ';')
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Expected ';'.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Expected ';', '[', or '('";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Cope with preprocessor macro expansions at the top
|
||||
* level. */
|
||||
expr->type = HP_UNKNOWN;
|
||||
strncpy(expr->data.text, word, HEADER_EXPR_MAX);
|
||||
}
|
||||
|
||||
Free(word);
|
||||
}
|
||||
}
|
642
src/Http.c
Normal file
642
src/Http.c
Normal file
|
@ -0,0 +1,642 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Http.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <HashMap.h>
|
||||
#include <Util.h>
|
||||
#include <Str.h>
|
||||
|
||||
#ifndef CYTOPLASM_STRING_CHUNK
|
||||
#define CYTOPLASM_STRING_CHUNK 64
|
||||
#endif
|
||||
|
||||
const char *
|
||||
HttpRequestMethodToString(const HttpRequestMethod method)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case HTTP_GET:
|
||||
return "GET";
|
||||
case HTTP_HEAD:
|
||||
return "HEAD";
|
||||
case HTTP_POST:
|
||||
return "POST";
|
||||
case HTTP_PUT:
|
||||
return "PUT";
|
||||
case HTTP_DELETE:
|
||||
return "DELETE";
|
||||
case HTTP_CONNECT:
|
||||
return "CONNECT";
|
||||
case HTTP_OPTIONS:
|
||||
return "OPTIONS";
|
||||
case HTTP_TRACE:
|
||||
return "TRACE";
|
||||
case HTTP_PATCH:
|
||||
return "PATCH";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
HttpRequestMethod
|
||||
HttpRequestMethodFromString(const char *str)
|
||||
{
|
||||
if (StrEquals(str, "GET"))
|
||||
{
|
||||
return HTTP_GET;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "HEAD"))
|
||||
{
|
||||
return HTTP_HEAD;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "POST"))
|
||||
{
|
||||
return HTTP_POST;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "PUT"))
|
||||
{
|
||||
return HTTP_PUT;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "DELETE"))
|
||||
{
|
||||
return HTTP_DELETE;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "CONNECT"))
|
||||
{
|
||||
return HTTP_CONNECT;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "OPTIONS"))
|
||||
{
|
||||
return HTTP_OPTIONS;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "TRACE"))
|
||||
{
|
||||
return HTTP_TRACE;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "PATCH"))
|
||||
{
|
||||
return HTTP_PATCH;
|
||||
}
|
||||
|
||||
return HTTP_METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
const char *
|
||||
HttpStatusToString(const HttpStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case HTTP_CONTINUE:
|
||||
return "Continue";
|
||||
case HTTP_SWITCHING_PROTOCOLS:
|
||||
return "Switching Protocols";
|
||||
case HTTP_EARLY_HINTS:
|
||||
return "Early Hints";
|
||||
case HTTP_OK:
|
||||
return "Ok";
|
||||
case HTTP_CREATED:
|
||||
return "Created";
|
||||
case HTTP_ACCEPTED:
|
||||
return "Accepted";
|
||||
case HTTP_NON_AUTHORITATIVE_INFORMATION:
|
||||
return "Non-Authoritative Information";
|
||||
case HTTP_NO_CONTENT:
|
||||
return "No Content";
|
||||
case HTTP_RESET_CONTENT:
|
||||
return "Reset Content";
|
||||
case HTTP_PARTIAL_CONTENT:
|
||||
return "Partial Content";
|
||||
case HTTP_MULTIPLE_CHOICES:
|
||||
return "Multiple Choices";
|
||||
case HTTP_MOVED_PERMANENTLY:
|
||||
return "Moved Permanently";
|
||||
case HTTP_FOUND:
|
||||
return "Found";
|
||||
case HTTP_SEE_OTHER:
|
||||
return "See Other";
|
||||
case HTTP_NOT_MODIFIED:
|
||||
return "Not Modified";
|
||||
case HTTP_TEMPORARY_REDIRECT:
|
||||
return "Temporary Redirect";
|
||||
case HTTP_PERMANENT_REDIRECT:
|
||||
return "Permanent Redirect";
|
||||
case HTTP_BAD_REQUEST:
|
||||
return "Bad Request";
|
||||
case HTTP_UNAUTHORIZED:
|
||||
return "Unauthorized";
|
||||
case HTTP_FORBIDDEN:
|
||||
return "Forbidden";
|
||||
case HTTP_NOT_FOUND:
|
||||
return "Not Found";
|
||||
case HTTP_METHOD_NOT_ALLOWED:
|
||||
return "Method Not Allowed";
|
||||
case HTTP_NOT_ACCEPTABLE:
|
||||
return "Not Acceptable";
|
||||
case HTTP_PROXY_AUTH_REQUIRED:
|
||||
return "Proxy Authentication Required";
|
||||
case HTTP_REQUEST_TIMEOUT:
|
||||
return "Request Timeout";
|
||||
case HTTP_CONFLICT:
|
||||
return "Conflict";
|
||||
case HTTP_GONE:
|
||||
return "Gone";
|
||||
case HTTP_LENGTH_REQUIRED:
|
||||
return "Length Required";
|
||||
case HTTP_PRECONDITION_FAILED:
|
||||
return "Precondition Failed";
|
||||
case HTTP_PAYLOAD_TOO_LARGE:
|
||||
return "Payload Too Large";
|
||||
case HTTP_URI_TOO_LONG:
|
||||
return "URI Too Long";
|
||||
case HTTP_UNSUPPORTED_MEDIA_TYPE:
|
||||
return "Unsupported Media Type";
|
||||
case HTTP_RANGE_NOT_SATISFIABLE:
|
||||
return "Range Not Satisfiable";
|
||||
case HTTP_EXPECTATION_FAILED:
|
||||
return "Expectation Failed";
|
||||
case HTTP_TEAPOT:
|
||||
return "I'm a Teapot";
|
||||
case HTTP_UPGRADE_REQUIRED:
|
||||
return "Upgrade Required";
|
||||
case HTTP_PRECONDITION_REQUIRED:
|
||||
return "Precondition Required";
|
||||
case HTTP_TOO_MANY_REQUESTS:
|
||||
return "Too Many Requests";
|
||||
case HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE:
|
||||
return "Request Header Fields Too Large";
|
||||
case HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
|
||||
return "Unavailable For Legal Reasons";
|
||||
case HTTP_INTERNAL_SERVER_ERROR:
|
||||
return "Internal Server Error";
|
||||
case HTTP_NOT_IMPLEMENTED:
|
||||
return "Not Implemented";
|
||||
case HTTP_BAD_GATEWAY:
|
||||
return "Bad Gateway";
|
||||
case HTTP_SERVICE_UNAVAILABLE:
|
||||
return "Service Unavailable";
|
||||
case HTTP_GATEWAY_TIMEOUT:
|
||||
return "Gateway Timeout";
|
||||
case HTTP_VERSION_NOT_SUPPORTED:
|
||||
return "Version Not Supported";
|
||||
case HTTP_VARIANT_ALSO_NEGOTIATES:
|
||||
return "Variant Also Negotiates";
|
||||
case HTTP_NOT_EXTENDED:
|
||||
return "Not Extended";
|
||||
case HTTP_NETWORK_AUTH_REQUIRED:
|
||||
return "Network Authentication Required";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
char *
|
||||
HttpUrlEncode(char *str)
|
||||
{
|
||||
size_t size;
|
||||
size_t len;
|
||||
char *encoded;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size = CYTOPLASM_STRING_CHUNK;
|
||||
len = 0;
|
||||
encoded = Malloc(size);
|
||||
if (!encoded)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (*str)
|
||||
{
|
||||
char c = *str;
|
||||
|
||||
if (len >= size - 4)
|
||||
{
|
||||
char *tmp;
|
||||
|
||||
size += CYTOPLASM_STRING_CHUNK;
|
||||
tmp = Realloc(encoded, size);
|
||||
if (!tmp)
|
||||
{
|
||||
Free(encoded);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
encoded = tmp;
|
||||
}
|
||||
|
||||
/* Control characters and extended characters */
|
||||
if (c <= 0x1F || c >= 0x7F)
|
||||
{
|
||||
goto percentEncode;
|
||||
}
|
||||
|
||||
/* Reserved and unsafe characters */
|
||||
switch (c)
|
||||
{
|
||||
case '$':
|
||||
case '&':
|
||||
case '+':
|
||||
case ',':
|
||||
case '/':
|
||||
case ':':
|
||||
case ';':
|
||||
case '=':
|
||||
case '?':
|
||||
case '@':
|
||||
case ' ':
|
||||
case '"':
|
||||
case '<':
|
||||
case '>':
|
||||
case '#':
|
||||
case '%':
|
||||
case '{':
|
||||
case '}':
|
||||
case '|':
|
||||
case '\\':
|
||||
case '^':
|
||||
case '~':
|
||||
case '[':
|
||||
case ']':
|
||||
case '`':
|
||||
goto percentEncode;
|
||||
break;
|
||||
default:
|
||||
encoded[len] = c;
|
||||
len++;
|
||||
str++;
|
||||
continue;
|
||||
}
|
||||
|
||||
percentEncode:
|
||||
encoded[len] = '%';
|
||||
len++;
|
||||
snprintf(encoded + len, 3, "%2X", c);
|
||||
len += 2;
|
||||
|
||||
str++;
|
||||
}
|
||||
|
||||
encoded[len] = '\0';
|
||||
return encoded;
|
||||
}
|
||||
|
||||
char *
|
||||
HttpUrlDecode(char *str)
|
||||
{
|
||||
size_t i;
|
||||
size_t inputLen;
|
||||
char *decoded;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
inputLen = strlen(str);
|
||||
decoded = Malloc(inputLen + 1);
|
||||
|
||||
if (!decoded)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (*str)
|
||||
{
|
||||
char c = *str;
|
||||
|
||||
if (c == '%')
|
||||
{
|
||||
unsigned int d;
|
||||
|
||||
str++;
|
||||
|
||||
if (sscanf(str, "%2X", &d) != 1)
|
||||
{
|
||||
/* Decoding error */
|
||||
Free(decoded);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!d)
|
||||
{
|
||||
/* Null character given, don't put that in the string. */
|
||||
continue;
|
||||
}
|
||||
|
||||
c = (char) d;
|
||||
|
||||
str++;
|
||||
}
|
||||
|
||||
decoded[i] = c;
|
||||
i++;
|
||||
|
||||
str++;
|
||||
}
|
||||
|
||||
decoded[i] = '\0';
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HttpParamDecode(char *in)
|
||||
{
|
||||
HashMap *params;
|
||||
|
||||
if (!in)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
params = HashMapCreate();
|
||||
if (!params)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (*in)
|
||||
{
|
||||
char *buf;
|
||||
size_t allocated;
|
||||
size_t len;
|
||||
|
||||
char *decKey;
|
||||
char *decVal;
|
||||
|
||||
/* Read in key */
|
||||
|
||||
allocated = CYTOPLASM_STRING_CHUNK;
|
||||
buf = Malloc(allocated);
|
||||
len = 0;
|
||||
|
||||
while (*in && *in != '=')
|
||||
{
|
||||
if (len >= allocated - 1)
|
||||
{
|
||||
allocated += CYTOPLASM_STRING_CHUNK;
|
||||
buf = Realloc(buf, allocated);
|
||||
}
|
||||
|
||||
buf[len] = *in;
|
||||
len++;
|
||||
in++;
|
||||
}
|
||||
|
||||
buf[len] = '\0';
|
||||
|
||||
/* Sanity check */
|
||||
if (*in != '=')
|
||||
{
|
||||
/* Malformed param */
|
||||
Free(buf);
|
||||
HashMapFree(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
in++;
|
||||
|
||||
/* Decode key */
|
||||
decKey = HttpUrlDecode(buf);
|
||||
Free(buf);
|
||||
|
||||
if (!decKey)
|
||||
{
|
||||
/* Decoding error */
|
||||
HashMapFree(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Read in value */
|
||||
allocated = CYTOPLASM_STRING_CHUNK;
|
||||
buf = Malloc(allocated);
|
||||
len = 0;
|
||||
|
||||
while (*in && *in != '&')
|
||||
{
|
||||
if (len >= allocated - 1)
|
||||
{
|
||||
allocated += CYTOPLASM_STRING_CHUNK;
|
||||
buf = Realloc(buf, allocated);
|
||||
}
|
||||
|
||||
buf[len] = *in;
|
||||
len++;
|
||||
in++;
|
||||
}
|
||||
|
||||
buf[len] = '\0';
|
||||
|
||||
/* Decode value */
|
||||
decVal = HttpUrlDecode(buf);
|
||||
Free(buf);
|
||||
|
||||
if (!decVal)
|
||||
{
|
||||
/* Decoding error */
|
||||
HashMapFree(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buf = HashMapSet(params, decKey, decVal);
|
||||
if (buf)
|
||||
{
|
||||
Free(buf);
|
||||
}
|
||||
Free(decKey);
|
||||
|
||||
if (*in == '&')
|
||||
{
|
||||
in++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
char *
|
||||
HttpParamEncode(HashMap * params)
|
||||
{
|
||||
char *key;
|
||||
char *val;
|
||||
char *out = NULL;
|
||||
|
||||
if (!params || !out)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (HashMapIterate(params, &key, (void *) &val))
|
||||
{
|
||||
char *encKey;
|
||||
char *encVal;
|
||||
|
||||
encKey = HttpUrlEncode(key);
|
||||
encVal = HttpUrlEncode(val);
|
||||
|
||||
if (!encKey || !encVal)
|
||||
{
|
||||
/* Memory error */
|
||||
Free(encKey);
|
||||
Free(encVal);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* TODO */
|
||||
|
||||
Free(encKey);
|
||||
Free(encVal);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HttpParseHeaders(Stream * fp)
|
||||
{
|
||||
HashMap *headers;
|
||||
|
||||
char *line;
|
||||
ssize_t lineLen;
|
||||
size_t lineSize;
|
||||
|
||||
char *headerKey;
|
||||
char *headerValue;
|
||||
|
||||
if (!fp)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
headers = HashMapCreate();
|
||||
if (!headers)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
line = NULL;
|
||||
lineLen = 0;
|
||||
|
||||
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) != -1)
|
||||
{
|
||||
char *headerPtr;
|
||||
|
||||
ssize_t i;
|
||||
size_t len;
|
||||
|
||||
if (StrEquals(line, "\r\n") || StrEquals(line, "\n"))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < lineLen; i++)
|
||||
{
|
||||
if (line[i] == ':')
|
||||
{
|
||||
line[i] = '\0';
|
||||
break;
|
||||
}
|
||||
|
||||
line[i] = tolower((unsigned char) line[i]);
|
||||
}
|
||||
|
||||
len = i + 1;
|
||||
headerKey = Malloc(len * sizeof(char));
|
||||
if (!headerKey)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
strncpy(headerKey, line, len);
|
||||
|
||||
headerPtr = line + i + 1;
|
||||
|
||||
while (isspace((unsigned char) *headerPtr))
|
||||
{
|
||||
headerPtr++;
|
||||
}
|
||||
|
||||
for (i = lineLen - 1; i > (line + lineLen) - headerPtr; i--)
|
||||
{
|
||||
if (!isspace((unsigned char) line[i]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
line[i] = '\0';
|
||||
}
|
||||
|
||||
len = strlen(headerPtr) + 1;
|
||||
headerValue = Malloc(len * sizeof(char));
|
||||
if (!headerValue)
|
||||
{
|
||||
Free(headerKey);
|
||||
goto error;
|
||||
}
|
||||
|
||||
strncpy(headerValue, headerPtr, len);
|
||||
|
||||
HashMapSet(headers, headerKey, headerValue);
|
||||
Free(headerKey);
|
||||
}
|
||||
|
||||
Free(line);
|
||||
return headers;
|
||||
|
||||
error:
|
||||
Free(line);
|
||||
|
||||
while (HashMapIterate(headers, &headerKey, (void **) &headerValue))
|
||||
{
|
||||
Free(headerValue);
|
||||
}
|
||||
|
||||
HashMapFree(headers);
|
||||
|
||||
return NULL;
|
||||
}
|
298
src/HttpClient.c
Normal file
298
src/HttpClient.c
Normal file
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <HttpClient.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#include <Http.h>
|
||||
#include <Memory.h>
|
||||
#include <Util.h>
|
||||
#include <Tls.h>
|
||||
|
||||
struct HttpClientContext
|
||||
{
|
||||
HashMap *responseHeaders;
|
||||
Stream *stream;
|
||||
};
|
||||
|
||||
HttpClientContext *
|
||||
HttpRequest(HttpRequestMethod method, int flags, unsigned short port, char *host, char *path)
|
||||
{
|
||||
HttpClientContext *context;
|
||||
|
||||
int sd = -1;
|
||||
struct addrinfo hints, *res, *res0;
|
||||
int error;
|
||||
|
||||
char serv[8];
|
||||
|
||||
if (!method || !host || !path)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifndef TLS_IMPL
|
||||
if (flags & HTTP_FLAG_TLS)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!port)
|
||||
{
|
||||
if (flags & HTTP_FLAG_TLS)
|
||||
{
|
||||
strcpy(serv, "https");
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(serv, "www");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(serv, sizeof(serv), "%hu", port);
|
||||
}
|
||||
|
||||
|
||||
context = Malloc(sizeof(HttpClientContext));
|
||||
if (!context)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
error = getaddrinfo(host, serv, &hints, &res0);
|
||||
|
||||
if (error)
|
||||
{
|
||||
Free(context);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (res = res0; res; res = res->ai_next)
|
||||
{
|
||||
sd = socket(res->ai_family, res->ai_socktype,
|
||||
res->ai_protocol);
|
||||
|
||||
if (sd < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (connect(sd, res->ai_addr, res->ai_addrlen) < 0)
|
||||
{
|
||||
close(sd);
|
||||
sd = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (sd < 0)
|
||||
{
|
||||
Free(context);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
freeaddrinfo(res0);
|
||||
|
||||
#ifdef TLS_IMPL
|
||||
if (flags & HTTP_FLAG_TLS)
|
||||
{
|
||||
context->stream = TlsClientStream(sd, host);
|
||||
}
|
||||
else
|
||||
{
|
||||
context->stream = StreamFd(sd);
|
||||
}
|
||||
#else
|
||||
context->stream = StreamFd(sd);
|
||||
#endif
|
||||
|
||||
if (!context->stream)
|
||||
{
|
||||
Free(context);
|
||||
close(sd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
StreamPrintf(context->stream, "%s %s HTTP/1.0\r\n",
|
||||
HttpRequestMethodToString(method), path);
|
||||
|
||||
HttpRequestHeader(context, "Connection", "close");
|
||||
HttpRequestHeader(context, "User-Agent", LIB_NAME "/" LIB_VERSION);
|
||||
HttpRequestHeader(context, "Host", host);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
void
|
||||
HttpRequestHeader(HttpClientContext * context, char *key, char *val)
|
||||
{
|
||||
if (!context || !key || !val)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StreamPrintf(context->stream, "%s: %s\r\n", key, val);
|
||||
}
|
||||
|
||||
void
|
||||
HttpRequestSendHeaders(HttpClientContext * context)
|
||||
{
|
||||
if (!context)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StreamPuts(context->stream, "\r\n");
|
||||
StreamFlush(context->stream);
|
||||
}
|
||||
|
||||
HttpStatus
|
||||
HttpRequestSend(HttpClientContext * context)
|
||||
{
|
||||
HttpStatus status;
|
||||
|
||||
char *line = NULL;
|
||||
ssize_t lineLen;
|
||||
size_t lineSize = 0;
|
||||
char *tmp;
|
||||
|
||||
if (!context)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
StreamFlush(context->stream);
|
||||
|
||||
lineLen = UtilGetLine(&line, &lineSize, context->stream);
|
||||
|
||||
while (lineLen == -1 && errno == EAGAIN)
|
||||
{
|
||||
StreamClearError(context->stream);
|
||||
lineLen = UtilGetLine(&line, &lineSize, context->stream);
|
||||
}
|
||||
|
||||
if (lineLen == -1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Line must contain at least "HTTP/x.x xxx" */
|
||||
if (lineLen < 12)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(strncmp(line, "HTTP/1.0", 8) == 0 ||
|
||||
strncmp(line, "HTTP/1.1", 8) == 0))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
tmp = line + 9;
|
||||
|
||||
while (isspace((unsigned char) *tmp) && *tmp != '\0')
|
||||
{
|
||||
tmp++;
|
||||
}
|
||||
|
||||
if (!*tmp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
status = atoi(tmp);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
context->responseHeaders = HttpParseHeaders(context->stream);
|
||||
if (!context->responseHeaders)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HttpResponseHeaders(HttpClientContext * context)
|
||||
{
|
||||
if (!context)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return context->responseHeaders;
|
||||
}
|
||||
|
||||
Stream *
|
||||
HttpClientStream(HttpClientContext * context)
|
||||
{
|
||||
if (!context)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return context->stream;
|
||||
}
|
||||
|
||||
void
|
||||
HttpClientContextFree(HttpClientContext * context)
|
||||
{
|
||||
char *key;
|
||||
void *val;
|
||||
|
||||
if (!context)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (HashMapIterate(context->responseHeaders, &key, &val))
|
||||
{
|
||||
Free(val);
|
||||
}
|
||||
|
||||
HashMapFree(context->responseHeaders);
|
||||
|
||||
StreamClose(context->stream);
|
||||
Free(context);
|
||||
}
|
296
src/HttpRouter.c
Normal file
296
src/HttpRouter.c
Normal file
|
@ -0,0 +1,296 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <HttpRouter.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <HashMap.h>
|
||||
#include <Str.h>
|
||||
|
||||
#include <regex.h>
|
||||
#include <string.h>
|
||||
|
||||
#define REG_FLAGS (REG_EXTENDED)
|
||||
#define REG_MAX_SUB 8
|
||||
|
||||
typedef struct RouteNode
|
||||
{
|
||||
HttpRouteFunc *exec;
|
||||
HashMap *children;
|
||||
|
||||
regex_t regex;
|
||||
} RouteNode;
|
||||
|
||||
struct HttpRouter
|
||||
{
|
||||
RouteNode *root;
|
||||
};
|
||||
|
||||
static RouteNode *
|
||||
RouteNodeCreate(char *regex, HttpRouteFunc * exec)
|
||||
{
|
||||
RouteNode *node;
|
||||
|
||||
if (!regex)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node = Malloc(sizeof(RouteNode));
|
||||
|
||||
if (!node)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node->children = HashMapCreate();
|
||||
if (!node->children)
|
||||
{
|
||||
Free(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Force the regex to match the entire path part exactly. */
|
||||
regex = StrConcat(3, "^", regex, "$");
|
||||
if (!regex)
|
||||
{
|
||||
Free(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (regcomp(&node->regex, regex, REG_FLAGS) != 0)
|
||||
{
|
||||
HashMapFree(node->children);
|
||||
Free(node);
|
||||
Free(regex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node->exec = exec;
|
||||
|
||||
Free(regex);
|
||||
return node;
|
||||
}
|
||||
|
||||
static void
|
||||
RouteNodeFree(RouteNode * node)
|
||||
{
|
||||
char *key;
|
||||
RouteNode *val;
|
||||
|
||||
if (!node)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (HashMapIterate(node->children, &key, (void **) &val))
|
||||
{
|
||||
RouteNodeFree(val);
|
||||
}
|
||||
|
||||
HashMapFree(node->children);
|
||||
|
||||
regfree(&node->regex);
|
||||
|
||||
Free(node);
|
||||
}
|
||||
|
||||
HttpRouter *
|
||||
HttpRouterCreate(void)
|
||||
{
|
||||
HttpRouter *router = Malloc(sizeof(HttpRouter));
|
||||
|
||||
if (!router)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
router->root = RouteNodeCreate("/", NULL);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
void
|
||||
HttpRouterFree(HttpRouter * router)
|
||||
{
|
||||
if (!router)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RouteNodeFree(router->root);
|
||||
Free(router);
|
||||
}
|
||||
|
||||
int
|
||||
HttpRouterAdd(HttpRouter * router, char *regPath, HttpRouteFunc * exec)
|
||||
{
|
||||
RouteNode *node;
|
||||
char *pathPart;
|
||||
char *tmp;
|
||||
|
||||
if (!router || !regPath || !exec)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (StrEquals(regPath, "/"))
|
||||
{
|
||||
router->root->exec = exec;
|
||||
return 1;
|
||||
}
|
||||
|
||||
regPath = StrDuplicate(regPath);
|
||||
if (!regPath)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
tmp = regPath;
|
||||
node = router->root;
|
||||
|
||||
while ((pathPart = strtok_r(tmp, "/", &tmp)))
|
||||
{
|
||||
RouteNode *tNode = HashMapGet(node->children, pathPart);
|
||||
|
||||
if (!tNode)
|
||||
{
|
||||
tNode = RouteNodeCreate(pathPart, NULL);
|
||||
RouteNodeFree(HashMapSet(node->children, pathPart, tNode));
|
||||
}
|
||||
|
||||
node = tNode;
|
||||
}
|
||||
|
||||
node->exec = exec;
|
||||
|
||||
Free(regPath);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
HttpRouterRoute(HttpRouter * router, char *path, void *args, void **ret)
|
||||
{
|
||||
RouteNode *node;
|
||||
char *pathPart;
|
||||
char *tmp;
|
||||
HttpRouteFunc *exec = NULL;
|
||||
Array *matches;
|
||||
size_t i;
|
||||
int retval;
|
||||
|
||||
if (!router || !path)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
matches = ArrayCreate();
|
||||
if (!matches)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
node = router->root;
|
||||
|
||||
if (StrEquals(path, "/"))
|
||||
{
|
||||
exec = node->exec;
|
||||
}
|
||||
else
|
||||
{
|
||||
path = StrDuplicate(path);
|
||||
tmp = path;
|
||||
while ((pathPart = strtok_r(tmp, "/", &tmp)))
|
||||
{
|
||||
char *key;
|
||||
RouteNode *val = NULL;
|
||||
|
||||
regmatch_t pmatch[REG_MAX_SUB];
|
||||
|
||||
i = 0;
|
||||
|
||||
while (HashMapIterateReentrant(node->children, &key, (void **) &val, &i))
|
||||
{
|
||||
if (regexec(&val->regex, pathPart, REG_MAX_SUB, pmatch, 0) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
val = NULL;
|
||||
}
|
||||
|
||||
if (!val)
|
||||
{
|
||||
exec = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
node = val;
|
||||
exec = node->exec;
|
||||
|
||||
/* If we want to pass an arg, the match must be in parens */
|
||||
if (val->regex.re_nsub)
|
||||
{
|
||||
/* pmatch[0] is the whole string, not the first
|
||||
* subexpression */
|
||||
for (i = 1; i < REG_MAX_SUB; i++)
|
||||
{
|
||||
if (pmatch[i].rm_so == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ArrayAdd(matches, StrSubstr(pathPart, pmatch[i].rm_so, pmatch[i].rm_eo));
|
||||
}
|
||||
}
|
||||
}
|
||||
Free(path);
|
||||
}
|
||||
|
||||
if (!exec)
|
||||
{
|
||||
retval = 0;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
{
|
||||
*ret = exec(matches, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
exec(matches, args);
|
||||
}
|
||||
|
||||
retval = 1;
|
||||
|
||||
finish:
|
||||
for (i = 0; i < ArraySize(matches); i++)
|
||||
{
|
||||
Free(ArrayGet(matches, i));
|
||||
}
|
||||
ArrayFree(matches);
|
||||
|
||||
return retval;
|
||||
}
|
753
src/HttpServer.c
Normal file
753
src/HttpServer.c
Normal file
|
@ -0,0 +1,753 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <HttpServer.h>
|
||||
#include <Memory.h>
|
||||
#include <Queue.h>
|
||||
#include <Array.h>
|
||||
#include <Util.h>
|
||||
#include <Tls.h>
|
||||
#include <Log.h>
|
||||
#include <Str.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
static const char ENABLE = 1;
|
||||
|
||||
struct HttpServer
|
||||
{
|
||||
HttpServerConfig config;
|
||||
int sd;
|
||||
pthread_t socketThread;
|
||||
|
||||
volatile unsigned int stop:1;
|
||||
volatile unsigned int isRunning:1;
|
||||
|
||||
Queue *connQueue;
|
||||
pthread_mutex_t connQueueMutex;
|
||||
|
||||
Array *threadPool;
|
||||
};
|
||||
|
||||
struct HttpServerContext
|
||||
{
|
||||
HashMap *requestHeaders;
|
||||
HttpRequestMethod requestMethod;
|
||||
char *requestPath;
|
||||
HashMap *requestParams;
|
||||
|
||||
HashMap *responseHeaders;
|
||||
HttpStatus responseStatus;
|
||||
|
||||
Stream *stream;
|
||||
};
|
||||
|
||||
typedef struct HttpServerWorkerThreadArgs
|
||||
{
|
||||
HttpServer *server;
|
||||
int id;
|
||||
pthread_t thread;
|
||||
} HttpServerWorkerThreadArgs;
|
||||
|
||||
static HttpServerContext *
|
||||
HttpServerContextCreate(HttpRequestMethod requestMethod,
|
||||
char *requestPath, HashMap * requestParams, Stream * stream)
|
||||
{
|
||||
HttpServerContext *c;
|
||||
|
||||
c = Malloc(sizeof(HttpServerContext));
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c->responseHeaders = HashMapCreate();
|
||||
if (!c->responseHeaders)
|
||||
{
|
||||
Free(c->requestHeaders);
|
||||
Free(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c->requestMethod = requestMethod;
|
||||
c->requestPath = requestPath;
|
||||
c->requestParams = requestParams;
|
||||
c->stream = stream;
|
||||
c->responseStatus = HTTP_OK;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static void
|
||||
HttpServerContextFree(HttpServerContext * c)
|
||||
{
|
||||
char *key;
|
||||
void *val;
|
||||
|
||||
if (!c)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (HashMapIterate(c->requestHeaders, &key, &val))
|
||||
{
|
||||
Free(val);
|
||||
}
|
||||
HashMapFree(c->requestHeaders);
|
||||
|
||||
while (HashMapIterate(c->responseHeaders, &key, &val))
|
||||
{
|
||||
/*
|
||||
* These are generated by code. As such, they may be either
|
||||
* on the heap, or on the stack, depending on how they were
|
||||
* added.
|
||||
*
|
||||
* Basically, if the memory API knows about a pointer, then
|
||||
* it can be freed. If it doesn't know about a pointer, skip
|
||||
* freeing it because it's probably a stack pointer.
|
||||
*/
|
||||
|
||||
if (MemoryInfoGet(val))
|
||||
{
|
||||
Free(val);
|
||||
}
|
||||
}
|
||||
|
||||
HashMapFree(c->responseHeaders);
|
||||
|
||||
while (HashMapIterate(c->requestParams, &key, &val))
|
||||
{
|
||||
Free(val);
|
||||
}
|
||||
|
||||
HashMapFree(c->requestParams);
|
||||
|
||||
Free(c->requestPath);
|
||||
StreamClose(c->stream);
|
||||
|
||||
Free(c);
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HttpRequestHeaders(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c->requestHeaders;
|
||||
}
|
||||
|
||||
HttpRequestMethod
|
||||
HttpRequestMethodGet(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return HTTP_METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
return c->requestMethod;
|
||||
}
|
||||
|
||||
char *
|
||||
HttpRequestPath(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c->requestPath;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HttpRequestParams(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c->requestParams;
|
||||
}
|
||||
|
||||
char *
|
||||
HttpResponseHeader(HttpServerContext * c, char *key, char *val)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return HashMapSet(c->responseHeaders, key, val);
|
||||
}
|
||||
|
||||
void
|
||||
HttpResponseStatus(HttpServerContext * c, HttpStatus status)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
c->responseStatus = status;
|
||||
}
|
||||
|
||||
HttpStatus
|
||||
HttpResponseStatusGet(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return HTTP_STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
return c->responseStatus;
|
||||
}
|
||||
|
||||
Stream *
|
||||
HttpServerStream(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c->stream;
|
||||
}
|
||||
|
||||
void
|
||||
HttpSendHeaders(HttpServerContext * c)
|
||||
{
|
||||
Stream *fp = c->stream;
|
||||
|
||||
char *key;
|
||||
char *val;
|
||||
|
||||
StreamPrintf(fp, "HTTP/1.0 %d %s\n", c->responseStatus, HttpStatusToString(c->responseStatus));
|
||||
|
||||
while (HashMapIterate(c->responseHeaders, &key, (void **) &val))
|
||||
{
|
||||
StreamPrintf(fp, "%s: %s\n", key, val);
|
||||
}
|
||||
|
||||
StreamPuts(fp, "\n");
|
||||
}
|
||||
|
||||
static Stream *
|
||||
DequeueConnection(HttpServer * server)
|
||||
{
|
||||
Stream *fp;
|
||||
|
||||
if (!server)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&server->connQueueMutex);
|
||||
fp = QueuePop(server->connQueue);
|
||||
pthread_mutex_unlock(&server->connQueueMutex);
|
||||
|
||||
return fp;
|
||||
}
|
||||
|
||||
HttpServer *
|
||||
HttpServerCreate(HttpServerConfig * config)
|
||||
{
|
||||
HttpServer *server;
|
||||
struct sockaddr_in sa;
|
||||
|
||||
if (!config)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!config->handler)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifndef TLS_IMPL
|
||||
if (config->flags & HTTP_FLAG_TLS)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
server = Malloc(sizeof(HttpServer));
|
||||
if (!server)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
memset(server, 0, sizeof(HttpServer));
|
||||
|
||||
server->config = *config;
|
||||
server->config.tlsCert = StrDuplicate(config->tlsCert);
|
||||
server->config.tlsKey = StrDuplicate(config->tlsKey);
|
||||
|
||||
server->threadPool = ArrayCreate();
|
||||
if (!server->threadPool)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
server->connQueue = QueueCreate(config->maxConnections);
|
||||
if (!server->connQueue)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&server->connQueueMutex, NULL) != 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
server->sd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (server->sd < 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (fcntl(server->sd, F_SETFL, O_NONBLOCK) == -1)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (setsockopt(server->sd, SOL_SOCKET, SO_REUSEADDR, &ENABLE, sizeof(int)) < 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
#ifdef SO_REUSEPORT
|
||||
if (setsockopt(server->sd, SOL_SOCKET, SO_REUSEPORT, &ENABLE, sizeof(int)) < 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
|
||||
memset(&sa, 0, sizeof(struct sockaddr_in));
|
||||
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(config->port);
|
||||
sa.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
if (bind(server->sd, (struct sockaddr *) & sa, sizeof(sa)) < 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (listen(server->sd, config->maxConnections) < 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
server->stop = 0;
|
||||
server->isRunning = 0;
|
||||
|
||||
return server;
|
||||
|
||||
error:
|
||||
if (server)
|
||||
{
|
||||
if (server->connQueue)
|
||||
{
|
||||
QueueFree(server->connQueue);
|
||||
}
|
||||
|
||||
pthread_mutex_destroy(&server->connQueueMutex);
|
||||
|
||||
if (server->threadPool)
|
||||
{
|
||||
ArrayFree(server->threadPool);
|
||||
}
|
||||
|
||||
if (server->sd)
|
||||
{
|
||||
close(server->sd);
|
||||
}
|
||||
|
||||
Free(server);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HttpServerConfig *
|
||||
HttpServerConfigGet(HttpServer * server)
|
||||
{
|
||||
if (!server)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &server->config;
|
||||
}
|
||||
|
||||
void
|
||||
HttpServerFree(HttpServer * server)
|
||||
{
|
||||
if (!server)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
close(server->sd);
|
||||
QueueFree(server->connQueue);
|
||||
pthread_mutex_destroy(&server->connQueueMutex);
|
||||
ArrayFree(server->threadPool);
|
||||
Free(server->config.tlsCert);
|
||||
Free(server->config.tlsKey);
|
||||
Free(server);
|
||||
}
|
||||
|
||||
static void *
|
||||
HttpServerWorkerThread(void *args)
|
||||
{
|
||||
HttpServerWorkerThreadArgs *wArgs = (HttpServerWorkerThreadArgs *) args;
|
||||
HttpServer *server = wArgs->server;
|
||||
|
||||
while (!server->stop)
|
||||
{
|
||||
Stream *fp;
|
||||
HttpServerContext *context;
|
||||
|
||||
char *line = NULL;
|
||||
size_t lineSize = 0;
|
||||
ssize_t lineLen = 0;
|
||||
|
||||
char *requestMethodPtr;
|
||||
char *pathPtr;
|
||||
char *requestPath;
|
||||
char *requestProtocol;
|
||||
|
||||
HashMap *requestParams;
|
||||
ssize_t requestPathLen;
|
||||
|
||||
ssize_t i = 0;
|
||||
HttpRequestMethod requestMethod;
|
||||
|
||||
long firstRead;
|
||||
|
||||
fp = DequeueConnection(server);
|
||||
|
||||
if (!fp)
|
||||
{
|
||||
/* Block for 1 millisecond before continuing so we don't
|
||||
* murder the CPU if the queue is empty. */
|
||||
UtilSleepMillis(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Get the first line of the request.
|
||||
*
|
||||
* Every once in a while, we're too fast for the client. When this
|
||||
* happens, UtilGetLine() sets errno to EAGAIN. If we get
|
||||
* EAGAIN, then clear the error on the stream and try again
|
||||
* after a few ms. This is typically more than enough time for
|
||||
* the client to send data. */
|
||||
firstRead = UtilServerTs();
|
||||
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) == -1
|
||||
&& errno == EAGAIN)
|
||||
{
|
||||
StreamClearError(fp);
|
||||
|
||||
/* If the server is stopped, or it's been a while, just
|
||||
* give up so we aren't wasting a thread on this client. */
|
||||
if (server->stop || (UtilServerTs() - firstRead) > 1000 * 30)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
UtilSleepMillis(5);
|
||||
}
|
||||
|
||||
if (lineLen == -1)
|
||||
{
|
||||
goto bad_request;
|
||||
}
|
||||
|
||||
requestMethodPtr = line;
|
||||
for (i = 0; i < lineLen; i++)
|
||||
{
|
||||
if (line[i] == ' ')
|
||||
{
|
||||
line[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == lineLen)
|
||||
{
|
||||
goto bad_request;
|
||||
}
|
||||
|
||||
requestMethod = HttpRequestMethodFromString(requestMethodPtr);
|
||||
if (requestMethod == HTTP_METHOD_UNKNOWN)
|
||||
{
|
||||
goto bad_request;
|
||||
}
|
||||
|
||||
pathPtr = line + i + 1;
|
||||
|
||||
for (i = 0; i < (line + lineLen) - pathPtr; i++)
|
||||
{
|
||||
if (pathPtr[i] == ' ')
|
||||
{
|
||||
pathPtr[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
requestPathLen = i;
|
||||
requestPath = Malloc(((requestPathLen + 1) * sizeof(char)));
|
||||
strncpy(requestPath, pathPtr, requestPathLen + 1);
|
||||
|
||||
requestProtocol = &pathPtr[i + 1];
|
||||
line[lineLen - 2] = '\0'; /* Get rid of \r and \n */
|
||||
|
||||
if (!StrEquals(requestProtocol, "HTTP/1.1") && !StrEquals(requestProtocol, "HTTP/1.0"))
|
||||
{
|
||||
Free(requestPath);
|
||||
goto bad_request;
|
||||
}
|
||||
|
||||
/* Find request params */
|
||||
for (i = 0; i < requestPathLen; i++)
|
||||
{
|
||||
if (requestPath[i] == '?')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
requestPath[i] = '\0';
|
||||
requestParams = (i == requestPathLen) ? NULL : HttpParamDecode(requestPath + i + 1);
|
||||
|
||||
context = HttpServerContextCreate(requestMethod, requestPath, requestParams, fp);
|
||||
if (!context)
|
||||
{
|
||||
Free(requestPath);
|
||||
goto internal_error;
|
||||
}
|
||||
|
||||
context->requestHeaders = HttpParseHeaders(fp);
|
||||
if (!context->requestHeaders)
|
||||
{
|
||||
goto internal_error;
|
||||
}
|
||||
|
||||
server->config.handler(context, server->config.handlerArgs);
|
||||
|
||||
HttpServerContextFree(context);
|
||||
fp = NULL; /* The above call will close this
|
||||
* Stream */
|
||||
goto finish;
|
||||
|
||||
internal_error:
|
||||
StreamPuts(fp, "HTTP/1.0 500 Internal Server Error\n");
|
||||
StreamPuts(fp, "Connection: close\n");
|
||||
goto finish;
|
||||
|
||||
bad_request:
|
||||
StreamPuts(fp, "HTTP/1.0 400 Bad Request\n");
|
||||
StreamPuts(fp, "Connection: close\n");
|
||||
goto finish;
|
||||
|
||||
finish:
|
||||
Free(line);
|
||||
if (fp)
|
||||
{
|
||||
StreamClose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *
|
||||
HttpServerEventThread(void *args)
|
||||
{
|
||||
HttpServer *server = (HttpServer *) args;
|
||||
struct pollfd pollFds[1];
|
||||
Stream *fp;
|
||||
size_t i;
|
||||
|
||||
server->isRunning = 1;
|
||||
server->stop = 0;
|
||||
|
||||
pollFds[0].fd = server->sd;
|
||||
pollFds[0].events = POLLIN;
|
||||
|
||||
for (i = 0; i < server->config.threads; i++)
|
||||
{
|
||||
HttpServerWorkerThreadArgs *workerThread = Malloc(sizeof(HttpServerWorkerThreadArgs));
|
||||
|
||||
if (!workerThread)
|
||||
{
|
||||
/* TODO: Make the event thread return an error to the main
|
||||
* thread */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
workerThread->server = server;
|
||||
workerThread->id = i;
|
||||
|
||||
if (pthread_create(&workerThread->thread, NULL, HttpServerWorkerThread, workerThread) != 0)
|
||||
{
|
||||
/* TODO: Make the event thread return an error to the main
|
||||
* thread */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ArrayAdd(server->threadPool, workerThread);
|
||||
}
|
||||
|
||||
while (!server->stop)
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addrLen = sizeof(addr);
|
||||
int connFd;
|
||||
int pollResult;
|
||||
|
||||
|
||||
pollResult = poll(pollFds, 1, 500);
|
||||
|
||||
if (pollResult < 0)
|
||||
{
|
||||
/* The poll either timed out, or was interrupted. */
|
||||
continue;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&server->connQueueMutex);
|
||||
|
||||
/* Don't even accept connections if the queue is full. */
|
||||
if (!QueueFull(server->connQueue))
|
||||
{
|
||||
connFd = accept(server->sd, (struct sockaddr *) & addr, &addrLen);
|
||||
|
||||
if (connFd < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&server->connQueueMutex);
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef TLS_IMPL
|
||||
if (server->config.flags & HTTP_FLAG_TLS)
|
||||
{
|
||||
fp = TlsServerStream(connFd, server->config.tlsCert, server->config.tlsKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
fp = StreamFd(connFd);
|
||||
}
|
||||
#else
|
||||
fp = StreamFd(connFd);
|
||||
#endif
|
||||
|
||||
if (!fp)
|
||||
{
|
||||
pthread_mutex_unlock(&server->connQueueMutex);
|
||||
close(connFd);
|
||||
continue;
|
||||
}
|
||||
|
||||
QueuePush(server->connQueue, fp);
|
||||
}
|
||||
pthread_mutex_unlock(&server->connQueueMutex);
|
||||
}
|
||||
|
||||
for (i = 0; i < server->config.threads; i++)
|
||||
{
|
||||
HttpServerWorkerThreadArgs *workerThread = ArrayGet(server->threadPool, i);
|
||||
|
||||
pthread_join(workerThread->thread, NULL);
|
||||
Free(workerThread);
|
||||
}
|
||||
|
||||
while ((fp = DequeueConnection(server)))
|
||||
{
|
||||
StreamClose(fp);
|
||||
}
|
||||
|
||||
server->isRunning = 0;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
HttpServerStart(HttpServer * server)
|
||||
{
|
||||
if (!server)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (server->isRunning)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (pthread_create(&server->socketThread, NULL, HttpServerEventThread, server) != 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
HttpServerJoin(HttpServer * server)
|
||||
{
|
||||
if (!server)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_join(server->socketThread, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
HttpServerStop(HttpServer * server)
|
||||
{
|
||||
if (!server)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
server->stop = 1;
|
||||
}
|
212
src/Io.c
Normal file
212
src/Io.c
Normal file
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Io.h>
|
||||
|
||||
#include <Memory.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
struct Io
|
||||
{
|
||||
IoFunctions io;
|
||||
void *cookie;
|
||||
};
|
||||
|
||||
Io *
|
||||
IoCreate(void *cookie, IoFunctions funcs)
|
||||
{
|
||||
Io *io;
|
||||
|
||||
/* Must have at least read or write */
|
||||
if (!funcs.read && !funcs.write)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
io = Malloc(sizeof(Io));
|
||||
|
||||
if (!io)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
io->cookie = cookie;
|
||||
|
||||
io->io.read = funcs.read;
|
||||
io->io.write = funcs.write;
|
||||
io->io.seek = funcs.seek;
|
||||
io->io.close = funcs.close;
|
||||
|
||||
return io;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
IoRead(Io * io, void *buf, size_t nBytes)
|
||||
{
|
||||
if (!io || !io->io.read)
|
||||
{
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return io->io.read(io->cookie, buf, nBytes);
|
||||
}
|
||||
|
||||
ssize_t
|
||||
IoWrite(Io * io, void *buf, size_t nBytes)
|
||||
{
|
||||
if (!io || !io->io.write)
|
||||
{
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return io->io.write(io->cookie, buf, nBytes);
|
||||
}
|
||||
|
||||
off_t
|
||||
IoSeek(Io * io, off_t offset, int whence)
|
||||
{
|
||||
if (!io)
|
||||
{
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!io->io.seek)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return io->io.seek(io->cookie, offset, whence);
|
||||
}
|
||||
|
||||
int
|
||||
IoClose(Io * io)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!io)
|
||||
{
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (io->io.close)
|
||||
{
|
||||
ret = io->io.close(io->cookie);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
Free(io);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
IoVprintf(Io * io, const char *fmt, va_list ap)
|
||||
{
|
||||
char *buf = NULL;
|
||||
size_t bufSize = 0;
|
||||
FILE *fp;
|
||||
|
||||
int ret;
|
||||
|
||||
if (!io || !fmt)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
fp = open_memstream(&buf, &bufSize);
|
||||
if (!fp)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = vfprintf(fp, fmt, ap);
|
||||
fclose(fp);
|
||||
|
||||
if (ret >= 0)
|
||||
{
|
||||
ret = IoWrite(io, buf, bufSize);
|
||||
}
|
||||
|
||||
free(buf); /* Allocated by stdlib, not Memory
|
||||
* API */
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
IoPrintf(Io * io, const char *fmt,...)
|
||||
{
|
||||
va_list ap;
|
||||
int ret;
|
||||
|
||||
va_start(ap, fmt);
|
||||
ret = IoVprintf(io, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
IoCopy(Io * in, Io * out)
|
||||
{
|
||||
ssize_t nBytes = 0;
|
||||
char buf[IO_BUFFER];
|
||||
ssize_t rRes;
|
||||
ssize_t wRes;
|
||||
|
||||
if (!in || !out)
|
||||
{
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
while ((rRes = IoRead(in, &buf, IO_BUFFER)) != 0)
|
||||
{
|
||||
if (rRes == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
wRes = IoWrite(out, &buf, rRes);
|
||||
|
||||
if (wRes == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
nBytes += wRes;
|
||||
}
|
||||
|
||||
return nBytes;
|
||||
}
|
95
src/Io/IoFd.c
Normal file
95
src/Io/IoFd.c
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Io.h>
|
||||
|
||||
#include <Memory.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
static ssize_t
|
||||
IoReadFd(void *cookie, void *buf, size_t nBytes)
|
||||
{
|
||||
int fd = *((int *) cookie);
|
||||
|
||||
return read(fd, buf, nBytes);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
IoWriteFd(void *cookie, void *buf, size_t nBytes)
|
||||
{
|
||||
int fd = *((int *) cookie);
|
||||
|
||||
return write(fd, buf, nBytes);
|
||||
}
|
||||
|
||||
static off_t
|
||||
IoSeekFd(void *cookie, off_t offset, int whence)
|
||||
{
|
||||
int fd = *((int *) cookie);
|
||||
|
||||
return lseek(fd, offset, whence);
|
||||
}
|
||||
|
||||
static int
|
||||
IoCloseFd(void *cookie)
|
||||
{
|
||||
int fd = *((int *) cookie);
|
||||
|
||||
Free(cookie);
|
||||
return close(fd);
|
||||
}
|
||||
|
||||
Io *
|
||||
IoFd(int fd)
|
||||
{
|
||||
int *cookie = Malloc(sizeof(int));
|
||||
IoFunctions f;
|
||||
|
||||
if (!cookie)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*cookie = fd;
|
||||
|
||||
f.read = IoReadFd;
|
||||
f.write = IoWriteFd;
|
||||
f.seek = IoSeekFd;
|
||||
f.close = IoCloseFd;
|
||||
|
||||
return IoCreate(cookie, f);
|
||||
}
|
||||
|
||||
Io *
|
||||
IoOpen(const char *path, int flags, mode_t mode)
|
||||
{
|
||||
int fd = open(path, flags, mode);
|
||||
|
||||
if (fd == -1)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return IoFd(fd);
|
||||
}
|
91
src/Io/IoFile.c
Normal file
91
src/Io/IoFile.c
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Io.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static ssize_t
|
||||
IoReadFile(void *cookie, void *buf, size_t nBytes)
|
||||
{
|
||||
FILE *fp = cookie;
|
||||
|
||||
return fread(buf, 1, nBytes, fp);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
IoWriteFile(void *cookie, void *buf, size_t nBytes)
|
||||
{
|
||||
FILE *fp = cookie;
|
||||
size_t res = fwrite(buf, 1, nBytes, fp);
|
||||
|
||||
/*
|
||||
* fwrite() may be buffered on some platforms, but at this low level,
|
||||
* it should not be; buffering happens in Stream, not Io.
|
||||
*/
|
||||
fflush(fp);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static off_t
|
||||
IoSeekFile(void *cookie, off_t offset, int whence)
|
||||
{
|
||||
FILE *fp = cookie;
|
||||
off_t ret = fseeko(fp, offset, whence);
|
||||
|
||||
if (ret > -1)
|
||||
{
|
||||
return ftello(fp);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
IoCloseFile(void *cookie)
|
||||
{
|
||||
FILE *fp = cookie;
|
||||
|
||||
return fclose(fp);
|
||||
}
|
||||
|
||||
Io *
|
||||
IoFile(FILE * fp)
|
||||
{
|
||||
IoFunctions f;
|
||||
|
||||
if (!fp)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
f.read = IoReadFile;
|
||||
f.write = IoWriteFile;
|
||||
f.seek = IoSeekFile;
|
||||
f.close = IoCloseFile;
|
||||
|
||||
return IoCreate(fp, f);
|
||||
}
|
1384
src/Json.c
Normal file
1384
src/Json.c
Normal file
File diff suppressed because it is too large
Load diff
388
src/Log.c
Normal file
388
src/Log.c
Normal file
|
@ -0,0 +1,388 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Log.h>
|
||||
|
||||
#include <Memory.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#define LOG_TSBUFFER 64
|
||||
|
||||
struct LogConfig
|
||||
{
|
||||
int level;
|
||||
size_t indent;
|
||||
Stream *out;
|
||||
int flags;
|
||||
char *tsFmt;
|
||||
|
||||
pthread_mutex_t lock;
|
||||
};
|
||||
|
||||
LogConfig *globalConfig = NULL;
|
||||
|
||||
LogConfig *
|
||||
LogConfigCreate(void)
|
||||
{
|
||||
LogConfig *config;
|
||||
|
||||
config = Malloc(sizeof(LogConfig));
|
||||
|
||||
if (!config)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(config, 0, sizeof(LogConfig));
|
||||
|
||||
LogConfigLevelSet(config, LOG_INFO);
|
||||
LogConfigIndentSet(config, 0);
|
||||
LogConfigOutputSet(config, NULL); /* Will set to stdout */
|
||||
LogConfigFlagSet(config, LOG_FLAG_COLOR);
|
||||
LogConfigTimeStampFormatSet(config, "%y-%m-%d %H:%M:%S");
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
LogConfig *
|
||||
LogConfigGlobal(void)
|
||||
{
|
||||
if (!globalConfig)
|
||||
{
|
||||
globalConfig = LogConfigCreate();
|
||||
}
|
||||
|
||||
return globalConfig;
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigFlagClear(LogConfig * config, int flags)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
config->flags &= ~flags;
|
||||
}
|
||||
|
||||
static int
|
||||
LogConfigFlagGet(LogConfig * config, int flags)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return config->flags & flags;
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigFlagSet(LogConfig * config, int flags)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
config->flags |= flags;
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigFree(LogConfig * config)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Free(config);
|
||||
|
||||
if (config == globalConfig)
|
||||
{
|
||||
globalConfig = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigIndent(LogConfig * config)
|
||||
{
|
||||
if (config)
|
||||
{
|
||||
config->indent += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigIndentSet(LogConfig * config, size_t indent)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
config->indent = indent;
|
||||
}
|
||||
|
||||
int
|
||||
LogConfigLevelGet(LogConfig * config)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return config->level;
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigLevelSet(LogConfig * config, int level)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case LOG_ERR:
|
||||
case LOG_WARNING:
|
||||
case LOG_INFO:
|
||||
case LOG_DEBUG:
|
||||
config->level = level;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigOutputSet(LogConfig * config, Stream * out)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (out)
|
||||
{
|
||||
config->out = out;
|
||||
}
|
||||
else
|
||||
{
|
||||
config->out = StreamStdout();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigTimeStampFormatSet(LogConfig * config, char *tsFmt)
|
||||
{
|
||||
if (config)
|
||||
{
|
||||
config->tsFmt = tsFmt;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigUnindent(LogConfig * config)
|
||||
{
|
||||
if (config && config->indent >= 2)
|
||||
{
|
||||
config->indent -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Logv(LogConfig * config, int level, const char *msg, va_list argp)
|
||||
{
|
||||
size_t i;
|
||||
int doColor;
|
||||
char indicator;
|
||||
|
||||
/*
|
||||
* Only proceed if we have a config and its log level is set to a
|
||||
* value that permits us to log. This is as close as we can get
|
||||
* to a no-op function if we aren't logging anything, without doing
|
||||
* some crazy macro magic.
|
||||
*/
|
||||
if (!config || level > config->level)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Misconfiguration */
|
||||
if (!config->out)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&config->lock);
|
||||
|
||||
if (LogConfigFlagGet(config, LOG_FLAG_SYSLOG))
|
||||
{
|
||||
/* No further print logic is needed; syslog will handle it all
|
||||
* for us. */
|
||||
vsyslog(level, msg, argp);
|
||||
pthread_mutex_unlock(&config->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
doColor = LogConfigFlagGet(config, LOG_FLAG_COLOR)
|
||||
&& isatty(StreamFileno(config->out));
|
||||
|
||||
if (doColor)
|
||||
{
|
||||
char *ansi;
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case LOG_EMERG:
|
||||
case LOG_ALERT:
|
||||
case LOG_CRIT:
|
||||
case LOG_ERR:
|
||||
/* Bold Red */
|
||||
ansi = "\033[1;31m";
|
||||
break;
|
||||
case LOG_WARNING:
|
||||
/* Bold Yellow */
|
||||
ansi = "\033[1;33m";
|
||||
break;
|
||||
case LOG_NOTICE:
|
||||
/* Bold Magenta */
|
||||
ansi = "\033[1;35m";
|
||||
break;
|
||||
case LOG_INFO:
|
||||
/* Bold Green */
|
||||
ansi = "\033[1;32m";
|
||||
break;
|
||||
case LOG_DEBUG:
|
||||
/* Bold Blue */
|
||||
ansi = "\033[1;34m";
|
||||
break;
|
||||
default:
|
||||
ansi = "";
|
||||
break;
|
||||
}
|
||||
|
||||
StreamPuts(config->out, ansi);
|
||||
}
|
||||
|
||||
StreamPutc(config->out, '[');
|
||||
|
||||
if (config->tsFmt)
|
||||
{
|
||||
time_t timer = time(NULL);
|
||||
struct tm *timeInfo = localtime(&timer);
|
||||
char tsBuffer[LOG_TSBUFFER];
|
||||
|
||||
int tsLength = strftime(tsBuffer, LOG_TSBUFFER, config->tsFmt,
|
||||
timeInfo);
|
||||
|
||||
if (tsLength)
|
||||
{
|
||||
StreamPuts(config->out, tsBuffer);
|
||||
if (!isspace((unsigned char) tsBuffer[tsLength - 1]))
|
||||
{
|
||||
StreamPutc(config->out, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case LOG_EMERG:
|
||||
indicator = '#';
|
||||
break;
|
||||
case LOG_ALERT:
|
||||
indicator = '@';
|
||||
break;
|
||||
case LOG_CRIT:
|
||||
indicator = 'X';
|
||||
break;
|
||||
case LOG_ERR:
|
||||
indicator = 'x';
|
||||
break;
|
||||
case LOG_WARNING:
|
||||
indicator = '!';
|
||||
break;
|
||||
case LOG_NOTICE:
|
||||
indicator = '~';
|
||||
break;
|
||||
case LOG_INFO:
|
||||
indicator = '>';
|
||||
break;
|
||||
case LOG_DEBUG:
|
||||
indicator = '*';
|
||||
break;
|
||||
default:
|
||||
indicator = '?';
|
||||
break;
|
||||
}
|
||||
|
||||
StreamPrintf(config->out, "%c]", indicator);
|
||||
|
||||
if (doColor)
|
||||
{
|
||||
/* ANSI Reset */
|
||||
StreamPuts(config->out, "\033[0m");
|
||||
}
|
||||
|
||||
StreamPutc(config->out, ' ');
|
||||
for (i = 0; i < config->indent; i++)
|
||||
{
|
||||
StreamPutc(config->out, ' ');
|
||||
}
|
||||
|
||||
StreamVprintf(config->out, msg, argp);
|
||||
StreamPutc(config->out, '\n');
|
||||
|
||||
StreamFlush(config->out);
|
||||
|
||||
pthread_mutex_unlock(&config->lock);
|
||||
}
|
||||
|
||||
void
|
||||
LogTo(LogConfig * config, int level, const char *fmt,...)
|
||||
{
|
||||
va_list argp;
|
||||
|
||||
va_start(argp, fmt);
|
||||
Logv(config, level, fmt, argp);
|
||||
va_end(argp);
|
||||
}
|
||||
|
||||
extern void
|
||||
Log(int level, const char *fmt,...)
|
||||
{
|
||||
va_list argp;
|
||||
|
||||
va_start(argp, fmt);
|
||||
Logv(LogConfigGlobal(), level, fmt, argp);
|
||||
va_end(argp);
|
||||
}
|
493
src/Memory.c
Normal file
493
src/Memory.c
Normal file
|
@ -0,0 +1,493 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Memory.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#ifndef MEMORY_TABLE_CHUNK
|
||||
#define MEMORY_TABLE_CHUNK 256
|
||||
#endif
|
||||
|
||||
#ifndef MEMORY_HEXDUMP_WIDTH
|
||||
#define MEMORY_HEXDUMP_WIDTH 16
|
||||
#endif
|
||||
|
||||
struct MemoryInfo
|
||||
{
|
||||
size_t size;
|
||||
const char *file;
|
||||
int line;
|
||||
void *pointer;
|
||||
};
|
||||
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static void (*hook) (MemoryAction, MemoryInfo *, void *) = NULL;
|
||||
static void *hookArgs = NULL;
|
||||
|
||||
static MemoryInfo **allocations = NULL;
|
||||
static size_t allocationsSize = 0;
|
||||
static size_t allocationsLen = 0;
|
||||
|
||||
static size_t
|
||||
MemoryHash(void *p)
|
||||
{
|
||||
return (((size_t) p) >> 2 * 7) % allocationsSize;
|
||||
}
|
||||
|
||||
static int
|
||||
MemoryInsert(MemoryInfo * a)
|
||||
{
|
||||
size_t hash;
|
||||
|
||||
if (!allocations)
|
||||
{
|
||||
allocationsSize = MEMORY_TABLE_CHUNK;
|
||||
allocations = calloc(allocationsSize, sizeof(void *));
|
||||
if (!allocations)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the next insertion would cause the table to be at least 3/4
|
||||
* full, re-allocate and re-hash. */
|
||||
if ((allocationsLen + 1) >= ((allocationsSize * 3) >> 2))
|
||||
{
|
||||
size_t i;
|
||||
size_t tmpAllocationsSize = allocationsSize;
|
||||
MemoryInfo **tmpAllocations;
|
||||
|
||||
allocationsSize += MEMORY_TABLE_CHUNK;
|
||||
tmpAllocations = calloc(allocationsSize, sizeof(void *));
|
||||
|
||||
if (!tmpAllocations)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < tmpAllocationsSize; i++)
|
||||
{
|
||||
if (allocations[i])
|
||||
{
|
||||
hash = MemoryHash(allocations[i]->pointer);
|
||||
|
||||
while (tmpAllocations[hash])
|
||||
{
|
||||
hash = (hash + 1) % allocationsSize;
|
||||
}
|
||||
|
||||
tmpAllocations[hash] = allocations[i];
|
||||
}
|
||||
}
|
||||
|
||||
free(allocations);
|
||||
allocations = tmpAllocations;
|
||||
}
|
||||
|
||||
hash = MemoryHash(a->pointer);
|
||||
|
||||
while (allocations[hash])
|
||||
{
|
||||
hash = (hash + 1) % allocationsSize;
|
||||
}
|
||||
|
||||
allocations[hash] = a;
|
||||
allocationsLen++;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
MemoryDelete(MemoryInfo * a)
|
||||
{
|
||||
size_t hash = MemoryHash(a->pointer);
|
||||
size_t count = 0;
|
||||
|
||||
while (count <= allocationsSize)
|
||||
{
|
||||
if (allocations[hash] && allocations[hash] == a)
|
||||
{
|
||||
allocations[hash] = NULL;
|
||||
allocationsLen--;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
hash = (hash + 1) % allocationsSize;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void *
|
||||
MemoryAllocate(size_t size, const char *file, int line)
|
||||
{
|
||||
void *p;
|
||||
MemoryInfo *a;
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
p = malloc(size);
|
||||
if (!p)
|
||||
{
|
||||
pthread_mutex_unlock(&lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
a = malloc(sizeof(MemoryInfo));
|
||||
if (!a)
|
||||
{
|
||||
free(p);
|
||||
pthread_mutex_unlock(&lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
a->size = size;
|
||||
a->file = file;
|
||||
a->line = line;
|
||||
a->pointer = p;
|
||||
|
||||
if (!MemoryInsert(a))
|
||||
{
|
||||
free(a);
|
||||
free(p);
|
||||
pthread_mutex_unlock(&lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (hook)
|
||||
{
|
||||
hook(MEMORY_ALLOCATE, a, hookArgs);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
return p;
|
||||
}
|
||||
|
||||
void *
|
||||
MemoryReallocate(void *p, size_t size, const char *file, int line)
|
||||
{
|
||||
MemoryInfo *a;
|
||||
void *new = NULL;
|
||||
|
||||
if (!p)
|
||||
{
|
||||
return MemoryAllocate(size, file, line);
|
||||
}
|
||||
|
||||
a = MemoryInfoGet(p);
|
||||
if (a)
|
||||
{
|
||||
pthread_mutex_lock(&lock);
|
||||
new = realloc(a->pointer, size);
|
||||
if (new)
|
||||
{
|
||||
MemoryDelete(a);
|
||||
a->size = size;
|
||||
a->file = file;
|
||||
a->line = line;
|
||||
|
||||
a->pointer = new;
|
||||
MemoryInsert(a);
|
||||
|
||||
if (hook)
|
||||
{
|
||||
hook(MEMORY_REALLOCATE, a, hookArgs);
|
||||
}
|
||||
|
||||
}
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
else if (hook)
|
||||
{
|
||||
a = malloc(sizeof(MemoryInfo));
|
||||
if (a)
|
||||
{
|
||||
a->size = 0;
|
||||
a->file = file;
|
||||
a->line = line;
|
||||
a->pointer = p;
|
||||
hook(MEMORY_BAD_POINTER, a, hookArgs);
|
||||
free(a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
void
|
||||
MemoryFree(void *p, const char *file, int line)
|
||||
{
|
||||
MemoryInfo *a;
|
||||
|
||||
if (!p)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
a = MemoryInfoGet(p);
|
||||
if (a)
|
||||
{
|
||||
pthread_mutex_lock(&lock);
|
||||
if (hook)
|
||||
{
|
||||
a->file = file;
|
||||
a->line = line;
|
||||
hook(MEMORY_FREE, a, hookArgs);
|
||||
}
|
||||
MemoryDelete(a);
|
||||
free(a->pointer);
|
||||
free(a);
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
else if (hook)
|
||||
{
|
||||
a = malloc(sizeof(MemoryInfo));
|
||||
if (a)
|
||||
{
|
||||
a->file = file;
|
||||
a->line = line;
|
||||
a->size = 0;
|
||||
a->pointer = p;
|
||||
hook(MEMORY_BAD_POINTER, a, hookArgs);
|
||||
free(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
MemoryAllocated(void)
|
||||
{
|
||||
size_t i;
|
||||
size_t total = 0;
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
for (i = 0; i < allocationsSize; i++)
|
||||
{
|
||||
if (allocations[i])
|
||||
{
|
||||
total += allocations[i]->size;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
void
|
||||
MemoryFreeAll(void)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
for (i = 0; i < allocationsSize; i++)
|
||||
{
|
||||
if (allocations[i])
|
||||
{
|
||||
free(allocations[i]->pointer);
|
||||
free(allocations[i]);
|
||||
}
|
||||
}
|
||||
|
||||
free(allocations);
|
||||
allocations = NULL;
|
||||
allocationsSize = 0;
|
||||
allocationsLen = 0;
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
|
||||
MemoryInfo *
|
||||
MemoryInfoGet(void *p)
|
||||
{
|
||||
size_t hash, count;
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
hash = MemoryHash(p);
|
||||
|
||||
count = 0;
|
||||
while (count <= allocationsSize)
|
||||
{
|
||||
if (!allocations[hash] || allocations[hash]->pointer != p)
|
||||
{
|
||||
hash = (hash + 1) % allocationsSize;
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pthread_mutex_unlock(&lock);
|
||||
return allocations[hash];
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t
|
||||
MemoryInfoGetSize(MemoryInfo * a)
|
||||
{
|
||||
if (!a)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return a->size;
|
||||
}
|
||||
|
||||
const char *
|
||||
MemoryInfoGetFile(MemoryInfo * a)
|
||||
{
|
||||
if (!a)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return a->file;
|
||||
}
|
||||
|
||||
int
|
||||
MemoryInfoGetLine(MemoryInfo * a)
|
||||
{
|
||||
if (!a)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return a->line;
|
||||
}
|
||||
|
||||
void *
|
||||
MemoryInfoGetPointer(MemoryInfo * a)
|
||||
{
|
||||
if (!a)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return a->pointer;
|
||||
}
|
||||
|
||||
void
|
||||
MemoryIterate(void (*iterFunc) (MemoryInfo *, void *), void *args)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
for (i = 0; i < allocationsSize; i++)
|
||||
{
|
||||
if (allocations[i])
|
||||
{
|
||||
iterFunc(allocations[i], args);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
|
||||
void
|
||||
MemoryHook(void (*memHook) (MemoryAction, MemoryInfo *, void *), void *args)
|
||||
{
|
||||
pthread_mutex_lock(&lock);
|
||||
hook = memHook;
|
||||
hookArgs = args;
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
|
||||
void
|
||||
MemoryHexDump(MemoryInfo * info, void (*printFunc) (size_t, char *, char *, void *), void *args)
|
||||
{
|
||||
char hexBuf[(MEMORY_HEXDUMP_WIDTH * 2) + MEMORY_HEXDUMP_WIDTH + 1];
|
||||
char asciiBuf[MEMORY_HEXDUMP_WIDTH + 1];
|
||||
size_t pI = 0;
|
||||
size_t hI = 0;
|
||||
size_t aI = 0;
|
||||
const unsigned char *pc;
|
||||
|
||||
if (!info || !printFunc)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pc = MemoryInfoGetPointer(info);
|
||||
|
||||
for (pI = 0; pI < MemoryInfoGetSize(info); pI++)
|
||||
{
|
||||
if (pI > 0 && pI % MEMORY_HEXDUMP_WIDTH == 0)
|
||||
{
|
||||
hexBuf[hI - 1] = '\0';
|
||||
asciiBuf[aI] = '\0';
|
||||
|
||||
printFunc(pI - MEMORY_HEXDUMP_WIDTH, hexBuf, asciiBuf, args);
|
||||
|
||||
snprintf(hexBuf, 4, "%02x ", pc[pI]);
|
||||
hI = 3;
|
||||
|
||||
asciiBuf[0] = isprint(pc[pI]) ? pc[pI] : '.';
|
||||
asciiBuf[1] = '\0';
|
||||
aI = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
asciiBuf[aI] = isprint(pc[pI]) ? pc[pI] : '.';
|
||||
aI++;
|
||||
|
||||
snprintf(hexBuf + hI, 4, "%02x ", pc[pI]);
|
||||
hI += 3;
|
||||
}
|
||||
}
|
||||
|
||||
hexBuf[hI] = '\0';
|
||||
hI--;
|
||||
|
||||
while (hI < sizeof(hexBuf) - 2)
|
||||
{
|
||||
hexBuf[hI] = ' ';
|
||||
hI++;
|
||||
}
|
||||
|
||||
while (aI < sizeof(asciiBuf) - 1)
|
||||
{
|
||||
asciiBuf[aI] = ' ';
|
||||
aI++;
|
||||
}
|
||||
|
||||
hexBuf[hI] = '\0';
|
||||
asciiBuf[aI] = '\0';
|
||||
|
||||
printFunc(pI - ((pI % MEMORY_HEXDUMP_WIDTH) ?
|
||||
(pI % MEMORY_HEXDUMP_WIDTH) : MEMORY_HEXDUMP_WIDTH),
|
||||
hexBuf, asciiBuf, args);
|
||||
printFunc(pI, NULL, NULL, args);
|
||||
}
|
176
src/Queue.c
Normal file
176
src/Queue.c
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Queue.h>
|
||||
|
||||
#include <Memory.h>
|
||||
|
||||
struct Queue
|
||||
{
|
||||
void **items;
|
||||
size_t size;
|
||||
size_t front;
|
||||
size_t rear;
|
||||
};
|
||||
|
||||
Queue *
|
||||
QueueCreate(size_t size)
|
||||
{
|
||||
Queue *q;
|
||||
|
||||
if (!size)
|
||||
{
|
||||
/* Can't have a queue of length zero */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
q = Malloc(sizeof(Queue));
|
||||
if (!q)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
q->items = Malloc(size * sizeof(void *));
|
||||
if (!q->items)
|
||||
{
|
||||
Free(q);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
q->size = size;
|
||||
q->front = size + 1;
|
||||
q->rear = size + 1;
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
void
|
||||
QueueFree(Queue * q)
|
||||
{
|
||||
if (q)
|
||||
{
|
||||
Free(q->items);
|
||||
}
|
||||
|
||||
Free(q);
|
||||
}
|
||||
|
||||
int
|
||||
QueueFull(Queue * q)
|
||||
{
|
||||
if (!q)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ((q->front == q->rear + 1) || (q->front == 0 && q->rear == q->size - 1));
|
||||
}
|
||||
|
||||
int
|
||||
QueueEmpty(Queue * q)
|
||||
{
|
||||
if (!q)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return q->front == q->size + 1;
|
||||
}
|
||||
|
||||
int
|
||||
QueuePush(Queue * q, void *element)
|
||||
{
|
||||
if (!q || !element)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (QueueFull(q))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (q->front == q->size + 1)
|
||||
{
|
||||
q->front = 0;
|
||||
}
|
||||
|
||||
if (q->rear == q->size + 1)
|
||||
{
|
||||
q->rear = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
q->rear = (q->rear + 1) % q->size;
|
||||
}
|
||||
|
||||
q->items[q->rear] = element;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void *
|
||||
QueuePop(Queue * q)
|
||||
{
|
||||
void *element;
|
||||
|
||||
if (!q)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (QueueEmpty(q))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
element = q->items[q->front];
|
||||
|
||||
if (q->front == q->rear)
|
||||
{
|
||||
q->front = q->size + 1;
|
||||
q->rear = q->size + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
q->front = (q->front + 1) % q->size;
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
void *
|
||||
QueuePeek(Queue * q)
|
||||
{
|
||||
if (!q)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (QueueEmpty(q))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return q->items[q->front];
|
||||
}
|
172
src/Rand.c
Normal file
172
src/Rand.c
Normal file
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Rand.h>
|
||||
|
||||
#include <Int.h>
|
||||
#include <Util.h>
|
||||
#include <Memory.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define RAND_STATE_VECTOR_LENGTH 624
|
||||
#define RAND_STATE_VECTOR_M 397
|
||||
|
||||
#define RAND_UPPER_MASK 0x80000000
|
||||
#define RAND_LOWER_MASK 0x7FFFFFFF
|
||||
#define RAND_TEMPER_B 0x9D2C5680
|
||||
#define RAND_TEMPER_C 0xEFC60000
|
||||
|
||||
typedef struct RandState
|
||||
{
|
||||
UInt32 mt[RAND_STATE_VECTOR_LENGTH];
|
||||
int index;
|
||||
} RandState;
|
||||
|
||||
static void
|
||||
RandSeed(RandState * state, UInt32 seed)
|
||||
{
|
||||
state->mt[0] = seed & 0xFFFFFFFF;
|
||||
|
||||
for (state->index = 1; state->index < RAND_STATE_VECTOR_LENGTH; state->index++)
|
||||
{
|
||||
state->mt[state->index] = (6069 * state->mt[state->index - 1]) & 0xFFFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
static UInt32
|
||||
RandGenerate(RandState * state)
|
||||
{
|
||||
static const UInt32 mag[2] = {0x0, 0x9908B0DF};
|
||||
|
||||
UInt32 result;
|
||||
|
||||
if (state->index >= RAND_STATE_VECTOR_LENGTH || state->index < 0)
|
||||
{
|
||||
int kk;
|
||||
|
||||
if (state->index >= RAND_STATE_VECTOR_LENGTH + 1 || state->index < 0)
|
||||
{
|
||||
RandSeed(state, 4357);
|
||||
}
|
||||
|
||||
for (kk = 0; kk < RAND_STATE_VECTOR_LENGTH - RAND_STATE_VECTOR_M; kk++)
|
||||
{
|
||||
result = (state->mt[kk] & RAND_UPPER_MASK) | (state->mt[kk + 1] & RAND_LOWER_MASK);
|
||||
state->mt[kk] = state->mt[kk + RAND_STATE_VECTOR_M] ^ (result >> 1) ^ mag[result & 0x1];
|
||||
}
|
||||
|
||||
for (; kk < RAND_STATE_VECTOR_LENGTH - 1; kk++)
|
||||
{
|
||||
result = (state->mt[kk] & RAND_UPPER_MASK) | (state->mt[kk + 1] & RAND_LOWER_MASK);
|
||||
state->mt[kk] = state->mt[kk + (RAND_STATE_VECTOR_M - RAND_STATE_VECTOR_LENGTH)] ^ (result >> 1) ^ mag[result & 0x1];
|
||||
}
|
||||
|
||||
result = (state->mt[RAND_STATE_VECTOR_LENGTH - 1] & RAND_UPPER_MASK) | (state->mt[0] & RAND_LOWER_MASK);
|
||||
state->mt[RAND_STATE_VECTOR_LENGTH - 1] = state->mt[RAND_STATE_VECTOR_M - 1] ^ (result >> 1) ^ mag[result & 0x1];
|
||||
state->index = 0;
|
||||
}
|
||||
|
||||
result = state->mt[state->index++];
|
||||
result ^= (result >> 11);
|
||||
result ^= (result << 7) & RAND_TEMPER_B;
|
||||
result ^= (result << 15) & RAND_TEMPER_C;
|
||||
result ^= (result >> 18);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
RandDestructor(void *p)
|
||||
{
|
||||
Free(p);
|
||||
}
|
||||
|
||||
/* Generate random numbers using rejection sampling. The basic idea is
|
||||
* to "reroll" if a number happens to be outside the range. However
|
||||
* this could be extremely inefficient.
|
||||
*
|
||||
* Another idea would just be to "reroll" if the generated number ends up
|
||||
* in the previously "biased" range, and THEN do a modulo.
|
||||
*
|
||||
* This would be far more efficient for small values of max, and fixes the
|
||||
* bias issue. */
|
||||
|
||||
/* This algorithm therefore computes N random numbers generally in O(N)
|
||||
* time, while being less biased. */
|
||||
void
|
||||
RandIntN(int *buf, size_t size, unsigned int max)
|
||||
{
|
||||
static pthread_key_t stateKey;
|
||||
static int createdKey = 0;
|
||||
|
||||
/* Limit the range to banish all previously biased results */
|
||||
const int allowed = RAND_MAX - RAND_MAX % max;
|
||||
|
||||
RandState *state;
|
||||
int tmp;
|
||||
size_t i;
|
||||
|
||||
if (!createdKey)
|
||||
{
|
||||
pthread_key_create(&stateKey, RandDestructor);
|
||||
createdKey = 1;
|
||||
}
|
||||
|
||||
state = pthread_getspecific(stateKey);
|
||||
|
||||
if (!state)
|
||||
{
|
||||
/* Generate a seed from the system time, PID, and TID */
|
||||
UInt32 seed = UtilServerTs() ^ getpid() ^ (unsigned long) pthread_self();
|
||||
|
||||
state = Malloc(sizeof(RandState));
|
||||
RandSeed(state, seed);
|
||||
|
||||
pthread_setspecific(stateKey, state);
|
||||
}
|
||||
|
||||
/* Generate {size} random numbers. */
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
/* Most of the time, this will take about 1 loop */
|
||||
do
|
||||
{
|
||||
tmp = RandGenerate(state);
|
||||
} while (tmp > allowed);
|
||||
|
||||
buf[i] = tmp % max;
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate just 1 random number */
|
||||
int
|
||||
RandInt(unsigned int max)
|
||||
{
|
||||
int val = 0;
|
||||
|
||||
RandIntN(&val, 1, max);
|
||||
return val;
|
||||
}
|
162
src/RtStub.c
Normal file
162
src/RtStub.c
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Runtime.h>
|
||||
#include <Array.h>
|
||||
#include <HashMap.h>
|
||||
#include <Stream.h>
|
||||
#include <Log.h>
|
||||
#include <Memory.h>
|
||||
#include <Str.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/* Specified by POSIX to contain environment variables */
|
||||
extern char **environ;
|
||||
|
||||
/* The linking program is expected to provide Main */
|
||||
extern int Main(Array *, HashMap *);
|
||||
|
||||
typedef struct MainArgs
|
||||
{
|
||||
Array *args;
|
||||
HashMap *env;
|
||||
} MainArgs;
|
||||
|
||||
static void *
|
||||
MainThread(void *argp)
|
||||
{
|
||||
long ret;
|
||||
MainArgs *args = argp;
|
||||
|
||||
args = argp;
|
||||
ret = Main(args->args, args->env);
|
||||
|
||||
return (void *) ret;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
pthread_t mainThread;
|
||||
size_t i;
|
||||
int ret;
|
||||
|
||||
char *key;
|
||||
char *val;
|
||||
char **envp;
|
||||
|
||||
MainArgs args;
|
||||
|
||||
args.args = NULL;
|
||||
args.env = NULL;
|
||||
|
||||
args.args = ArrayCreate();
|
||||
|
||||
if (!args.args)
|
||||
{
|
||||
Log(LOG_ERR, "Bootstrap error: Unable to allocate memory for arguments.");
|
||||
ret = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
args.env = HashMapCreate();
|
||||
if (!args.env)
|
||||
{
|
||||
Log(LOG_ERR, "Bootstrap error: Unable to allocate memory for environment.");
|
||||
ret = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
for (i = 0; i < (size_t) argc; i++)
|
||||
{
|
||||
ArrayAdd(args.args, StrDuplicate(argv[i]));
|
||||
}
|
||||
|
||||
envp = environ;
|
||||
while (*envp)
|
||||
{
|
||||
size_t valInd;
|
||||
|
||||
/* It is unclear whether or not envp strings are writable, so
|
||||
* we make our own copy to manipulate it */
|
||||
key = StrDuplicate(*envp);
|
||||
valInd = strcspn(key, "=");
|
||||
|
||||
key[valInd] = '\0';
|
||||
val = key + valInd + 1;
|
||||
HashMapSet(args.env, key, StrDuplicate(val));
|
||||
Free(key);
|
||||
envp++;
|
||||
}
|
||||
|
||||
if (pthread_create(&mainThread, NULL, MainThread, &args) != 0)
|
||||
{
|
||||
Log(LOG_ERR, "Bootstrap error: Unable to create main thread.");
|
||||
ret = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (pthread_join(mainThread, (void **) &ret) != 0)
|
||||
{
|
||||
/* Should never happen */
|
||||
Log(LOG_ERR, "Unable to join main thread.");
|
||||
ret = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
finish:
|
||||
if (args.args)
|
||||
{
|
||||
for (i = 0; i < ArraySize(args.args); i++)
|
||||
{
|
||||
Free(ArrayGet(args.args, i));
|
||||
}
|
||||
ArrayFree(args.args);
|
||||
}
|
||||
|
||||
if (args.env)
|
||||
{
|
||||
while (HashMapIterate(args.env, &key, (void **) &val))
|
||||
{
|
||||
Free(val);
|
||||
}
|
||||
HashMapFree(args.env);
|
||||
}
|
||||
|
||||
Log(LOG_DEBUG, "Exitting with code: %d", ret);
|
||||
|
||||
LogConfigFree(LogConfigGlobal());
|
||||
|
||||
StreamClose(StreamStdout());
|
||||
StreamClose(StreamStdin());
|
||||
StreamClose(StreamStderr());
|
||||
|
||||
GenerateMemoryReport(argv[0]);
|
||||
|
||||
MemoryFreeAll();
|
||||
|
||||
return ret;
|
||||
}
|
111
src/Runtime.c
Normal file
111
src/Runtime.c
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Runtime.h>
|
||||
#include <Array.h>
|
||||
#include <Stream.h>
|
||||
#include <Log.h>
|
||||
#include <Memory.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <libgen.h>
|
||||
|
||||
static void
|
||||
HexDump(size_t off, char *hexBuf, char *asciiBuf, void *args)
|
||||
{
|
||||
FILE *report = args;
|
||||
|
||||
if (hexBuf && asciiBuf)
|
||||
{
|
||||
fprintf(report, "%04lx: %s | %s |\n", off, hexBuf, asciiBuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(report, "%04lx\n", off);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
MemoryIterator(MemoryInfo * i, void *args)
|
||||
{
|
||||
FILE *report = args;
|
||||
|
||||
fprintf(report, "%s:%d: %lu bytes at %p\n",
|
||||
MemoryInfoGetFile(i), MemoryInfoGetLine(i),
|
||||
MemoryInfoGetSize(i), MemoryInfoGetPointer(i));
|
||||
|
||||
MemoryHexDump(i, HexDump, report);
|
||||
|
||||
fprintf(report, "\n");
|
||||
}
|
||||
|
||||
void
|
||||
GenerateMemoryReport(const char *prog)
|
||||
{
|
||||
char reportName[128];
|
||||
char *namePtr;
|
||||
|
||||
/*
|
||||
* Use C standard IO instead of the Stream or Io APIs, because
|
||||
* those use the Memory API, and that's exactly what we're trying
|
||||
* to assess, so using it would generate false positives. None of
|
||||
* this code should leak memory.
|
||||
*/
|
||||
FILE *report;
|
||||
time_t currentTime;
|
||||
struct tm *timeInfo;
|
||||
char tsBuffer[1024];
|
||||
|
||||
if (!MemoryAllocated())
|
||||
{
|
||||
/* No memory leaked, no need to write the report. This is the
|
||||
* ideal situation; we only want the report to show up if leaks
|
||||
* occurred. */
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(reportName, sizeof(reportName), "%s-leaked.txt", prog);
|
||||
/* Make sure the report goes in the current working directory. */
|
||||
namePtr = basename(reportName);
|
||||
report = fopen(namePtr, "a");
|
||||
if (!report)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
currentTime = time(NULL);
|
||||
timeInfo = localtime(¤tTime);
|
||||
strftime(tsBuffer, sizeof(tsBuffer), "%c", timeInfo);
|
||||
|
||||
fprintf(report, "---------- Memory Report ----------\n");
|
||||
fprintf(report, "Program: %s\n", prog);
|
||||
fprintf(report, "Date: %s\n", tsBuffer);
|
||||
fprintf(report, "Total Bytes: %lu\n", MemoryAllocated());
|
||||
fprintf(report, "\n");
|
||||
|
||||
MemoryIterate(MemoryIterator, report);
|
||||
|
||||
fclose(report);
|
||||
}
|
238
src/Sha2.c
Normal file
238
src/Sha2.c
Normal file
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Sha2.h>
|
||||
#include <Memory.h>
|
||||
#include <Int.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#define GET_UINT32(x) \
|
||||
(((UInt32)(x)[0] << 24) | \
|
||||
((UInt32)(x)[1] << 16) | \
|
||||
((UInt32)(x)[2] << 8) | \
|
||||
((UInt32)(x)[3]))
|
||||
|
||||
#define PUT_UINT32(dst, x) { \
|
||||
(dst)[0] = (x) >> 24; \
|
||||
(dst)[1] = (x) >> 16; \
|
||||
(dst)[2] = (x) >> 8; \
|
||||
(dst)[3] = (x); \
|
||||
}
|
||||
|
||||
#define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
|
||||
|
||||
#define S0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ (x >> 3))
|
||||
#define S1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ (x >> 10))
|
||||
|
||||
#define T0(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
|
||||
#define T1(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
|
||||
|
||||
#define CH(a, b, c) (((a) & (b)) ^ ((~(a)) & (c)))
|
||||
#define MAJ(a, b, c) (((a) & (b)) ^ ((a) & (c)) ^ ((b) & (c)))
|
||||
#define WW(i) (w[i] = w[i - 16] + S0(w[i - 15]) + w[i - 7] + S1(w[i - 2]))
|
||||
|
||||
#define ROUND(a, b, c, d, e, f, g, h, k, w) { \
|
||||
UInt32 tmp0 = h + T0(e) + CH(e, f, g) + k + w; \
|
||||
UInt32 tmp1 = T1(a) + MAJ(a, b, c); \
|
||||
h = tmp0 + tmp1; \
|
||||
d += tmp0; \
|
||||
}
|
||||
|
||||
typedef struct Sha256Context
|
||||
{
|
||||
size_t length;
|
||||
UInt32 state[8];
|
||||
size_t bufLen;
|
||||
unsigned char buffer[64];
|
||||
} Sha256Context;
|
||||
|
||||
static void
|
||||
Sha256Chunk(Sha256Context * context, unsigned char chunk[64])
|
||||
{
|
||||
const UInt32 rk[64] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
|
||||
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
|
||||
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
|
||||
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
|
||||
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
|
||||
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
||||
};
|
||||
|
||||
UInt32 w[64];
|
||||
UInt32 a, b, c, d, e, f, g, h;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
{
|
||||
w[i] = GET_UINT32(&chunk[4 * i]);
|
||||
}
|
||||
|
||||
a = context->state[0];
|
||||
b = context->state[1];
|
||||
c = context->state[2];
|
||||
d = context->state[3];
|
||||
e = context->state[4];
|
||||
f = context->state[5];
|
||||
g = context->state[6];
|
||||
h = context->state[7];
|
||||
|
||||
for (i = 0; i < 16; i += 8)
|
||||
{
|
||||
ROUND(a, b, c, d, e, f, g, h, rk[i], w[i]);
|
||||
ROUND(h, a, b, c, d, e, f, g, rk[i + 1], w[i + 1]);
|
||||
ROUND(g, h, a, b, c, d, e, f, rk[i + 2], w[i + 2]);
|
||||
ROUND(f, g, h, a, b, c, d, e, rk[i + 3], w[i + 3]);
|
||||
ROUND(e, f, g, h, a, b, c, d, rk[i + 4], w[i + 4]);
|
||||
ROUND(d, e, f, g, h, a, b, c, rk[i + 5], w[i + 5]);
|
||||
ROUND(c, d, e, f, g, h, a, b, rk[i + 6], w[i + 6]);
|
||||
ROUND(b, c, d, e, f, g, h, a, rk[i + 7], w[i + 7]);
|
||||
}
|
||||
|
||||
for (i = 16; i < 64; i += 8)
|
||||
{
|
||||
ROUND(a, b, c, d, e, f, g, h, rk[i], WW(i));
|
||||
ROUND(h, a, b, c, d, e, f, g, rk[i + 1], WW(i + 1));
|
||||
ROUND(g, h, a, b, c, d, e, f, rk[i + 2], WW(i + 2));
|
||||
ROUND(f, g, h, a, b, c, d, e, rk[i + 3], WW(i + 3));
|
||||
ROUND(e, f, g, h, a, b, c, d, rk[i + 4], WW(i + 4));
|
||||
ROUND(d, e, f, g, h, a, b, c, rk[i + 5], WW(i + 5));
|
||||
ROUND(c, d, e, f, g, h, a, b, rk[i + 6], WW(i + 6));
|
||||
ROUND(b, c, d, e, f, g, h, a, rk[i + 7], WW(i + 7));
|
||||
}
|
||||
|
||||
context->state[0] += a;
|
||||
context->state[1] += b;
|
||||
context->state[2] += c;
|
||||
context->state[3] += d;
|
||||
context->state[4] += e;
|
||||
context->state[5] += f;
|
||||
context->state[6] += g;
|
||||
context->state[7] += h;
|
||||
}
|
||||
|
||||
static void
|
||||
Sha256Process(Sha256Context * context, unsigned char *data, size_t length)
|
||||
{
|
||||
context->length += length;
|
||||
|
||||
if (context->bufLen && context->bufLen + length >= 64)
|
||||
{
|
||||
int len = 64 - context->bufLen;
|
||||
|
||||
memcpy(context->buffer + context->bufLen, data, len);
|
||||
Sha256Chunk(context, context->buffer);
|
||||
data += len;
|
||||
length -= len;
|
||||
context->bufLen = 0;
|
||||
}
|
||||
|
||||
while (length >= 64)
|
||||
{
|
||||
Sha256Chunk(context, data);
|
||||
data += 64;
|
||||
length -= 64;
|
||||
}
|
||||
|
||||
if (length)
|
||||
{
|
||||
memcpy(context->buffer + context->bufLen, data, length);
|
||||
context->bufLen += length;
|
||||
}
|
||||
}
|
||||
|
||||
char *
|
||||
Sha256(char *str)
|
||||
{
|
||||
Sha256Context context;
|
||||
size_t i;
|
||||
unsigned char out[32];
|
||||
char *outStr;
|
||||
|
||||
unsigned char fill[64];
|
||||
UInt32 fillLen;
|
||||
unsigned char buf[8];
|
||||
UInt32 hiLen;
|
||||
UInt32 loLen;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
outStr = Malloc(65);
|
||||
if (!outStr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
context.state[0] = 0x6a09e667;
|
||||
context.state[1] = 0xbb67ae85;
|
||||
context.state[2] = 0x3c6ef372;
|
||||
context.state[3] = 0xa54ff53a;
|
||||
context.state[4] = 0x510e527f;
|
||||
context.state[5] = 0x9b05688c;
|
||||
context.state[6] = 0x1f83d9ab;
|
||||
context.state[7] = 0x5be0cd19;
|
||||
|
||||
context.bufLen = 0;
|
||||
context.length = 0;
|
||||
memset(context.buffer, 0, 64);
|
||||
|
||||
Sha256Process(&context, (unsigned char *) str, strlen(str));
|
||||
|
||||
memset(fill, 0, 64);
|
||||
fill[0] = 0x80;
|
||||
|
||||
fillLen = (context.bufLen < 56) ? 56 - context.bufLen : 120 - context.bufLen;
|
||||
hiLen = (UInt32) (context.length >> 29);
|
||||
loLen = (UInt32) (context.length << 3);
|
||||
|
||||
PUT_UINT32(&buf[0], hiLen);
|
||||
PUT_UINT32(&buf[4], loLen);
|
||||
|
||||
Sha256Process(&context, fill, fillLen);
|
||||
Sha256Process(&context, buf, 8);
|
||||
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
PUT_UINT32(&out[4 * i], context.state[i]);
|
||||
}
|
||||
|
||||
/* Convert to string */
|
||||
for (i = 0; i < 32; i++)
|
||||
{
|
||||
snprintf(outStr + (2 * i), 3, "%02x", out[i]);
|
||||
}
|
||||
|
||||
return outStr;
|
||||
}
|
297
src/Str.c
Normal file
297
src/Str.c
Normal file
|
@ -0,0 +1,297 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Str.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Util.h>
|
||||
#include <Rand.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
char *
|
||||
StrUtf8Encode(unsigned long utf8)
|
||||
{
|
||||
char *str;
|
||||
|
||||
str = Malloc(5 * sizeof(char));
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (utf8 <= 0x7F) /* Plain ASCII */
|
||||
{
|
||||
str[0] = (char) utf8;
|
||||
str[1] = '\0';
|
||||
}
|
||||
else if (utf8 <= 0x07FF) /* 2-byte */
|
||||
{
|
||||
str[0] = (char) (((utf8 >> 6) & 0x1F) | 0xC0);
|
||||
str[1] = (char) (((utf8 >> 0) & 0x3F) | 0x80);
|
||||
str[2] = '\0';
|
||||
}
|
||||
else if (utf8 <= 0xFFFF) /* 3-byte */
|
||||
{
|
||||
str[0] = (char) (((utf8 >> 12) & 0x0F) | 0xE0);
|
||||
str[1] = (char) (((utf8 >> 6) & 0x3F) | 0x80);
|
||||
str[2] = (char) (((utf8 >> 0) & 0x3F) | 0x80);
|
||||
str[3] = '\0';
|
||||
}
|
||||
else if (utf8 <= 0x10FFFF) /* 4-byte */
|
||||
{
|
||||
str[0] = (char) (((utf8 >> 18) & 0x07) | 0xF0);
|
||||
str[1] = (char) (((utf8 >> 12) & 0x3F) | 0x80);
|
||||
str[2] = (char) (((utf8 >> 6) & 0x3F) | 0x80);
|
||||
str[3] = (char) (((utf8 >> 0) & 0x3F) | 0x80);
|
||||
str[4] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Send replacement character */
|
||||
str[0] = (char) 0xEF;
|
||||
str[1] = (char) 0xBF;
|
||||
str[2] = (char) 0xBD;
|
||||
str[3] = '\0';
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
char *
|
||||
StrDuplicate(const char *inStr)
|
||||
{
|
||||
size_t len;
|
||||
char *outStr;
|
||||
|
||||
if (!inStr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
len = strlen(inStr);
|
||||
outStr = Malloc(len + 1); /* For the null terminator */
|
||||
if (!outStr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
strncpy(outStr, inStr, len + 1);
|
||||
|
||||
return outStr;
|
||||
}
|
||||
|
||||
char *
|
||||
StrSubstr(const char *inStr, size_t start, size_t end)
|
||||
{
|
||||
size_t len;
|
||||
size_t i;
|
||||
size_t j;
|
||||
|
||||
char *outStr;
|
||||
|
||||
if (!inStr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (start >= end)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
len = end - start;
|
||||
|
||||
outStr = Malloc(len + 1);
|
||||
if (!outStr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
j = 0;
|
||||
for (i = start; i < end; i++)
|
||||
{
|
||||
if (inStr[i] == '\0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
outStr[j] = inStr[i];
|
||||
j++;
|
||||
}
|
||||
|
||||
outStr[j] = '\0';
|
||||
|
||||
return outStr;
|
||||
}
|
||||
|
||||
char *
|
||||
StrConcat(size_t nStr,...)
|
||||
{
|
||||
va_list argp;
|
||||
char *str;
|
||||
char *strp;
|
||||
size_t strLen = 0;
|
||||
size_t i;
|
||||
|
||||
va_start(argp, nStr);
|
||||
for (i = 0; i < nStr; i++)
|
||||
{
|
||||
char *argStr = va_arg(argp, char *);
|
||||
|
||||
if (argStr)
|
||||
{
|
||||
strLen += strlen(argStr);
|
||||
}
|
||||
}
|
||||
va_end(argp);
|
||||
|
||||
str = Malloc(strLen + 1);
|
||||
strp = str;
|
||||
|
||||
va_start(argp, nStr);
|
||||
|
||||
for (i = 0; i < nStr; i++)
|
||||
{
|
||||
/* Manually copy chars instead of using strcopy() so we don't
|
||||
* have to call strlen() on the strings again, and we aren't
|
||||
* writing useless null chars. */
|
||||
|
||||
char *argStr = va_arg(argp, char *);
|
||||
|
||||
if (argStr)
|
||||
{
|
||||
while (*argStr)
|
||||
{
|
||||
*strp = *argStr;
|
||||
strp++;
|
||||
argStr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
va_end(argp);
|
||||
str[strLen] = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
int
|
||||
StrBlank(const char *str)
|
||||
{
|
||||
int blank = 1;
|
||||
size_t i = 0;
|
||||
|
||||
while (str[i])
|
||||
{
|
||||
blank &= isspace((unsigned char) str[i]);
|
||||
/* No need to continue if we don't have a blank */
|
||||
if (!blank)
|
||||
{
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return blank;
|
||||
}
|
||||
|
||||
char *
|
||||
StrRandom(size_t len)
|
||||
{
|
||||
static const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
char *str;
|
||||
int *nums;
|
||||
size_t i;
|
||||
|
||||
if (!len)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
str = Malloc(len + 1);
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
nums = Malloc(len * sizeof(int));
|
||||
if (!nums)
|
||||
{
|
||||
Free(str);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* TODO: This seems slow. */
|
||||
RandIntN(nums, len, sizeof(charset) - 1);
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
str[i] = charset[nums[i]];
|
||||
}
|
||||
|
||||
Free(nums);
|
||||
str[len] = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
char *
|
||||
StrInt(long i)
|
||||
{
|
||||
char *str;
|
||||
int len;
|
||||
|
||||
len = snprintf(NULL, 0, "%ld", i);
|
||||
str = Malloc(len + 1);
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
snprintf(str, len + 1, "%ld", i);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
int
|
||||
StrEquals(const char *str1, const char *str2)
|
||||
{
|
||||
/* Both strings are NULL, they're equal */
|
||||
if (!str1 && !str2)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* One or the other is NULL, they're not equal */
|
||||
if (!str1 || !str2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Neither are NULL, do a regular string comparison */
|
||||
return strcmp(str1, str2) == 0;
|
||||
}
|
657
src/Stream.c
Normal file
657
src/Stream.c
Normal file
|
@ -0,0 +1,657 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Stream.h>
|
||||
|
||||
#include <Io.h>
|
||||
#include <Memory.h>
|
||||
#include <Util.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef STREAM_RETRIES
|
||||
#define STREAM_RETRIES 10
|
||||
#endif
|
||||
|
||||
#ifndef STREAM_DELAY
|
||||
#define STREAM_DELAY 2
|
||||
#endif
|
||||
|
||||
#define STREAM_EOF (1 << 0)
|
||||
#define STREAM_ERR (1 << 1)
|
||||
#define STREAM_TTY (1 << 2)
|
||||
|
||||
struct Stream
|
||||
{
|
||||
Io *io;
|
||||
|
||||
char *rBuf;
|
||||
size_t rLen;
|
||||
size_t rOff;
|
||||
|
||||
char *wBuf;
|
||||
size_t wLen;
|
||||
|
||||
char *ugBuf;
|
||||
size_t ugSize;
|
||||
size_t ugLen;
|
||||
|
||||
int flags;
|
||||
|
||||
int fd;
|
||||
};
|
||||
|
||||
Stream *
|
||||
StreamIo(Io * io)
|
||||
{
|
||||
Stream *stream;
|
||||
|
||||
if (!io)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
stream = Malloc(sizeof(Stream));
|
||||
if (!stream)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(stream, 0, sizeof(Stream));
|
||||
stream->io = io;
|
||||
stream->fd = -1;
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
Stream *
|
||||
StreamFd(int fd)
|
||||
{
|
||||
Io *io = IoFd(fd);
|
||||
Stream *stream;
|
||||
|
||||
if (!io)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
stream = StreamIo(io);
|
||||