Use Makefiles instead of a custom script (#38)

This pull request also requires the use of the external [Cytoplasm](/Telodendria/Cytoplasm) repository by removing the in-tree copy of Cytoplasm. The increased modularity requires a little more complex build process, but is overall better. Closes #19

The appropriate documentation has been updated. Closes #18

---

Please review the developer certificate of origin:

1. The contribution was created in whole or in part by me, and I have
the right to submit it under the open source licenses of the
Telodendria project; or
1. The contribution is based upon a previous work that, to the best of
my knowledge, is covered under an appropriate open source license and
I have the right under that license to submit that work with
modifications, whether created in whole or in part by me, under the
Telodendria project license; or
1. The contribution was provided directly to me by some other person
who certified (1), (2), or (3), and I have not modified it.
1. I understand and agree that this project and the contribution are
made public and that a record of the contribution—including all
personal information I submit with it—is maintained indefinitely
and may be redistributed consistent with this project or the open
source licenses involved.

- [x] I have read the Telodendria Project development certificate of
origin, and I certify that I have permission to submit this patch
under the conditions specified in it.

Reviewed-on: Telodendria/Telodendria#38
This commit is contained in:
Jordan Bancino 2023-11-01 12:27:45 -04:00
parent 6377689a83
commit 1fee47a628
135 changed files with 492 additions and 21408 deletions

10
.gitignore vendored
View file

@ -1,13 +1,17 @@
# Telodendria .gitignore
build
out
data
Makefile
*-leaked.txt
.env
*.patch
*.orig
*.log
vgcore.*
*.core
Cytoplasm/build
Cytoplasm/out
Cytoplasm/*-leaked.txt
contrib/.vagrant
src/Schema
src/include/Schema

28
Cytoplasm/.indent.pro vendored
View file

@ -1,28 +0,0 @@
-bad
-bap
-bbb
-nbc
-bl
-c36
-cd36
-ncdb
-nce
-ci8
-cli1
-d0
-di1
-ndj
-ei
-fc1
-i4
-ip
-l72
-lc72
-lp
-npcs
-psl
-sc
-nsob
-nut
-nv

View file

@ -1,23 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

View file

@ -1,78 +0,0 @@
# Cytoplasm (libcytoplasm)
Cytoplasm is a general-purpose C library and runtime stub for creating high-level (particularly networked and multi-threaded) C applications. It allows applications to take advantage of the speed, flexibility, and simplicity of the C programming language, while providing helpful code to perform various complex tasks. Cytoplasm provides high-level data structures, a basic logging facility, an HTTP client and server, and more.
Cytoplasm aims not to only do one thing well, but to do many things good enough. The primary target of Cytoplasm is simple, yet higher level C applications that have to perform relatively complex tasks, but don't want to pull in a large number of dependencies.
Cytoplasm is extremely opinionated on the way programs using it are written. It strives to create a comprehensive and tightly-integrated programming environment, while also maintaining C programming correctness. It doesn't do any macro magic or make C look like anything other than C. It is written entirely in C89, and depends only on a POSIX environment. This differentiates it from other general-purpose libraries that often require modern compilers and non-standard language and environment features. Cytoplasm is intended to be extremely portable and simple, while still providing some of the functionality expected in higher-level programming languages in a platform-agnostic manner. In the case of TLS, Cytoplasm wraps low-level TLS libraries to offer a single, unified interface to TLS so that programs do not have to care about the underlying implementation.
Cytoplasm is probably not suitable for embedded programming. It makes liberal use of the heap, and while data structures are designed to conserve memory where possible and practical, minimal memory usage is not really a design goal for Cytoplasm, although Cytoplasm takes care not to use any more memory than it absolutely needs. Cytoplasm also wraps a few standard libraries with additional logic and checking. While this ensures better runtime safetly, this inevitably adds a little overhead.
Originally a part of Telodendria (https://telodendria.io), a Matrix homeserver written in C, Cytoplasm was split off into its own project due to the desire of some Telodendria developers to use Telodendria's code in other projects. Cytoplasm is still a Telodendria project, and is maintained along side of Telodendria itself, even living in the same CVS module, but it is designed specifically to be distributed and used totally independent of Telodendria.
The name "Cytoplasm" was chosen for a few reasons. It plays off the precedent set up by the Matrix organization in naming projects after the parts of a neuron. It also speaks to the function of Cytoplasm. The cytoplasm of a cell is the supporting material. It is what gives the cell its shape, and it facilitates the movement of materials to the other cell parts. Likewise, Cytoplasm aims to provide a support mechanism for C applications that have to perform complex tasks.
Cytoplasm also starts with a C, which I think is a nice touch for C libraries. It's also fun to say and unique enough that searching for "libcytoplasm" should bring you to this project and not some other one.
## Building
If your operating system or software distribution provides a pre-built package of Cytoplasm, you should prefer to use that instead of building it from source.
Cytoplasm aims to have zero dependencies beyond what is mandated by POSIX. You only need the standard math and pthread libraries to build it. TLS support can optionally be enabled by setting the TLS_IMPL environment variable. The supported TLS implementations are as follows:
- OpenSSL
- LibreSSL
If TLS support is not enabled, all APIs that use it should fall back to non-TLS behavior in a sensible manner. For example, if TLS support is not enabled, then the HTTP client API will simply return an error if a TLS connection is requested. Cytoplasm uses a custom build script instead of Make, for the sake of portability. To build everything, just run the script:
$ sh make.sh
This will produce the following out/ directory:
```
out/
lib/
libcytoplasm.so - The Cytoplasm shared library.
libcytoplasm.a - The Cytoplasm static library.
cytoplasm.o - The Cytoplasm runtime stub.
bin/
hdoc - The documentation generator tool.
man/ - All Cytoplasm API documentation.
```
## Usage
Cytoplasm provides the typical .so and .a files, which can be used to link programs with it in the usual way. However, Cytoplasm also provides a minimal runtime environment that is intended to be used with the library. As such, it also provides a runtime stub, which is intended to be linked in with programs using Cytoplasm. This stub is responsible for setting up and tearing down some Cytoplasm APIs. While it isn't required by any means, it makes Cytoplasm a lot easier to use for programmers by abstracting out all of the common logic that most programs will want to use.
Here is the canonical Hello World written with Cytoplasm:
```c
#include <Log.h>
int Main(void)
{
Log(LOG_INFO, "Hello World!");
return 0;
}
```
If this file is Hello.c, then you can compile it by doing something like this:
$ cc -o hello Hello.c cytoplasm.o -lcytoplasm
This command assumes that the runtime stub resides in the current working directory, and that libcytoplasm.so is in your library path. If you're using the version of Cytoplasm installed by your operating system or software distribution, consult the documentation for the location of the runtime stub. It may be located in /usr/local/libexec or some other similar location. If you've built Cytoplasm from source and wish to link to it from there, you may wish to do something like this:
$ export CYTOPLASM=/path/to/Cytoplasm/out/lib
$ cc -o hello Hello.c "${CYTOPLASM}/cytoplasm.o" \
"-L${CYTOPLASM}" -lcytoplasm
As you may have noticed, C programs using Cytoplasm's runtime stub don't write the main() function. Instead, they use Main(). The main() function is provided by the runtime stub. The full form of Main() expected by the stub is as follows:
```c
int Main(Array *args, HashMap *env);
```
The first argument is a Cytoplasm array of the command line arguments, and the second is a Cytoplasm hash map of environment variables. Most linkers will let programs omit the env argument, or both arguments if you don't need either. The return value of Main() is returned to the operating system.
Note that both arguments to Main may be treated like any other array or hash map. However, do not invoke ArrayFree() or HashMapFree() on the passed pointers, because memory is cleaned up after Main() returns.

View file

@ -1,51 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Array.h>
#include <HashMap.h>
#include <Log.h>
int
Main(Array * args, HashMap * env)
{
size_t i;
char *key;
char *val;
Log(LOG_INFO, "Hello World!");
Log(LOG_INFO, "Arguments: %lu", ArraySize(args));
for (i = 0; i < ArraySize(args); i++)
{
Log(LOG_INFO, " [%ld] %s", i, ArrayGet(args, i));
}
Log(LOG_INFO, "Environment:");
while (HashMapIterate(env, &key, (void **) &val))
{
Log(LOG_INFO, " %s = %s", key, val);
}
return 0;
}

View file

@ -1,328 +0,0 @@
#!/usr/bin/env sh
addprefix() {
prefix="$1"
shift
for thing in "$@"; do
echo "${prefix}${thing}"
done
unset prefix
unset thing
}
: "${NAME:=Cytoplasm}"
: "${LIB_NAME:=$(echo ${NAME} | tr '[A-Z]' '[a-z]')}"
: "${VERSION:=0.4.0}"
: "${CVS_TAG:=${NAME}-$(echo ${VERSION} | sed 's/\./_/g')}"
: "${SRC:=src}"
: "${TOOLS:=tools}"
: "${EXAMPLES:=examples}"
: "${BUILD:=build}"
: "${OUT:=out}"
: "${STUB:=RtStub}"
: "${LICENSE:=LICENSE.txt}"
: "${CC:=cc}"
: "${AR:=ar}"
: "${DEFINES:=-D_DEFAULT_SOURCE -DLIB_NAME=\"${NAME}\" -DLIB_VERSION=\"${VERSION}\"}"
: "${INCLUDE:=${SRC}/include}"
: "${CFLAGS:=-Wall -Wextra -pedantic -std=c89 -O3 -pipe}"
: "${LD_EXTRA:=-flto -fdata-sections -ffunction-sections -s -Wl,-gc-sections}"
: "${LDFLAGS:=-lm -pthread}"
if [ "${DEBUG}" = "1" ]; then
CFLAGS="${CFLAGS} -O0 -g"
LD_EXTRA=""
fi
if [ -n "${TLS_IMPL}" ]; then
case "${TLS_IMPL}" in
"LIBRESSL")
TLS_LIBS="-ltls -lcrypto -lssl"
;;
"OPENSSL")
TLS_LIBS="-lcrypto -lssl"
;;
*)
echo "Unrecognized TLS implementation: ${TLS_IMPL}"
echo "Consult Tls.h for supported implementations."
echo "Note that the TLS_ prefix is omitted in TLS_IMPL."
exit 1
;;
esac
DEFINES="${DEFINES} -DTLS_IMPL=TLS_${TLS_IMPL}"
LDFLAGS="${LDFLAGS} ${TLS_LIBS}"
fi
CFLAGS="${CFLAGS} ${DEFINES} $(addprefix -I$(pwd)/ ${INCLUDE})"
LDFLAGS="${LDFLAGS} ${LD_EXTRA}"
# Check the modificiation time of a file. This is used to do
# incremental builds; we only want to rebuild files that have
# have changed.
mod_time() {
if [ -n "$1" ] && [ -f "$1" ]; then
case "$(uname)" in
Linux | CYGWIN_NT* | Haiku)
stat -c %Y "$1"
;;
*BSD | DragonFly | Minix)
stat -f %m "$1"
;;
*)
# Platform unknown, force rebuilding the whole
# project every time.
echo "0"
;;
esac
else
echo "0"
fi
}
# Substitute shell variables in a stream with their actual value
# in this shell.
setsubst() {
SED="/tmp/sed-$RANDOM.txt"
(
set | while IFS='=' read -r var val; do
val=$(echo "$val" | cut -d "'" -f 2- | rev | cut -d "'" -f 2- | rev)
echo "s|\\\${$var}|$val|g"
done
echo "s|\\\${[a-zA-Z_]*}||g"
echo "s|'''|'|g"
) >"$SED"
sed -f "$SED" $@
rm "$SED"
}
recipe_docs() {
export LD_LIBRARY_PATH="${OUT}/lib"
mkdir -p "${OUT}/man/man3"
for header in $(find ${INCLUDE} -name '*.h'); do
basename=$(basename "$header")
man=$(echo "${OUT}/man/man3/$basename" | sed -e 's/\.h$/\.3/')
if [ $(mod_time "$header") -ge $(mod_time "$man") ]; then
echo "DOC $basename"
if ! "${OUT}/bin/hdoc" -D "Os=${NAME}" -i "$header" -o "$man"; then
rm "$man"
exit 1
fi
fi
done
if which makewhatis 2>&1 > /dev/null; then
makewhatis "${OUT}/man"
fi
}
recipe_libs() {
echo "-lm -pthread ${TLS_LIBS}"
}
recipe_build() {
mkdir -p "${BUILD}" "${OUT}/bin" "${OUT}/lib"
cd "${SRC}"
echo "CC = ${CC}"
echo "CFLAGS = ${CFLAGS}"
echo "LDFLAGS = ${LDFLAGS}"
echo
do_rebuild=0
objs=""
for src in $(find . -name '*.c' | cut -d '/' -f 2-); do
obj=$(echo "${BUILD}/$src" | sed -e 's/\.c$/\.o/')
if [ $(basename "$obj" .o) != "${STUB}" ]; then
objs="$objs $obj"
fi
if [ $(mod_time "$src") -ge $(mod_time "../$obj") ]; then
echo "CC $(basename $obj)"
obj_dir=$(dirname "../$obj")
mkdir -p "$obj_dir"
if ! $CC $CFLAGS -fPIC -c -o "../$obj" "$src"; then
exit 1
fi
do_rebuild=1
fi
done
cd ..
if [ $do_rebuild -eq 1 ] || [ ! -f "${OUT}/lib/lib${LIB_NAME}.a" ]; then
echo "AR lib${LIB_NAME}.a"
if ! $AR rcs "${OUT}/lib/lib${LIB_NAME}.a" $objs; then
exit 1
fi
fi
if [ $do_rebuild -eq 1 ] || [ ! -f "${OUT}/lib/lib${LIB_NAME}.so" ]; then
echo "LD lib${LIB_NAME}.so"
if ! $CC -shared -o "${OUT}/lib/lib${LIB_NAME}.so" $objs ${LDFLAGS}; then
exit 1
fi
fi
cp "${BUILD}/${STUB}.o" "${OUT}/lib/${LIB_NAME}.o"
for src in $(find "${TOOLS}" -name '*.c'); do
out=$(basename "$src" .c)
out="${OUT}/bin/$out"
if [ $(mod_time "$src") -ge $(mod_time "$out") ] || [ $do_rebuild -eq 1 ]; then
echo "CC $(basename $out)"
mkdir -p "$(dirname $out)"
if ! $CC $CFLAGS -o "$out" "$src" "${OUT}/lib/${LIB_NAME}.o" "-L${OUT}/lib" "-l${LIB_NAME}" ${LDFLAGS}; then
exit 1
fi
fi
done
recipe_docs
}
recipe_examples() {
for src in $(find "${EXAMPLES}" -name '*.c'); do
out=$(basename "$src" .c)
out="${OUT}/bin/$out"
if [ $(mod_time "$src") -ge $(mod_time "$out") ]; then
echo "CC $(basename $out)"
mkdir -p "$(dirname $out)"
if ! $CC $CFLAGS -o "$out" "$src" "${OUT}/lib/${LIB_NAME}.o" "-L${OUT}/lib" "-l${LIB_NAME}" ${LDFLAGS}; then
exit 1
fi
fi
done
}
recipe_clean() {
rm -r "${BUILD}" "${OUT}"
}
# Update copyright comments in sources and header files.
recipe_license() {
find . -name '*.[ch]' | while IFS= read -r src; do
if [ -t 1 ]; then
printf "LICENSE %s%*c\r" $(basename "$src") "16" " "
fi
srcHeader=$(grep -n -m 1 '^ \*/' "$src" | cut -d ':' -f 1)
head "-n$srcHeader" "$src" |
diff -u -p - "${LICENSE}" |
patch "$src" | grep -v "^Hmm"
done
if [ -t 1 ]; then
printf "%*c\n" "50" " "
fi
}
# Format source code files by running indent(1) on them.
recipe_format() {
find . -name '*.c' | while IFS= read -r src; do
if [ -t 1 ]; then
printf "FMT %s%*c\r" $(basename "$src") "16" " "
fi
if indent "$src"; then
rm $(basename "$src").BAK
fi
done
if [ -t 1 ]; then
printf "%*c\n" "50" " "
fi
}
# Generate a release tarball, checksum and sign it, and push it to
# the web root.
recipe_release() {
# Tag the release at this point in time.
cvs tag "$CVS_TAG"
mkdir -p "${OUT}/release"
cd "${OUT}/release"
# Generate the release tarball.
cvs export "-r$CVS_TAG" "${NAME}"
mv "${NAME}" "${NAME}-v${VERSION}"
tar -czf "${NAME}-v${VERSION}.tar.gz" "${NAME}-v${VERSION}"
rm -r "${NAME}-v${VERSION}"
# Checksum the release tarball.
sha256 "${NAME}-v${VERSION}.tar.gz" > "${NAME}-v${VERSION}.tar.gz.sha256"
# Sign the release tarball.
if [ ! -z "${SIGNIFY_SECRET}" ]; then
signify -S -s "${SIGNIFY_SECRET}" \
-m "${NAME}-v${VERSION}.tar.gz" \
-x "${NAME}-v${VERSION}.tar.gz.sig"
else
echo "Warning: SIGNIFY_SECRET not net."
echo "The built tarball will not be signed."
fi
}
recipe_patch() {
# If the user has not set their MXID, try to deduce one from
# their system.
if [ -z "$MXID" ]; then
MXID="@${USER}:$(hostname)"
fi
# If the user has not set their EDITOR, use a safe default.
# (vi should be available on any POSIX system)
if [ -z "$EDITOR" ]; then
EDITOR=vi
fi
NORMALIZED_MXID=$(echo "$MXID" | sed -e 's/@//g' -e 's/:/-/g')
PATCH_FILE="${NORMALIZED_MXID}_$(date +%s).patch"
# Generate the actual patch file
# Here we write some nice headers, and then do a cvs diff.
(
printf 'From: "%s" <%s>\n' "$DISPLAY_NAME" "$MXID"
echo "Date: $(date)"
echo "Subject: "
echo
cvs -q diff -uNp $PATCHSET | grep -v '^\? '
) >"$PATCH_FILE"
"$EDITOR" "$PATCH_FILE"
echo "$PATCH_FILE"
}
recipe_diff() {
tmp_patch="/tmp/${NAME}-$(date +%s).patch"
cvs -q diff -uNp $PATCHSET >"$tmp_patch"
if [ -z "$PAGER" ]; then
PAGER="less -F"
fi
$PAGER "$tmp_patch"
rm "$tmp_patch"
}
# Execute the user-specified recipes.
for recipe in $@; do
recipe_$recipe
done
# If no recipe was provided, run a build.
if [ -z "$1" ]; then
recipe_build
fi

View file

@ -1,51 +0,0 @@
.Dd $Mdocdate: May 21 2023 $
.Dt HDOC 1
.Os Cytoplasm
.Sh NAME
.Nm hdoc
.Nd Generate documentation from a C header file.
.Sh SYNOPSIS
.Nm
.Op Fl i Ar file
.Op Fl o Ar file
.Op Fl D Ar key=value
.Sh DESCRIPTION
.Nm
is an extremely simple documentation generator that generates
a BSD man page from a specially-formatted C header file.
See
.Xr hdoc 5
for details on the format of this header file.
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl i Ar file
The input C header file to read from. If this option is omitted,
then the standard input is read.
.It Fl o Ar file
The output BSD man page file to write. If this option is omitted,
then the standard output is written.
.It Fl D Ar key=value
Set the register
.Ar key
to
.Ar value .
.Nm
registers are used in various places, and can be set in either
the header file, or on the command line. Setting registers that
should be the same in all headers is best done from the command
line for maintainability purposes, but header-specific values
should be set in the header file itself.
.Pp
Registers are explained in more detail in
.Xr hdoc 5 .
.El
.Sh RETURN VALUE
.Pp
.Nm
returns a success value if the header is well-formed and the
man page is successfully generated. It returns an error code in
all other scenarios.
.Sh SEE ALSO
.Xr hdoc 5 ,
.Xr HeaderParser 3

View file

@ -1,195 +0,0 @@
.Dd $Mdocdate: May 21 2023 $
.Dt HDOC 5
.Os Cytoplasm
.Sh NAME
.Nm hdoc
.Nd C header file format accepted by the documentation generator.
.Sh DESCRIPTION
.Pp
.Nm
uses an extremely primitive parser to generate documentation from
C header files. As such, the format accepted by
.Nm
is rather strict and may not be suitable for other projects beyond
of Cytoplasm. This document describes what
.Nm
considers to be a valid C header file, and how that header file can
be annotated to produce a nicely-formatted man page.
.Pp
At the very top level, a C header is a sequences of tokens that
represent the following:
.Bl -bullet -offset indent
.It
An ANSI C89 comment.
.It
A preprocessor directive.
.It
A typedef declaration.
.It
A constant or other global variable declaration.
.It
A function declaration.
.El
.Pp
Note that global variables and functions
.Em must
be marked with
.Ar extern ,
otherwise the parser will fail to recognize them. This is by
design; a header should make everything extern by default,
because it does not actually implement or declare anything.
.Pp
Preprocessor directives are completely ignored. Regular C
comments are also ignored.
.Nm
is primarily concerned with type declarations, global
variables, and functions. It also inspects specially-formatted
C comments, which are used to annotate these elements of the
header. The format of these comments is described here.
.Pp
There are two types of special comments recognized, the first
of which is called the ``main'' comment block, as it documents
the header itself, not the declarations contained in it. Main
comment blocks also control the behavior of the parser and the
resulting man page by setting registers. The format of the
main comment block, which typically appears only once at the
top of the header, although this is not a requirement, is as
follows:
.Bl -bullet -offset indent
.It
The comment should start with a ``triple star,'' like this:
.Bd -literal -offset indent
/***
*
*/
.Ed
.It
Any lines that start with a ``@'' are parser directives that
set registers. The name of the register to set follows
immediately after the ``@'' sigil, and continues until the first
whitespace. The rest of the line is the value of the register.
A list of registers recognized by
.Nm
is as follows. These registers control the man page output,
and the last value set is the one that is used:
.Bl -tag -width Ds
.It \&Nm
The name register. The name of the man page will be set to the
contents of this register. It defaults to ``Unnmamed.''
.It \&Dd
The date register. The date of the man page will be set to the
contents of this register. If left unset, it defaults to the
current date.
.It \&Os
The operating system register. The Os value, which appears in
the bottom left and right corners of the man page, will be set
to the contents of this register. If left unset, it is not
output.
.It \&Nd
The description register. The description of the man page will
be set to the contents of this register. It defaults to
``No description.''
.It \&Xr
The cross reference register. The SEE ALSO section of the man
page will list the specified cross references, which are to be
separated by a single space. The section shall be omitted,
because it is set automatically to the same section that the
current man page will belong to. This limitation may be removed
in the future.
.El
.Pp
These registers control the parser itself, modifying its
behavior as soon as the appear in the file:
.Bl -tag -width Ds
.It ignore-typedefs
Don't throw an error for an undocumented type declaration.
The value doesn't matter; as soon as this register shows
up, it's set. In most cases, it should not be used, however,
it may be helpful in a few scenarios, such as when there are
multiple typedefs that do the same thing, but are controlled
by preprocessor macros.
.It suppress-warnings
Don't issue a warning for invalid or unrecognized top-level
tokens. They will instead be ignored until the next
recognized top-level token is found. The value doesn't
matter; as soon as this register shows up, it is set. In most
cases, it should not be used, however it may be helpful in a
few scenarios, such as when function declarations are generated
by preprocessor directives and thus don't follow the standard
form.
.El
.It
Any other lines in the main block are output to the DESCRIPTION
section of the main page. This description may contain mdoc
directives in it, as the lines are copied verbatim. If multiple
main comment blocks appear in a single header, their description
lines are appended in the order they appear.
.El
.Pp
Declaration documentation comments are created as follows:
.Bl -bullet -offset indent
.It
The comment should start with a ``double star,'' like this:
.Bd -literal -offset indent
/**
*
*/
.Ed
.It
The contents of the comment are copied verbatim into the output,
so the comment may contain mdoc directives.
.It
The comment must appear before a declaration. If multiple
documentation comments appear before a declaration, the last
one before the declaration is used.
.El
.Pp
The generated man page includes the name and description of the
header, a synopsis section that lists all of the functions in
the header, a description section that contains all the non-register
lines of the main comment blocks, and then all of the documentation
for each function, with the function prototype displayed as a
subsection header, and the documentation displayed under it.
.Sh EXAMPLES
.Pp
Consider the following simple C header:
.Bd -literal -offset indent
#include <stdio.h>
extern void SayHello(FILE *);
.Ed
.Pp
To annotate this header in the manner
.Nm
expects, do something like this:
.Bd -literal -offset indent
/***
* @Nm Hello
* @Nd Say hello.
* @Dd May 17 2023
*
* .Nm
* provides functionality to write hello world messages
* into standard C file descriptors.
*
* @Xr fputs fprintf
*/
#include <stdio.h>
/**
* This function writes "hello world" to the given file
* descriptor.
* .Pp
* There really isn't much more to be said about it.
*/
extern void SayHello(FILE *);
.Ed
.Pp
This example shows how mdoc directives can be placed in
documentation comments. Note that the triple-star comment
documents the header itself, and the double-star comment
documents the type declaration or function definition
below it.
.Sh SEE ALSO
.Xr hdoc 5 ,
.Xr HeaderParser 3

View file

@ -1,118 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Args.h>
#include <Memory.h>
#include <Log.h>
#include <Str.h>
#include <ctype.h>
#include <string.h>
void
ArgParseStateInit(ArgParseState * state)
{
state->optPos = 1;
state->optErr = 1;
state->optInd = 1;
state->optOpt = 0;
state->optArg = NULL;
}
int
ArgParse(ArgParseState * state, Array * args, const char *optStr)
{
const char *arg;
arg = ArrayGet(args, state->optInd);
if (arg && StrEquals(arg, "--"))
{
state->optInd++;
return -1;
}
else if (!arg || arg[0] != '-' || !isalnum((unsigned char) arg[1]))
{
return -1;
}
else
{
const char *opt = strchr(optStr, arg[state->optPos]);
state->optOpt = arg[state->optPos];
if (!opt)
{
if (state->optErr && *optStr != ':')
{
Log(LOG_ERR, "Illegal option: %c", ArrayGet(args, 0), state->optOpt);
}
if (!arg[++state->optPos])
{
state->optInd++;
state->optPos = 1;
}
return '?';
}
else if (opt[1] == ':')
{
if (arg[state->optPos + 1])
{
state->optArg = (char *) arg + state->optPos + 1;
state->optInd++;
state->optPos = 1;
return state->optOpt;
}
else if (ArrayGet(args, state->optInd + 1))
{
state->optArg = (char *) ArrayGet(args, state->optInd + 1);
state->optInd += 2;
state->optPos = 1;
return state->optOpt;
}
else
{
if (state->optErr && *optStr != ':')
{
Log(LOG_ERR, "Option requires an argument: %c", state->optOpt);
}
if (!arg[++state->optPos])
{
state->optInd++;
state->optPos = 1;
}
return *optStr == ':' ? ':' : '?';
}
}
else
{
if (!arg[++state->optPos])
{
state->optInd++;
state->optPos = 1;
}
return state->optOpt;
}
}
}

View file

@ -1,423 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Array.h>
#ifndef ARRAY_BLOCK
#define ARRAY_BLOCK 16
#endif
#include <stddef.h>
#include <Memory.h>
struct Array
{
void **entries; /* An array of void pointers, to
* store any data */
size_t allocated; /* Elements allocated on the heap */
size_t size; /* Elements actually filled */
};
int
ArrayAdd(Array * array, void *value)
{
if (!array)
{
return 0;
}
return ArrayInsert(array, array->size, value);
}
Array *
ArrayCreate(void)
{
Array *array = Malloc(sizeof(Array));
if (!array)
{
return NULL;
}
array->size = 0;
array->allocated = ARRAY_BLOCK;
array->entries = Malloc(sizeof(void *) * ARRAY_BLOCK);
if (!array->entries)
{
Free(array);
return NULL;
}
return array;
}
void *
ArrayDelete(Array * array, size_t index)
{
size_t i;
void *element;
if (!array || array->size <= index)
{
return NULL;
}
element = array->entries[index];
for (i = index; i < array->size - 1; i++)
{
array->entries[i] = array->entries[i + 1];
}
array->size--;
return element;
}
void
ArrayFree(Array * array)
{
if (array)
{
Free(array->entries);
Free(array);
}
}
void *
ArrayGet(Array * array, size_t index)
{
if (!array)
{
return NULL;
}
if (index >= array->size)
{
return NULL;
}
return array->entries[index];
}
extern int
ArrayInsert(Array * array, size_t index, void *value)
{
size_t i;
if (!array || !value || index > array->size)
{
return 0;
}
if (array->size >= array->allocated)
{
void **tmp;
size_t newSize = array->allocated + ARRAY_BLOCK;
tmp = array->entries;
array->entries = Realloc(array->entries,
sizeof(void *) * newSize);
if (!array->entries)
{
array->entries = tmp;
return 0;
}
array->allocated = newSize;
}
for (i = array->size; i > index; i--)
{
array->entries[i] = array->entries[i - 1];
}
array->size++;
array->entries[index] = value;
return 1;
}
extern void *
ArraySet(Array * array, size_t index, void *value)
{
void *oldValue;
if (!value)
{
return ArrayDelete(array, index);
}
if (!array)
{
return NULL;
}
if (index >= array->size)
{
return NULL;
}
oldValue = array->entries[index];
array->entries[index] = value;
return oldValue;
}
size_t
ArraySize(Array * array)
{
if (!array)
{
return 0;
}
return array->size;
}
int
ArrayTrim(Array * array)
{
void **tmp;
if (!array)
{
return 0;
}
tmp = array->entries;
array->entries = Realloc(array->entries,
sizeof(void *) * array->size);
if (!array->entries)
{
array->entries = tmp;
return 0;
}
return 1;
}
static void
ArraySwap(Array * array, size_t i, size_t j)
{
void *p = array->entries[i];
array->entries[i] = array->entries[j];
array->entries[j] = p;
}
static size_t
ArrayPartition(Array * array, size_t low, size_t high, int (*compare) (void *, void *))
{
void *pivot = array->entries[high];
size_t i = low - 1;
size_t j;
for (j = low; j <= high - 1; j++)
{
if (compare(array->entries[j], pivot) < 0)
{
i++;
ArraySwap(array, i, j);
}
}
ArraySwap(array, i + 1, high);
return i + 1;
}
static void
ArrayQuickSort(Array * array, size_t low, size_t high, int (*compare) (void *, void *))
{
if (low < high)
{
size_t pi = ArrayPartition(array, low, high, compare);
ArrayQuickSort(array, low, pi ? pi - 1 : 0, compare);
ArrayQuickSort(array, pi + 1, high, compare);
}
}
void
ArraySort(Array * array, int (*compare) (void *, void *))
{
if (!array)
{
return;
}
ArrayQuickSort(array, 0, array->size - 1, compare);
}
Array *
ArrayUnique(Array * array, int (*compare) (void *, void *))
{
Array *ret;
size_t i;
if (!array)
{
return NULL;
}
ret = ArrayDuplicate(array);
if (!ret)
{
return NULL;
}
if (ArraySize(ret) == 1)
{
/* There can't be any duplicates when there's only 1 value */
return ret;
}
ArraySort(ret, compare);
for (i = 1; i < ArraySize(ret); i++)
{
void *cur = ret->entries[i];
void *prev = ret->entries[i - 1];
if (compare(cur, prev) == 0)
{
/* Remove the duplicate, and put i back where it was. */
ArrayDelete(ret, i--);
}
}
ArrayTrim(ret);
return ret;
}
/* Even though the following operations could be done using only the
* public Array API defined above, I opted for low-level struct
* manipulation because it allows much more efficient copying; we only
* allocate what we for sure need upfront, and don't have to
* re-allocate during the operation. */
Array *
ArrayReverse(Array * array)
{
Array *ret;
size_t i;
size_t size;
if (!array)
{
return NULL;
}
if (!array->size)
{
return ArrayCreate();
}
ret = Malloc(sizeof(Array));
size = array->size;
ret->size = size;
ret->allocated = size;
ret->entries = Malloc(sizeof(void *) * size);
if (!ret->entries)
{
Free(ret);
return NULL;
}
for (i = 0; i < size; i++)
{
ret->entries[i] = array->entries[size - i - 1];
}
return ret;
}
Array *
ArrayFromVarArgs(size_t n, va_list ap)
{
size_t i;
Array *arr = Malloc(sizeof(Array));
if (!arr)
{
return NULL;
}
arr->size = n;
arr->allocated = n;
arr->entries = Malloc(sizeof(void *) * arr->allocated);
if (!arr->entries)
{
Free(arr);
return NULL;
}
for (i = 0; i < n; i++)
{
arr->entries[i] = va_arg(ap, void *);
}
return arr;
}
Array *
ArrayDuplicate(Array * arr)
{
size_t i;
Array *arr2 = Malloc(sizeof(Array));
if (!arr2)
{
return NULL;
}
arr2->size = arr->size;
arr2->allocated = arr->size;
arr2->entries = Malloc(sizeof(void *) * arr->allocated);
if (!arr2->entries)
{
Free(arr2);
return NULL;
}
for (i = 0; i < arr2->size; i++)
{
arr2->entries[i] = arr->entries[i];
}
return arr2;
}

View file

@ -1,244 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Base64.h>
#include <Memory.h>
static const char Base64EncodeMap[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const int Base64DecodeMap[] = {
62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58,
59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51
};
size_t
Base64EncodedSize(size_t inputSize)
{
size_t size = inputSize;
if (inputSize % 3)
{
size += 3 - (inputSize % 3);
}
size /= 3;
size *= 4;
return size;
}
size_t
Base64DecodedSize(const char *base64, size_t len)
{
size_t ret;
size_t i;
if (!base64)
{
return 0;
}
ret = len / 4 * 3;
for (i = len; i > 0; i--)
{
if (base64[i] == '=')
{
ret--;
}
else
{
break;
}
}
return ret;
}
char *
Base64Encode(const char *input, size_t len)
{
char *out;
size_t outLen;
size_t i, j, v;
if (!input || !len)
{
return NULL;
}
outLen = Base64EncodedSize(len);
out = Malloc(outLen + 1);
if (!out)
{
return NULL;
}
out[outLen] = '\0';
for (i = 0, j = 0; i < len; i += 3, j += 4)
{
v = input[i];
v = i + 1 < len ? v << 8 | input[i + 1] : v << 8;
v = i + 2 < len ? v << 8 | input[i + 2] : v << 8;
out[j] = Base64EncodeMap[(v >> 18) & 0x3F];
out[j + 1] = Base64EncodeMap[(v >> 12) & 0x3F];
if (i + 1 < len)
{
out[j + 2] = Base64EncodeMap[(v >> 6) & 0x3F];
}
else
{
out[j + 2] = '=';
}
if (i + 2 < len)
{
out[j + 3] = Base64EncodeMap[v & 0x3F];
}
else
{
out[j + 3] = '=';
}
}
return out;
}
static int
Base64IsValidChar(char c)
{
return (c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c == '+') ||
(c == '/') ||
(c == '=');
}
char *
Base64Decode(const char *input, size_t len)
{
size_t i, j;
int v;
size_t outLen;
char *out;
if (!input)
{
return NULL;
}
outLen = Base64DecodedSize(input, len);
if (len % 4)
{
/* Invalid length; must have incorrect padding */
return NULL;
}
/* Scan for invalid characters. */
for (i = 0; i < len; i++)
{
if (!Base64IsValidChar(input[i]))
{
return NULL;
}
}
out = Malloc(outLen + 1);
if (!out)
{
return NULL;
}
out[outLen] = '\0';
for (i = 0, j = 0; i < len; i += 4, j += 3)
{
v = Base64DecodeMap[input[i] - 43];
v = (v << 6) | Base64DecodeMap[input[i + 1] - 43];
v = input[i + 2] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 2] - 43];
v = input[i + 3] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 3] - 43];
out[j] = (v >> 16) & 0xFF;
if (input[i + 2] != '=')
out[j + 1] = (v >> 8) & 0xFF;
if (input[i + 3] != '=')
out[j + 2] = v & 0xFF;
}
return out;
}
extern void
Base64Unpad(char *base64, size_t length)
{
if (!base64)
{
return;
}
while (base64[length - 1] == '=')
{
length--;
}
base64[length] = '\0';
}
extern int
Base64Pad(char **base64Ptr, size_t length)
{
char *tmp;
size_t newSize;
size_t i;
if (length % 4 == 0)
{
return length; /* Success: no padding needed */
}
newSize = length + (4 - (length % 4));
tmp = Realloc(*base64Ptr, newSize + 100);;
if (!tmp)
{
return 0; /* Memory error */
}
*base64Ptr = tmp;
for (i = length; i < newSize; i++)
{
(*base64Ptr)[i] = '=';
}
(*base64Ptr)[newSize] = '\0';
return newSize;
}

View file

@ -1,252 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Cron.h>
#include <UInt64.h>
#include <Array.h>
#include <Memory.h>
#include <Util.h>
#include <pthread.h>
struct Cron
{
UInt64 tick;
Array *jobs;
pthread_mutex_t lock;
volatile unsigned int stop:1;
pthread_t thread;
};
typedef struct Job
{
UInt64 interval;
UInt64 lastExec;
JobFunc *func;
void *args;
} Job;
static Job *
JobCreate(UInt32 interval, JobFunc * func, void *args)
{
Job *job;
if (!func)
{
return NULL;
}
job = Malloc(sizeof(Job));
if (!job)
{
return NULL;
}
job->interval = UInt64Create(0, interval);
job->lastExec = UInt64Create(0, 0);
job->func = func;
job->args = args;
return job;
}
static void *
CronThread(void *args)
{
Cron *cron = args;
while (!cron->stop)
{
size_t i;
UInt64 ts; /* tick start */
UInt64 te; /* tick end */
pthread_mutex_lock(&cron->lock);
ts = UtilServerTs();
for (i = 0; i < ArraySize(cron->jobs); i++)
{
Job *job = ArrayGet(cron->jobs, i);
if (UInt64Gt(UInt64Sub(ts, job->lastExec), job->interval))
{
job->func(job->args);
job->lastExec = ts;
}
if (UInt64Eq(job->interval, UInt64Create(0, 0)))
{
ArrayDelete(cron->jobs, i);
Free(job);
}
}
te = UtilServerTs();
pthread_mutex_unlock(&cron->lock);
/* Only sleep if the jobs didn't overrun the tick */
if (UInt64Gt(cron->tick, UInt64Sub(te, ts)))
{
const UInt64 microTick = UInt64Create(0, 100);
UInt64 remainingTick = UInt64Sub(cron->tick, UInt64Sub(te, ts));
/* Only sleep for microTick ms at a time because if the job
* scheduler is supposed to stop before the tick is up, we
* don't want to be stuck in a long sleep */
while (UInt64Geq(remainingTick, microTick) && !cron->stop)
{
UtilSleepMillis(microTick);
remainingTick = UInt64Sub(remainingTick, microTick);
}
if (UInt64Neq(remainingTick, UInt64Create(0, 0)) && !cron->stop)
{
UtilSleepMillis(remainingTick);
}
}
}
return NULL;
}
Cron *
CronCreate(UInt32 tick)
{
Cron *cron = Malloc(sizeof(Cron));
if (!cron)
{
return NULL;
}
cron->jobs = ArrayCreate();
if (!cron->jobs)
{
Free(cron);
return NULL;
}
cron->tick = UInt64Create(0, tick);
cron->stop = 1;
pthread_mutex_init(&cron->lock, NULL);
return cron;
}
void
CronOnce(Cron * cron, JobFunc * func, void *args)
{
Job *job;
if (!cron || !func)
{
return;
}
job = JobCreate(0, func, args);
if (!job)
{
return;
}
pthread_mutex_lock(&cron->lock);
ArrayAdd(cron->jobs, job);
pthread_mutex_unlock(&cron->lock);
}
void
CronEvery(Cron * cron, unsigned long interval, JobFunc * func, void *args)
{
Job *job;
if (!cron || !func)
{
return;
}
job = JobCreate(interval, func, args);
if (!job)
{
return;
}
pthread_mutex_lock(&cron->lock);
ArrayAdd(cron->jobs, job);
pthread_mutex_unlock(&cron->lock);
}
void
CronStart(Cron * cron)
{
if (!cron || !cron->stop)
{
return;
}
cron->stop = 0;
pthread_create(&cron->thread, NULL, CronThread, cron);
}
void
CronStop(Cron * cron)
{
if (!cron || cron->stop)
{
return;
}
cron->stop = 1;
pthread_join(cron->thread, NULL);
}
void
CronFree(Cron * cron)
{
size_t i;
if (!cron)
{
return;
}
CronStop(cron);
pthread_mutex_lock(&cron->lock);
for (i = 0; i < ArraySize(cron->jobs); i++)
{
Free(ArrayGet(cron->jobs, i));
}
ArrayFree(cron->jobs);
pthread_mutex_unlock(&cron->lock);
pthread_mutex_destroy(&cron->lock);
Free(cron);
}

View file

@ -1,969 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Db.h>
#include <UInt64.h>
#include <Memory.h>
#include <Json.h>
#include <Util.h>
#include <Str.h>
#include <Stream.h>
#include <Log.h>
#include <sys/types.h>
#include <dirent.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
struct Db
{
char *dir;
pthread_mutex_t lock;
size_t cacheSize;
size_t maxCache;
HashMap *cache;
/*
* The cache uses a double linked list (see DbRef
* below) to know which objects are most and least
* recently used. The following diagram helps me
* know what way all the pointers go, because it
* can get very confusing sometimes. For example,
* there's nothing stopping "next" from pointing to
* least recent, and "prev" from pointing to most
* recent, so hopefully this clarifies the pointer
* terminology used when dealing with the linked
* list:
*
* mostRecent leastRecent
* | prev prev | prev
* +---+ ---> +---+ ---> +---+ ---> NULL
* |ref| |ref| |ref|
* NULL <--- +---+ <--- +---+ <--- +---+
* next next next
*/
DbRef *mostRecent;
DbRef *leastRecent;
};
struct DbRef
{
HashMap *json;
UInt64 ts;
size_t size;
Array *name;
DbRef *prev;
DbRef *next;
int fd;
Stream *stream;
};
static void
StringArrayFree(Array * arr)
{
size_t i;
for (i = 0; i < ArraySize(arr); i++)
{
Free(ArrayGet(arr, i));
}
ArrayFree(arr);
}
static ssize_t DbComputeSize(HashMap *);
static ssize_t
DbComputeSizeOfValue(JsonValue * val)
{
MemoryInfo *a;
ssize_t total = 0;
size_t i;
union
{
char *str;
Array *arr;
} u;
if (!val)
{
return -1;
}
a = MemoryInfoGet(val);
if (a)
{
total += MemoryInfoGetSize(a);
}
switch (JsonValueType(val))
{
case JSON_OBJECT:
total += DbComputeSize(JsonValueAsObject(val));
break;
case JSON_ARRAY:
u.arr = JsonValueAsArray(val);
a = MemoryInfoGet(u.arr);
if (a)
{
total += MemoryInfoGetSize(a);
}
for (i = 0; i < ArraySize(u.arr); i++)
{
total += DbComputeSizeOfValue(ArrayGet(u.arr, i));
}
break;
case JSON_STRING:
u.str = JsonValueAsString(val);
a = MemoryInfoGet(u.str);
if (a)
{
total += MemoryInfoGetSize(a);
}
break;
case JSON_NULL:
case JSON_INTEGER:
case JSON_FLOAT:
case JSON_BOOLEAN:
default:
/* These don't use any extra heap space */
break;
}
return total;
}
static ssize_t
DbComputeSize(HashMap * json)
{
char *key;
JsonValue *val;
MemoryInfo *a;
size_t total;
if (!json)
{
return -1;
}
total = 0;
a = MemoryInfoGet(json);
if (a)
{
total += MemoryInfoGetSize(a);
}
while (HashMapIterate(json, &key, (void **) &val))
{
a = MemoryInfoGet(key);
if (a)
{
total += MemoryInfoGetSize(a);
}
total += DbComputeSizeOfValue(val);
}
return total;
}
static char *
DbHashKey(Array * args)
{
size_t i;
char *str = NULL;
for (i = 0; i < ArraySize(args); i++)
{
char *tmp = StrConcat(2, str, ArrayGet(args, i));
Free(str);
str = tmp;
}
return str;
}
static char *
DbDirName(Db * db, Array * args, size_t strip)
{
size_t i;
char *str = StrConcat(2, db->dir, "/");
for (i = 0; i < ArraySize(args) - strip; i++)
{
char *tmp;
tmp = StrConcat(3, str, ArrayGet(args, i), "/");
Free(str);
str = tmp;
}
return str;
}
static char *
DbFileName(Db * db, Array * args)
{
size_t i;
char *str = StrConcat(2, db->dir, "/");
for (i = 0; i < ArraySize(args); i++)
{
char *tmp;
char *arg = StrDuplicate(ArrayGet(args, i));
size_t j = 0;
/* Sanitize name to prevent directory traversal attacks */
while (arg[j])
{
switch (arg[j])
{
case '/':
arg[j] = '_';
break;
case '.':
arg[j] = '-';
break;
default:
break;
}
j++;
}
tmp = StrConcat(3, str, arg,
(i < ArraySize(args) - 1) ? "/" : ".json");
Free(arg);
Free(str);
str = tmp;
}
return str;
}
static void
DbCacheEvict(Db * db)
{
DbRef *ref = db->leastRecent;
DbRef *tmp;
while (ref && db->cacheSize > db->maxCache)
{
char *hash;
JsonFree(ref->json);
hash = DbHashKey(ref->name);
HashMapDelete(db->cache, hash);
Free(hash);
StringArrayFree(ref->name);
db->cacheSize -= ref->size;
if (ref->next)
{
ref->next->prev = ref->prev;
}
else
{
db->mostRecent = ref->prev;
}
if (ref->prev)
{
ref->prev->next = ref->next;
}
else
{
db->leastRecent = ref->next;
}
tmp = ref->next;
Free(ref);
ref = tmp;
}
}
Db *
DbOpen(char *dir, size_t cache)
{
Db *db;
pthread_mutexattr_t attr;
if (!dir)
{
return NULL;
}
db = Malloc(sizeof(Db));
if (!db)
{
return NULL;
}
db->dir = dir;
db->maxCache = cache;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&db->lock, &attr);
pthread_mutexattr_destroy(&attr);
db->mostRecent = NULL;
db->leastRecent = NULL;
db->cacheSize = 0;
if (db->maxCache)
{
db->cache = HashMapCreate();
if (!db->cache)
{
return NULL;
}
}
else
{
db->cache = NULL;
}
return db;
}
void
DbMaxCacheSet(Db * db, size_t cache)
{
if (!db)
{
return;
}
pthread_mutex_lock(&db->lock);
db->maxCache = cache;
if (db->maxCache && !db->cache)
{
db->cache = HashMapCreate();
db->cacheSize = 0;
}
DbCacheEvict(db);
pthread_mutex_unlock(&db->lock);
}
void
DbClose(Db * db)
{
if (!db)
{
return;
}
pthread_mutex_lock(&db->lock);
DbMaxCacheSet(db, 0);
DbCacheEvict(db);
HashMapFree(db->cache);
pthread_mutex_unlock(&db->lock);
pthread_mutex_destroy(&db->lock);
Free(db);
}
static DbRef *
DbLockFromArr(Db * db, Array * args)
{
char *file;
char *hash;
DbRef *ref;
struct flock lock;
int fd;
Stream *stream;
if (!db || !args)
{
return NULL;
}
ref = NULL;
hash = NULL;
pthread_mutex_lock(&db->lock);
/* Check if the item is in the cache */
hash = DbHashKey(args);
ref = HashMapGet(db->cache, hash);
file = DbFileName(db, args);
fd = open(file, O_RDWR);
if (fd == -1)
{
if (ref)
{
HashMapDelete(db->cache, hash);
JsonFree(ref->json);
StringArrayFree(ref->name);
db->cacheSize -= ref->size;
if (ref->next)
{
ref->next->prev = ref->prev;
}
else
{
db->mostRecent = ref->prev;
}
if (ref->prev)
{
ref->prev->next = ref->next;
}
else
{
db->leastRecent = ref->next;
}
if (!db->leastRecent)
{
db->leastRecent = db->mostRecent;
}
Free(ref);
}
ref = NULL;
goto finish;
}
stream = StreamFd(fd);
lock.l_start = 0;
lock.l_len = 0;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
/* Lock the file on the disk */
if (fcntl(fd, F_SETLK, &lock) < 0)
{
StreamClose(stream);
ref = NULL;
goto finish;
}
if (ref) /* In cache */
{
UInt64 diskTs = UtilLastModified(file);
ref->fd = fd;
ref->stream = stream;
if (UInt64Gt(diskTs, ref->ts))
{
/* File was modified on disk since it was cached */
HashMap *json = JsonDecode(ref->stream);
if (!json)
{
StreamClose(ref->stream);
ref = NULL;
goto finish;
}
JsonFree(ref->json);
ref->json = json;
ref->ts = diskTs;
ref->size = DbComputeSize(ref->json);
}
/* Float this ref to mostRecent */
if (ref->next)
{
ref->next->prev = ref->prev;
if (!ref->prev)
{
db->leastRecent = ref->next;
}
else
{
ref->prev->next = ref->next;
}
ref->prev = db->mostRecent;
ref->next = NULL;
if (db->mostRecent)
{
db->mostRecent->next = ref;
}
db->mostRecent = ref;
}
/* If there is no least recent, this is the only thing in the
* cache, so it is also least recent. */
if (!db->leastRecent)
{
db->leastRecent = ref;
}
/* The file on disk may be larger than what we have in memory,
* which may require items in cache to be evicted. */
DbCacheEvict(db);
}
else
{
Array *name;
size_t i;
/* Not in cache; load from disk */
ref = Malloc(sizeof(DbRef));
if (!ref)
{
StreamClose(stream);
goto finish;
}
ref->json = JsonDecode(stream);
if (!ref->json)
{
Free(ref);
StreamClose(stream);
ref = NULL;
goto finish;
}
ref->fd = fd;
ref->stream = stream;
name = ArrayCreate();
for (i = 0; i < ArraySize(args); i++)
{
ArrayAdd(name, StrDuplicate(ArrayGet(args, i)));
}
ref->name = name;
if (db->cache)
{
ref->ts = UtilServerTs();
ref->size = DbComputeSize(ref->json);
HashMapSet(db->cache, hash, ref);
db->cacheSize += ref->size;
ref->next = NULL;
ref->prev = db->mostRecent;
if (db->mostRecent)
{
db->mostRecent->next = ref;
}
db->mostRecent = ref;
if (!db->leastRecent)
{
db->leastRecent = ref;
}
/* Adding this item to the cache may case it to grow too
* large, requiring some items to be evicted */
DbCacheEvict(db);
}
}
finish:
if (!ref)
{
pthread_mutex_unlock(&db->lock);
}
Free(file);
Free(hash);
return ref;
}
DbRef *
DbCreate(Db * db, size_t nArgs,...)
{
Stream *fp;
char *file;
char *dir;
va_list ap;
Array *args;
DbRef *ret;
if (!db)
{
return NULL;
}
va_start(ap, nArgs);
args = ArrayFromVarArgs(nArgs, ap);
va_end(ap);
if (!args)
{
return NULL;
}
pthread_mutex_lock(&db->lock);
file = DbFileName(db, args);
if (UInt64Neq(UtilLastModified(file), UInt64Create(0, 0)))
{
Free(file);
ArrayFree(args);
pthread_mutex_unlock(&db->lock);
return NULL;
}
dir = DbDirName(db, args, 1);
if (UtilMkdir(dir, 0750) < 0)
{
Free(file);
ArrayFree(args);
Free(dir);
pthread_mutex_unlock(&db->lock);
return NULL;
}
Free(dir);
fp = StreamOpen(file, "w");
Free(file);
if (!fp)
{
ArrayFree(args);
pthread_mutex_unlock(&db->lock);
return NULL;
}
StreamPuts(fp, "{}");
StreamClose(fp);
/* DbLockFromArr() will lock again for us */
pthread_mutex_unlock(&db->lock);
ret = DbLockFromArr(db, args);
ArrayFree(args);
return ret;
}
int
DbDelete(Db * db, size_t nArgs,...)
{
va_list ap;
Array *args;
char *file;
char *hash;
int ret = 1;
DbRef *ref;
if (!db)
{
return 0;
}
va_start(ap, nArgs);
args = ArrayFromVarArgs(nArgs, ap);
va_end(ap);
pthread_mutex_lock(&db->lock);
hash = DbHashKey(args);
file = DbFileName(db, args);
ref = HashMapGet(db->cache, hash);
if (ref)
{
HashMapDelete(db->cache, hash);
JsonFree(ref->json);
StringArrayFree(ref->name);
db->cacheSize -= ref->size;
if (ref->next)
{
ref->next->prev = ref->prev;
}
else
{
db->mostRecent = ref->prev;
}
if (ref->prev)
{
ref->prev->next = ref->next;
}
else
{
db->leastRecent = ref->next;
}
if (!db->leastRecent)
{
db->leastRecent = db->mostRecent;
}
Free(ref);
}
Free(hash);
if (UInt64Neq(UtilLastModified(file), UInt64Create(0, 0)))
{
ret = remove(file) == 0;
}
pthread_mutex_unlock(&db->lock);
ArrayFree(args);
Free(file);
return ret;
}
DbRef *
DbLock(Db * db, size_t nArgs,...)
{
va_list ap;
Array *args;
DbRef *ret;
va_start(ap, nArgs);
args = ArrayFromVarArgs(nArgs, ap);
va_end(ap);
if (!args)
{
return NULL;
}
ret = DbLockFromArr(db, args);
ArrayFree(args);
return ret;
}
int
DbUnlock(Db * db, DbRef * ref)
{
int destroy;
if (!db || !ref)
{
return 0;
}
lseek(ref->fd, 0L, SEEK_SET);
if (ftruncate(ref->fd, 0) < 0)
{
pthread_mutex_unlock(&db->lock);
Log(LOG_ERR, "Failed to truncate file on disk.");
Log(LOG_ERR, "Error on fd %d: %s", ref->fd, strerror(errno));
return 0;
}
JsonEncode(ref->json, ref->stream, JSON_DEFAULT);
StreamClose(ref->stream);
if (db->cache)
{
char *key = DbHashKey(ref->name);
if (HashMapGet(db->cache, key))
{
db->cacheSize -= ref->size;
ref->size = DbComputeSize(ref->json);
db->cacheSize += ref->size;
/* If this ref has grown significantly since we last
* computed its size, it may have filled the cache and
* require some items to be evicted. */
DbCacheEvict(db);
destroy = 0;
}
else
{
destroy = 1;
}
Free(key);
}
else
{
destroy = 1;
}
if (destroy)
{
JsonFree(ref->json);
StringArrayFree(ref->name);
Free(ref);
}
pthread_mutex_unlock(&db->lock);
return 1;
}
int
DbExists(Db * db, size_t nArgs,...)
{
va_list ap;
Array *args;
char *file;
int ret;
va_start(ap, nArgs);
args = ArrayFromVarArgs(nArgs, ap);
va_end(ap);
if (!args)
{
return 0;
}
pthread_mutex_lock(&db->lock);
file = DbFileName(db, args);
ret = UInt64Neq(UtilLastModified(file), UInt64Create(0, 0));
pthread_mutex_unlock(&db->lock);
Free(file);
ArrayFree(args);
return ret;
}
Array *
DbList(Db * db, size_t nArgs,...)
{
Array *result;
Array *path;
DIR *files;
struct dirent *file;
char *dir;
va_list ap;
if (!db || !nArgs)
{
return NULL;
}
result = ArrayCreate();
if (!result)
{
return NULL;
}
va_start(ap, nArgs);
path = ArrayFromVarArgs(nArgs, ap);
dir = DbDirName(db, path, 0);
pthread_mutex_lock(&db->lock);
files = opendir(dir);
if (!files)
{
ArrayFree(path);
ArrayFree(result);
Free(dir);
pthread_mutex_unlock(&db->lock);
return NULL;
}
while ((file = readdir(files)))
{
size_t namlen = strlen(file->d_name);
if (namlen > 5)
{
int nameOffset = namlen - 5;
if (StrEquals(file->d_name + nameOffset, ".json"))
{
file->d_name[nameOffset] = '\0';
ArrayAdd(result, StrDuplicate(file->d_name));
}
}
}
closedir(files);
ArrayFree(path);
Free(dir);
pthread_mutex_unlock(&db->lock);
return result;
}
void
DbListFree(Array * arr)
{
StringArrayFree(arr);
}
HashMap *
DbJson(DbRef * ref)
{
return ref ? ref->json : NULL;
}
int
DbJsonSet(DbRef * ref, HashMap * json)
{
if (!ref || !json)
{
return 0;
}
JsonFree(ref->json);
ref->json = JsonDuplicate(json);
return 1;
}

View file

@ -1,347 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Graph.h>
#include <Memory.h>
#include <string.h>
struct Graph
{
size_t n;
Edge *matrix;
};
Graph *
GraphCreate(size_t n)
{
Graph *g;
if (!n)
{
return NULL;
}
g = Malloc(sizeof(Graph));
if (!g)
{
return NULL;
}
g->n = n;
g->matrix = Malloc((n * n) * sizeof(Edge));
if (!g->matrix)
{
Free(g);
return NULL;
}
memset(g->matrix, 0, (n * n) * sizeof(Edge));
return g;
}
Graph *
GraphCreateWithEdges(size_t n, Edge * matrix)
{
Graph *g = GraphCreate(n);
if (!g)
{
return NULL;
}
memcpy(g->matrix, matrix, (n * n) * sizeof(Edge));
return g;
}
void
GraphFree(Graph * g)
{
if (!g)
{
return;
}
Free(g->matrix);
Free(g);
}
Edge
GraphEdgeGet(Graph * g, Node n1, Node n2)
{
if (n1 >= g->n || n2 >= g->n)
{
return -1;
}
return g->matrix[(g->n * n1) + n2];
}
Edge
GraphEdgeSet(Graph * g, Node n1, Node n2, Edge e)
{
int oldVal;
if (n1 >= g->n || n2 >= g->n)
{
return -1;
}
if (e < 0)
{
return -1;
}
oldVal = g->matrix[(g->n * n1) + n2];
g->matrix[(g->n * n1) + n2] = e;
return oldVal;
}
size_t
GraphCountNodes(Graph * g)
{
return g ? g->n : 0;
}
Node *
GraphBreadthFirstSearch(Graph * G, Node s, size_t * n)
{
Node *visited;
Node *queue;
Node *result;
size_t queueSize;
Node i;
if (!G || !n)
{
return NULL;
}
*n = 0;
result = Malloc(G->n * sizeof(Node));
if (!result)
{
return NULL;
}
if (s >= G->n)
{
Free(result);
return NULL;
}
visited = Malloc(G->n * sizeof(Node));
memset(visited, 0, G->n * sizeof(Node));
queue = Malloc(G->n * sizeof(Node));
queueSize = 0;
visited[s] = 1;
queueSize++;
queue[queueSize - 1] = s;
while (queueSize)
{
s = queue[queueSize - 1];
queueSize--;
result[*n] = s;
(*n)++;
for (i = 0; i < G->n; i++)
{
if (GraphEdgeGet(G, s, i) && !visited[i])
{
visited[i] = 1;
queueSize++;
queue[queueSize - 1] = i;
}
}
}
Free(visited);
Free(queue);
return result;
}
static void
GraphDepthFirstSearchRecursive(Graph * G, Node s, Node * result, size_t * n,
Node * visited)
{
size_t i;
visited[s] = 1;
result[*n] = s;
(*n)++;
for (i = 0; i < G->n; i++)
{
if (GraphEdgeGet(G, s, i) && !visited[i])
{
GraphDepthFirstSearchRecursive(G, i, result, n, visited);
}
}
}
Node *
GraphDepthFirstSearch(Graph * G, Node s, size_t * n)
{
Node *visited;
Node *result;
if (!G || !n)
{
return NULL;
}
result = Malloc(G->n * sizeof(Node));
if (!result)
{
return NULL;
}
*n = 0;
if (s >= G->n)
{
Free(result);
return NULL;
}
visited = Malloc(G->n * sizeof(Node));
memset(visited, 0, G->n * sizeof(Node));
GraphDepthFirstSearchRecursive(G, s, result, n, visited);
Free(visited);
return result;
}
static void
GraphTopologicalSortRecursive(Graph * G, Node s, Node * visited,
Node * stack, size_t * stackSize)
{
size_t i;
visited[s] = 1;
for (i = 0; i < G->n; i++)
{
if (GraphEdgeGet(G, s, i) && !visited[i])
{
GraphTopologicalSortRecursive(G, i, visited, stack, stackSize);
}
}
stack[*stackSize] = s;
(*stackSize)++;
}
Node *
GraphTopologicalSort(Graph * G, size_t * n)
{
Node *visited;
Node *stack;
Node *result;
size_t i;
size_t stackSize;
if (!G || !n)
{
return NULL;
}
*n = 0;
result = Malloc(G->n * sizeof(Node));
if (!result)
{
return NULL;
}
visited = Malloc(G->n * sizeof(Node));
memset(visited, 0, G->n * sizeof(Node));
stack = Malloc(G->n * sizeof(Node));
memset(stack, 0, G->n * sizeof(Node));
stackSize = 0;
for (i = 0; i < G->n; i++)
{
if (!visited[i])
{
GraphTopologicalSortRecursive(G, i, visited, stack, &stackSize);
}
}
Free(visited);
while (stackSize)
{
stackSize--;
result[*n] = stack[stackSize];
(*n)++;
}
Free(stack);
return result;
}
Graph *
GraphTranspose(Graph * G)
{
Graph *T = Malloc(sizeof(Graph));
size_t i, j;
T->n = G->n;
T->matrix = Malloc((G->n * G->n) * sizeof(Edge));
memset(T->matrix, 0, (T->n * T->n) * sizeof(Edge));
for (i = 0; i < G->n; i++)
{
for (j = 0; j < G->n; j++)
{
if (GraphEdgeGet(G, i, j))
{
GraphEdgeSet(T, j, i, GraphEdgeGet(G, i, j));
}
}
}
return T;
}

View file

@ -1,456 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <HashMap.h>
#include <Memory.h>
#include <Str.h>
#include <Array.h>
#include <stddef.h>
#include <string.h>
typedef struct HashMapBucket
{
unsigned long hash;
char *key;
void *value;
} HashMapBucket;
struct HashMap
{
size_t count;
size_t capacity;
HashMapBucket **entries;
unsigned long (*hashFunc) (const char *);
float maxLoad;
size_t iterator;
};
static unsigned long
HashMapHashKey(const char *key)
{
unsigned long hash = 2166136261u;
size_t i = 0;
while (key[i])
{
hash ^= (unsigned char) key[i];
hash *= 16777619;
i++;
}
return hash;
}
static int
HashMapGrow(HashMap * map)
{
size_t oldCapacity;
size_t i;
HashMapBucket **newEntries;
if (!map)
{
return 0;
}
oldCapacity = map->capacity;
map->capacity *= 2;
newEntries = Malloc(map->capacity * sizeof(HashMapBucket *));
if (!newEntries)
{
map->capacity /= 2;
return 0;
}
memset(newEntries, 0, map->capacity * sizeof(HashMapBucket *));
for (i = 0; i < oldCapacity; i++)
{
/* If there is a value here, and it isn't a tombstone */
if (map->entries[i] && map->entries[i]->hash)
{
/* Copy it to the new entries array */
size_t index = map->entries[i]->hash % map->capacity;
for (;;)
{
if (newEntries[index])
{
if (!newEntries[index]->hash)
{
Free(newEntries[index]);
newEntries[index] = map->entries[i];
break;
}
}
else
{
newEntries[index] = map->entries[i];
break;
}
index = (index + 1) % map->capacity;
}
}
else
{
/* Either NULL or a tombstone */
Free(map->entries[i]);
}
}
Free(map->entries);
map->entries = newEntries;
return 1;
}
HashMap *
HashMapCreate(void)
{
HashMap *map = Malloc(sizeof(HashMap));
if (!map)
{
return NULL;
}
map->maxLoad = 0.75;
map->count = 0;
map->capacity = 16;
map->iterator = 0;
map->hashFunc = HashMapHashKey;
map->entries = Malloc(map->capacity * sizeof(HashMapBucket *));
if (!map->entries)
{
Free(map);
return NULL;
}
memset(map->entries, 0, map->capacity * sizeof(HashMapBucket *));
return map;
}
void *
HashMapDelete(HashMap * map, const char *key)
{
unsigned long hash;
size_t index;
if (!map || !key)
{
return NULL;
}
hash = map->hashFunc(key);
index = hash % map->capacity;
for (;;)
{
HashMapBucket *bucket = map->entries[index];
if (!bucket)
{
break;
}
if (bucket->hash == hash)
{
bucket->hash = 0;
return bucket->value;
}
index = (index + 1) % map->capacity;
}
return NULL;
}
void
HashMapFree(HashMap * map)
{
if (map)
{
size_t i;
for (i = 0; i < map->capacity; i++)
{
if (map->entries[i])
{
Free(map->entries[i]->key);
Free(map->entries[i]);
}
}
Free(map->entries);
Free(map);
}
}
void *
HashMapGet(HashMap * map, const char *key)
{
unsigned long hash;
size_t index;
if (!map || !key)
{
return NULL;
}
hash = map->hashFunc(key);
index = hash % map->capacity;
for (;;)
{
HashMapBucket *bucket = map->entries[index];
if (!bucket)
{
break;
}
if (bucket->hash == hash)
{
return bucket->value;
}
index = (index + 1) % map->capacity;
}
return NULL;
}
int
HashMapIterateReentrant(HashMap * map, char **key, void **value, size_t * i)
{
if (!map)
{
return 0;
}
if (*i >= map->capacity)
{
*i = 0;
*key = NULL;
*value = NULL;
return 0;
}
while (*i < map->capacity)
{
HashMapBucket *bucket = map->entries[*i];
*i = *i + 1;
if (bucket && bucket->hash)
{
*key = bucket->key;
*value = bucket->value;
return 1;
}
}
*i = 0;
return 0;
}
int
HashMapIterate(HashMap * map, char **key, void **value)
{
if (!map)
{
return 0;
}
else
{
return HashMapIterateReentrant(map, key, value, &map->iterator);
}
}
void
HashMapMaxLoadSet(HashMap * map, float load)
{
if (!map || (load > 1.0 || load <= 0))
{
return;
}
map->maxLoad = load;
}
void
HashMapFunctionSet(HashMap * map, unsigned long (*hashFunc) (const char *))
{
if (!map || !hashFunc)
{
return;
}
map->hashFunc = hashFunc;
}
void *
HashMapSet(HashMap * map, char *key, void *value)
{
unsigned long hash;
size_t index;
if (!map || !key || !value)
{
return NULL;
}
key = StrDuplicate(key);
if (!key)
{
return NULL;
}
if (map->count + 1 > map->capacity * map->maxLoad)
{
HashMapGrow(map);
}
hash = map->hashFunc(key);
index = hash % map->capacity;
for (;;)
{
HashMapBucket *bucket = map->entries[index];
if (!bucket)
{
bucket = Malloc(sizeof(HashMapBucket));
if (!bucket)
{
break;
}
bucket->hash = hash;
bucket->key = key;
bucket->value = value;
map->entries[index] = bucket;
map->count++;
break;
}
if (!bucket->hash)
{
bucket->hash = hash;
Free(bucket->key);
bucket->key = key;
bucket->value = value;
break;
}
if (bucket->hash == hash)
{
void *oldValue = bucket->value;
Free(bucket->key);
bucket->key = key;
bucket->value = value;
return oldValue;
}
index = (index + 1) % map->capacity;
}
return NULL;
}
void
HashMapIterateFree(char *key, void *value)
{
if (key)
{
Free(key);
}
if (value)
{
Free(value);
}
}
Array *
HashMapKeys(HashMap * map)
{
Array *arr;
char *key;
void *val;
if (!map)
{
return NULL;
}
arr = ArrayCreate();
if (!arr)
{
return NULL;
}
while (HashMapIterate(map, &key, &val))
{
ArrayAdd(arr, key);
}
return arr;
}
Array *
HashMapValues(HashMap * map)
{
Array *arr;
char *key;
void *val;
if (!map)
{
return NULL;
}
arr = ArrayCreate();
if (!arr)
{
return NULL;
}
while (HashMapIterate(map, &key, &val))
{
ArrayAdd(arr, val);
}
return arr;
}

View file

@ -1,665 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <HeaderParser.h>
#include <Memory.h>
#include <Str.h>
#include <string.h>
#include <ctype.h>
static int
HeaderConsumeWhitespace(HeaderExpr * expr)
{
int c;
while (1)
{
c = StreamGetc(expr->state.stream);
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
{
expr->type = HP_EOF;
expr->data.error.msg = "End of stream reached.";
expr->data.error.lineNo = expr->state.lineNo;
break;
}
if (isspace(c))
{
if (c == '\n')
{
expr->state.lineNo++;
}
}
else
{
break;
}
}
return c;
}
static char *
HeaderConsumeWord(HeaderExpr * expr)
{
char *str = Malloc(16 * sizeof(char));
int len = 16;
int i;
int c;
if (!str)
{
return NULL;
}
c = HeaderConsumeWhitespace(expr);
i = 0;
str[i] = c;
i++;
while (!isspace(c = StreamGetc(expr->state.stream)))
{
if (i >= len)
{
len *= 2;
str = Realloc(str, len * sizeof(char));
}
str[i] = c;
i++;
}
if (i >= len)
{
len++;
str = Realloc(str, len * sizeof(char));
}
str[i] = '\0';
if (c != EOF)
{
StreamUngetc(expr->state.stream, c);
}
return str;
}
static char *
HeaderConsumeAlnum(HeaderExpr * expr)
{
char *str = Malloc(16 * sizeof(char));
int len = 16;
int i;
int c;
if (!str)
{
return NULL;
}
c = HeaderConsumeWhitespace(expr);
i = 0;
str[i] = c;
i++;
while (isalnum(c = StreamGetc(expr->state.stream)))
{
if (i >= len)
{
len *= 2;
str = Realloc(str, len * sizeof(char));
}
str[i] = c;
i++;
}
if (i >= len)
{
len++;
str = Realloc(str, len * sizeof(char));
}
str[i] = '\0';
if (c != EOF)
{
StreamUngetc(expr->state.stream, c);
}
return str;
}
static char *
HeaderConsumeArg(HeaderExpr * expr)
{
char *str = Malloc(16 * sizeof(char));
int len = 16;
int i;
int c;
int block = 0;
if (!str)
{
return NULL;
}
c = HeaderConsumeWhitespace(expr);
i = 0;
str[i] = c;
i++;
while (((c = StreamGetc(expr->state.stream)) != ',' && c != ')') || block > 0)
{
if (i >= len)
{
len *= 2;
str = Realloc(str, len * sizeof(char));
}
str[i] = c;
i++;
if (c == '(')
{
block++;
}
else if (c == ')')
{
block--;
}
else if (c == '\n')
{
expr->state.lineNo++;
}
}
if (i >= len)
{
len++;
str = Realloc(str, len * sizeof(char));
}
str[i] = '\0';
if (c != EOF)
{
StreamUngetc(expr->state.stream, c);
}
return str;
}
void
HeaderParse(Stream * stream, HeaderExpr * expr)
{
int c;
if (!expr)
{
return;
}
if (!stream)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "NULL pointer to stream.";
expr->data.error.lineNo = -1;
return;
}
if (expr->type == HP_DECLARATION && expr->data.declaration.args)
{
size_t i;
for (i = 0; i < ArraySize(expr->data.declaration.args); i++)
{
Free(ArrayGet(expr->data.declaration.args, i));
}
ArrayFree(expr->data.declaration.args);
}
expr->state.stream = stream;
if (!expr->state.lineNo)
{
expr->state.lineNo = 1;
}
c = HeaderConsumeWhitespace(expr);
if (StreamEof(stream) || StreamError(stream))
{
expr->type = HP_EOF;
expr->data.error.msg = "End of stream reached.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
if (c == '/')
{
int i = 0;
c = StreamGetc(expr->state.stream);
if (c != '*')
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Expected comment opening.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
expr->type = HP_COMMENT;
while (1)
{
if (i >= HEADER_EXPR_MAX - 1)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Memory limit exceeded while parsing comment.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
c = StreamGetc(expr->state.stream);
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Unterminated comment.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
if (c == '*')
{
c = StreamGetc(expr->state.stream);
if (c == '/')
{
expr->data.text[i] = '\0';
break;
}
else
{
expr->data.text[i] = '*';
i++;
expr->data.text[i] = c;
i++;
if (c == '\n')
{
expr->state.lineNo++;
}
}
}
else
{
expr->data.text[i] = c;
i++;
if (c == '\n')
{
expr->state.lineNo++;
}
}
}
}
else if (c == '#')
{
int i = 0;
char *word;
expr->type = HP_PREPROCESSOR_DIRECTIVE;
expr->data.text[i] = '#';
i++;
word = HeaderConsumeWord(expr);
strncpy(expr->data.text + i, word, HEADER_EXPR_MAX - i - 1);
i += strlen(word);
if (StrEquals(word, "include") ||
StrEquals(word, "undef") ||
StrEquals(word, "ifdef") ||
StrEquals(word, "ifndef"))
{
/* Read one more word */
Free(word);
word = HeaderConsumeWord(expr);
if (i + strlen(word) + 1 >= HEADER_EXPR_MAX)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Memory limit reached parsing preprocessor directive.";
expr->data.error.lineNo = expr->state.lineNo;
}
else
{
strncpy(expr->data.text + i + 1, word, HEADER_EXPR_MAX - i - 1);
expr->data.text[i] = ' ';
}
Free(word);
}
else if (StrEquals(word, "define") ||
StrEquals(word, "if") ||
StrEquals(word, "elif") ||
StrEquals(word, "error"))
{
int pC = 0;
Free(word);
expr->data.text[i] = ' ';
i++;
while (1)
{
if (i >= HEADER_EXPR_MAX - 1)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Memory limit reached parsing preprocessor directive.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
c = StreamGetc(expr->state.stream);
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Unterminated preprocessor directive.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
if (c == '\n')
{
expr->state.lineNo++;
if (pC != '\\')
{
expr->data.text[i] = '\0';
break;
}
}
expr->data.text[i] = c;
i++;
pC = c;
}
}
else if (StrEquals(word, "else") ||
StrEquals(word, "endif"))
{
/* Read no more words, that's the whole directive */
Free(word);
}
else
{
Free(word);
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Unknown preprocessor directive.";
expr->data.error.lineNo = expr->state.lineNo;
}
}
else
{
char *word;
StreamUngetc(expr->state.stream, c);
word = HeaderConsumeWord(expr);
if (StrEquals(word, "typedef"))
{
int block = 0;
int i = 0;
expr->type = HP_TYPEDEF;
strncpy(expr->data.text, word, HEADER_EXPR_MAX - 1);
i += strlen(word);
expr->data.text[i] = ' ';
i++;
while (1)
{
if (i >= HEADER_EXPR_MAX - 1)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Memory limit exceeded while parsing typedef.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
c = StreamGetc(expr->state.stream);
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Unterminated typedef.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
expr->data.text[i] = c;
i++;
if (c == '{')
{
block++;
}
else if (c == '}')
{
block--;
}
else if (c == '\n')
{
expr->state.lineNo++;
}
if (block <= 0 && c == ';')
{
expr->data.text[i] = '\0';
break;
}
}
}
else if (StrEquals(word, "extern"))
{
int wordLimit = sizeof(expr->data.declaration.returnType) - 8;
int wordLen;
Free(word);
word = HeaderConsumeWord(expr);
wordLen = strlen(word);
if (wordLen > wordLimit)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Return of declaration exceeds length limit.";
expr->data.error.lineNo = expr->state.lineNo;
}
else
{
int i = wordLen;
expr->type = HP_GLOBAL;
strncpy(expr->data.global.type, word, wordLimit);
if (StrEquals(word, "struct") ||
StrEquals(word, "enum") ||
StrEquals(word, "const") ||
StrEquals(word, "unsigned"))
{
Free(word);
word = HeaderConsumeWord(expr);
wordLen = strlen(word);
expr->data.global.type[i] = ' ';
strncpy(expr->data.global.type + i + 1, word, wordLen + 1);
i += wordLen + 1;
}
Free(word);
c = HeaderConsumeWhitespace(expr);
if (c == '*')
{
expr->data.global.type[i] = ' ';
i++;
expr->data.global.type[i] = '*';
i++;
while ((c = HeaderConsumeWhitespace(expr)) == '*')
{
expr->data.global.type[i] = c;
i++;
}
}
StreamUngetc(expr->state.stream, c);
word = HeaderConsumeAlnum(expr);
wordLen = strlen(word);
wordLimit = sizeof(expr->data.declaration.name) - 1;
if (wordLen > wordLimit)
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Function name too long.";
expr->data.error.lineNo = expr->state.lineNo;
}
else
{
strncpy(expr->data.global.name, word, wordLimit);
Free(word);
word = NULL;
c = HeaderConsumeWhitespace(expr);
if (c == ';')
{
/* That's the end of the global. */
}
else if (c == '[')
{
/* Looks like we have an array. Slurp all the
* dimensions */
int i = wordLen;
expr->data.global.name[i] = '[';
i++;
while (1)
{
if (i >= HEADER_EXPR_MAX - wordLen)
{
expr->type = HP_PARSE_ERROR;
expr->data.error.msg = "Memory limit exceeded while parsing global array.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
c = StreamGetc(expr->state.stream);
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Unterminated global array.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
if (c == ';')
{
expr->data.global.name[i] = '\0';
break;
}
else
{
expr->data.global.name[i] = c;
i++;
}
}
}
else if (c == '(')
{
expr->type = HP_DECLARATION;
expr->data.declaration.args = ArrayCreate();
do
{
word = HeaderConsumeArg(expr);
ArrayAdd(expr->data.declaration.args, word);
word = NULL;
}
while ((!StreamEof(expr->state.stream)) && ((c = HeaderConsumeWhitespace(expr)) != ')'));
if (StreamEof(expr->state.stream))
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "End of file reached before ')'.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
c = HeaderConsumeWhitespace(expr);
if (c != ';')
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Expected ';'.";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
}
else
{
expr->type = HP_SYNTAX_ERROR;
expr->data.error.msg = "Expected ';', '[', or '('";
expr->data.error.lineNo = expr->state.lineNo;
return;
}
}
}
}
else
{
/* Cope with preprocessor macro expansions at the top
* level. */
expr->type = HP_UNKNOWN;
strncpy(expr->data.text, word, HEADER_EXPR_MAX - 1);
}
Free(word);
}
}

View file

@ -1,642 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Http.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <Memory.h>
#include <HashMap.h>
#include <Util.h>
#include <Str.h>
#ifndef CYTOPLASM_STRING_CHUNK
#define CYTOPLASM_STRING_CHUNK 64
#endif
const char *
HttpRequestMethodToString(const HttpRequestMethod method)
{
switch (method)
{
case HTTP_GET:
return "GET";
case HTTP_HEAD:
return "HEAD";
case HTTP_POST:
return "POST";
case HTTP_PUT:
return "PUT";
case HTTP_DELETE:
return "DELETE";
case HTTP_CONNECT:
return "CONNECT";
case HTTP_OPTIONS:
return "OPTIONS";
case HTTP_TRACE:
return "TRACE";
case HTTP_PATCH:
return "PATCH";
default:
return NULL;
}
}
HttpRequestMethod
HttpRequestMethodFromString(const char *str)
{
if (StrEquals(str, "GET"))
{
return HTTP_GET;
}
if (StrEquals(str, "HEAD"))
{
return HTTP_HEAD;
}
if (StrEquals(str, "POST"))
{
return HTTP_POST;
}
if (StrEquals(str, "PUT"))
{
return HTTP_PUT;
}
if (StrEquals(str, "DELETE"))
{
return HTTP_DELETE;
}
if (StrEquals(str, "CONNECT"))
{
return HTTP_CONNECT;
}
if (StrEquals(str, "OPTIONS"))
{
return HTTP_OPTIONS;
}
if (StrEquals(str, "TRACE"))
{
return HTTP_TRACE;
}
if (StrEquals(str, "PATCH"))
{
return HTTP_PATCH;
}
return HTTP_METHOD_UNKNOWN;
}
const char *
HttpStatusToString(const HttpStatus status)
{
switch (status)
{
case HTTP_CONTINUE:
return "Continue";
case HTTP_SWITCHING_PROTOCOLS:
return "Switching Protocols";
case HTTP_EARLY_HINTS:
return "Early Hints";
case HTTP_OK:
return "Ok";
case HTTP_CREATED:
return "Created";
case HTTP_ACCEPTED:
return "Accepted";
case HTTP_NON_AUTHORITATIVE_INFORMATION:
return "Non-Authoritative Information";
case HTTP_NO_CONTENT:
return "No Content";
case HTTP_RESET_CONTENT:
return "Reset Content";
case HTTP_PARTIAL_CONTENT:
return "Partial Content";
case HTTP_MULTIPLE_CHOICES:
return "Multiple Choices";
case HTTP_MOVED_PERMANENTLY:
return "Moved Permanently";
case HTTP_FOUND:
return "Found";
case HTTP_SEE_OTHER:
return "See Other";
case HTTP_NOT_MODIFIED:
return "Not Modified";
case HTTP_TEMPORARY_REDIRECT:
return "Temporary Redirect";
case HTTP_PERMANENT_REDIRECT:
return "Permanent Redirect";
case HTTP_BAD_REQUEST:
return "Bad Request";
case HTTP_UNAUTHORIZED:
return "Unauthorized";
case HTTP_FORBIDDEN:
return "Forbidden";
case HTTP_NOT_FOUND:
return "Not Found";
case HTTP_METHOD_NOT_ALLOWED:
return "Method Not Allowed";
case HTTP_NOT_ACCEPTABLE:
return "Not Acceptable";
case HTTP_PROXY_AUTH_REQUIRED:
return "Proxy Authentication Required";
case HTTP_REQUEST_TIMEOUT:
return "Request Timeout";
case HTTP_CONFLICT:
return "Conflict";
case HTTP_GONE:
return "Gone";
case HTTP_LENGTH_REQUIRED:
return "Length Required";
case HTTP_PRECONDITION_FAILED:
return "Precondition Failed";
case HTTP_PAYLOAD_TOO_LARGE:
return "Payload Too Large";
case HTTP_URI_TOO_LONG:
return "URI Too Long";
case HTTP_UNSUPPORTED_MEDIA_TYPE:
return "Unsupported Media Type";
case HTTP_RANGE_NOT_SATISFIABLE:
return "Range Not Satisfiable";
case HTTP_EXPECTATION_FAILED:
return "Expectation Failed";
case HTTP_TEAPOT:
return "I'm a Teapot";
case HTTP_UPGRADE_REQUIRED:
return "Upgrade Required";
case HTTP_PRECONDITION_REQUIRED:
return "Precondition Required";
case HTTP_TOO_MANY_REQUESTS:
return "Too Many Requests";
case HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE:
return "Request Header Fields Too Large";
case HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
return "Unavailable For Legal Reasons";
case HTTP_INTERNAL_SERVER_ERROR:
return "Internal Server Error";
case HTTP_NOT_IMPLEMENTED:
return "Not Implemented";
case HTTP_BAD_GATEWAY:
return "Bad Gateway";
case HTTP_SERVICE_UNAVAILABLE:
return "Service Unavailable";
case HTTP_GATEWAY_TIMEOUT:
return "Gateway Timeout";
case HTTP_VERSION_NOT_SUPPORTED:
return "Version Not Supported";
case HTTP_VARIANT_ALSO_NEGOTIATES:
return "Variant Also Negotiates";
case HTTP_NOT_EXTENDED:
return "Not Extended";
case HTTP_NETWORK_AUTH_REQUIRED:
return "Network Authentication Required";
default:
return NULL;
}
}
char *
HttpUrlEncode(char *str)
{
size_t size;
size_t len;
char *encoded;
if (!str)
{
return NULL;
}
size = CYTOPLASM_STRING_CHUNK;
len = 0;
encoded = Malloc(size);
if (!encoded)
{
return NULL;
}
while (*str)
{
char c = *str;
if (len >= size - 4)
{
char *tmp;
size += CYTOPLASM_STRING_CHUNK;
tmp = Realloc(encoded, size);
if (!tmp)
{
Free(encoded);
return NULL;
}
encoded = tmp;
}
/* Control characters and extended characters */
if (c <= 0x1F || c >= 0x7F)
{
goto percentEncode;
}
/* Reserved and unsafe characters */
switch (c)
{
case '$':
case '&':
case '+':
case ',':
case '/':
case ':':
case ';':
case '=':
case '?':
case '@':
case ' ':
case '"':
case '<':
case '>':
case '#':
case '%':
case '{':
case '}':
case '|':
case '\\':
case '^':
case '~':
case '[':
case ']':
case '`':
goto percentEncode;
break;
default:
encoded[len] = c;
len++;
str++;
continue;
}
percentEncode:
encoded[len] = '%';
len++;
snprintf(encoded + len, 3, "%2X", c);
len += 2;
str++;
}
encoded[len] = '\0';
return encoded;
}
char *
HttpUrlDecode(char *str)
{
size_t i;
size_t inputLen;
char *decoded;
if (!str)
{
return NULL;
}
i = 0;
inputLen = strlen(str);
decoded = Malloc(inputLen + 1);
if (!decoded)
{
return NULL;
}
while (*str)
{
char c = *str;
if (c == '%')
{
unsigned int d;
str++;
if (sscanf(str, "%2X", &d) != 1)
{
/* Decoding error */
Free(decoded);
return NULL;
}
if (!d)
{
/* Null character given, don't put that in the string. */
continue;
}
c = (char) d;
str++;
}
decoded[i] = c;
i++;
str++;
}
decoded[i] = '\0';
return decoded;
}
HashMap *
HttpParamDecode(char *in)
{
HashMap *params;
if (!in)
{
return NULL;
}
params = HashMapCreate();
if (!params)
{
return NULL;
}
while (*in)
{
char *buf;
size_t allocated;
size_t len;
char *decKey;
char *decVal;
/* Read in key */
allocated = CYTOPLASM_STRING_CHUNK;
buf = Malloc(allocated);
len = 0;
while (*in && *in != '=')
{
if (len >= allocated - 1)
{
allocated += CYTOPLASM_STRING_CHUNK;
buf = Realloc(buf, allocated);
}
buf[len] = *in;
len++;
in++;
}
buf[len] = '\0';
/* Sanity check */
if (*in != '=')
{
/* Malformed param */
Free(buf);
HashMapFree(params);
return NULL;
}
in++;
/* Decode key */
decKey = HttpUrlDecode(buf);
Free(buf);
if (!decKey)
{
/* Decoding error */
HashMapFree(params);
return NULL;
}
/* Read in value */
allocated = CYTOPLASM_STRING_CHUNK;
buf = Malloc(allocated);
len = 0;
while (*in && *in != '&')
{
if (len >= allocated - 1)
{
allocated += CYTOPLASM_STRING_CHUNK;
buf = Realloc(buf, allocated);
}
buf[len] = *in;
len++;
in++;
}
buf[len] = '\0';
/* Decode value */
decVal = HttpUrlDecode(buf);
Free(buf);
if (!decVal)
{
/* Decoding error */
HashMapFree(params);
return NULL;
}
buf = HashMapSet(params, decKey, decVal);
if (buf)
{
Free(buf);
}
Free(decKey);
if (*in == '&')
{
in++;
continue;
}
else
{
break;
}
}
return params;
}
char *
HttpParamEncode(HashMap * params)
{
char *key;
char *val;
char *out = NULL;
if (!params || !out)
{
return NULL;
}
while (HashMapIterate(params, &key, (void *) &val))
{
char *encKey;
char *encVal;
encKey = HttpUrlEncode(key);
encVal = HttpUrlEncode(val);
if (!encKey || !encVal)
{
/* Memory error */
Free(encKey);
Free(encVal);
return NULL;
}
/* TODO */
Free(encKey);
Free(encVal);
}
return out;
}
HashMap *
HttpParseHeaders(Stream * fp)
{
HashMap *headers;
char *line;
ssize_t lineLen;
size_t lineSize;
char *headerKey;
char *headerValue;
if (!fp)
{
return NULL;
}
headers = HashMapCreate();
if (!headers)
{
return NULL;
}
line = NULL;
lineLen = 0;
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) != -1)
{
char *headerPtr;
ssize_t i;
size_t len;
if (StrEquals(line, "\r\n") || StrEquals(line, "\n"))
{
break;
}
for (i = 0; i < lineLen; i++)
{
if (line[i] == ':')
{
line[i] = '\0';
break;
}
line[i] = tolower((unsigned char) line[i]);
}
len = i + 1;
headerKey = Malloc(len * sizeof(char));
if (!headerKey)
{
goto error;
}
strncpy(headerKey, line, len);
headerPtr = line + i + 1;
while (isspace((unsigned char) *headerPtr))
{
headerPtr++;
}
for (i = lineLen - 1; i > (line + lineLen) - headerPtr; i--)
{
if (!isspace((unsigned char) line[i]))
{
break;
}
line[i] = '\0';
}
len = strlen(headerPtr) + 1;
headerValue = Malloc(len * sizeof(char));
if (!headerValue)
{
Free(headerKey);
goto error;
}
strncpy(headerValue, headerPtr, len);
HashMapSet(headers, headerKey, headerValue);
Free(headerKey);
}
Free(line);
return headers;
error:
Free(line);
while (HashMapIterate(headers, &headerKey, (void **) &headerValue))
{
Free(headerValue);
}
HashMapFree(headers);
return NULL;
}

View file

@ -1,302 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <HttpClient.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <Http.h>
#include <Memory.h>
#include <Util.h>
#include <Tls.h>
struct HttpClientContext
{
HashMap *responseHeaders;
Stream *stream;
};
HttpClientContext *
HttpRequest(HttpRequestMethod method, int flags, unsigned short port, char *host, char *path)
{
HttpClientContext *context;
int sd = -1;
struct addrinfo hints, *res, *res0;
int error;
char serv[8];
if (!method || !host || !path)
{
return NULL;
}
#ifndef TLS_IMPL
if (flags & HTTP_FLAG_TLS)
{
return NULL;
}
#endif
if (!port)
{
if (flags & HTTP_FLAG_TLS)
{
strcpy(serv, "https");
}
else
{
strcpy(serv, "www");
}
}
else
{
snprintf(serv, sizeof(serv), "%hu", port);
}
context = Malloc(sizeof(HttpClientContext));
if (!context)
{
return NULL;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(host, serv, &hints, &res0);
if (error)
{
Free(context);
return NULL;
}
for (res = res0; res; res = res->ai_next)
{
sd = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (sd < 0)
{
continue;
}
if (connect(sd, res->ai_addr, res->ai_addrlen) < 0)
{
close(sd);
sd = -1;
continue;
}
break;
}
if (sd < 0)
{
Free(context);
return NULL;
}
freeaddrinfo(res0);
#ifdef TLS_IMPL
if (flags & HTTP_FLAG_TLS)
{
context->stream = TlsClientStream(sd, host);
}
else
{
context->stream = StreamFd(sd);
}
#else
context->stream = StreamFd(sd);
#endif
if (!context->stream)
{
Free(context);
close(sd);
return NULL;
}
StreamPrintf(context->stream, "%s %s HTTP/1.0\r\n",
HttpRequestMethodToString(method), path);
HttpRequestHeader(context, "Connection", "close");
HttpRequestHeader(context, "User-Agent", LIB_NAME "/" LIB_VERSION);
HttpRequestHeader(context, "Host", host);
return context;
}
void
HttpRequestHeader(HttpClientContext * context, char *key, char *val)
{
if (!context || !key || !val)
{
return;
}
StreamPrintf(context->stream, "%s: %s\r\n", key, val);
}
void
HttpRequestSendHeaders(HttpClientContext * context)
{
if (!context)
{
return;
}
StreamPuts(context->stream, "\r\n");
StreamFlush(context->stream);
}
HttpStatus
HttpRequestSend(HttpClientContext * context)
{
HttpStatus status = HTTP_STATUS_UNKNOWN;
char *line = NULL;
ssize_t lineLen;
size_t lineSize = 0;
char *tmp;
if (!context)
{
goto finish;
}
StreamFlush(context->stream);
lineLen = UtilGetLine(&line, &lineSize, context->stream);
while (lineLen == -1 && errno == EAGAIN)
{
StreamClearError(context->stream);
lineLen = UtilGetLine(&line, &lineSize, context->stream);
}
if (lineLen == -1)
{
goto finish;
}
/* Line must contain at least "HTTP/x.x xxx" */
if (lineLen < 12)
{
goto finish;
}
if (!(strncmp(line, "HTTP/1.0", 8) == 0 ||
strncmp(line, "HTTP/1.1", 8) == 0))
{
goto finish;
}
tmp = line + 9;
while (isspace((unsigned char) *tmp) && *tmp != '\0')
{
tmp++;
}
if (!*tmp)
{
goto finish;
}
status = atoi(tmp);
if (!status)
{
status = HTTP_STATUS_UNKNOWN;
goto finish;
}
context->responseHeaders = HttpParseHeaders(context->stream);
if (!context->responseHeaders)
{
status = HTTP_STATUS_UNKNOWN;
goto finish;
}
finish:
Free(line);
return status;
}
HashMap *
HttpResponseHeaders(HttpClientContext * context)
{
if (!context)
{
return NULL;
}
return context->responseHeaders;
}
Stream *
HttpClientStream(HttpClientContext * context)
{
if (!context)
{
return NULL;
}
return context->stream;
}
void
HttpClientContextFree(HttpClientContext * context)
{
char *key;
void *val;
if (!context)
{
return;
}
while (HashMapIterate(context->responseHeaders, &key, &val))
{
Free(val);
}
HashMapFree(context->responseHeaders);
StreamClose(context->stream);
Free(context);
}

View file

@ -1,301 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <HttpRouter.h>
#include <Memory.h>
#include <HashMap.h>
#include <Str.h>
#include <regex.h>
#include <string.h>
#define REG_FLAGS (REG_EXTENDED)
#define REG_MAX_SUB 8
typedef struct RouteNode
{
HttpRouteFunc *exec;
HashMap *children;
regex_t regex;
} RouteNode;
struct HttpRouter
{
RouteNode *root;
};
static RouteNode *
RouteNodeCreate(char *regex, HttpRouteFunc * exec)
{
RouteNode *node;
if (!regex)
{
return NULL;
}
node = Malloc(sizeof(RouteNode));
if (!node)
{
return NULL;
}
node->children = HashMapCreate();
if (!node->children)
{
Free(node);
return NULL;
}
/* Force the regex to match the entire path part exactly. */
regex = StrConcat(3, "^", regex, "$");
if (!regex)
{
Free(node);
return NULL;
}
if (regcomp(&node->regex, regex, REG_FLAGS) != 0)
{
HashMapFree(node->children);
Free(node);
Free(regex);
return NULL;
}
node->exec = exec;
Free(regex);
return node;
}
static void
RouteNodeFree(RouteNode * node)
{
char *key;
RouteNode *val;
if (!node)
{
return;
}
while (HashMapIterate(node->children, &key, (void **) &val))
{
RouteNodeFree(val);
}
HashMapFree(node->children);
regfree(&node->regex);
Free(node);
}
HttpRouter *
HttpRouterCreate(void)
{
HttpRouter *router = Malloc(sizeof(HttpRouter));
if (!router)
{
return NULL;
}
router->root = RouteNodeCreate("/", NULL);
return router;
}
void
HttpRouterFree(HttpRouter * router)
{
if (!router)
{
return;
}
RouteNodeFree(router->root);
Free(router);
}
int
HttpRouterAdd(HttpRouter * router, char *regPath, HttpRouteFunc * exec)
{
RouteNode *node;
char *pathPart;
char *tmp;
if (!router || !regPath || !exec)
{
return 0;
}
if (StrEquals(regPath, "/"))
{
router->root->exec = exec;
return 1;
}
regPath = StrDuplicate(regPath);
if (!regPath)
{
return 0;
}
tmp = regPath;
node = router->root;
while ((pathPart = strtok_r(tmp, "/", &tmp)))
{
RouteNode *tNode = HashMapGet(node->children, pathPart);
if (!tNode)
{
tNode = RouteNodeCreate(pathPart, NULL);
RouteNodeFree(HashMapSet(node->children, pathPart, tNode));
}
node = tNode;
}
node->exec = exec;
Free(regPath);
return 1;
}
int
HttpRouterRoute(HttpRouter * router, char *path, void *args, void **ret)
{
RouteNode *node;
char *pathPart;
char *tmp;
HttpRouteFunc *exec = NULL;
Array *matches = NULL;
size_t i;
int retval;
if (!router || !path)
{
return 0;
}
matches = ArrayCreate();
if (!matches)
{
return 0;
}
node = router->root;
if (StrEquals(path, "/"))
{
exec = node->exec;
}
else
{
path = StrDuplicate(path);
tmp = path;
while ((pathPart = strtok_r(tmp, "/", &tmp)))
{
char *key;
RouteNode *val = NULL;
regmatch_t pmatch[REG_MAX_SUB];
i = 0;
while (HashMapIterateReentrant(node->children, &key, (void **) &val, &i))
{
if (regexec(&val->regex, pathPart, REG_MAX_SUB, pmatch, 0) == 0)
{
break;
}
val = NULL;
}
if (!val)
{
exec = NULL;
break;
}
node = val;
exec = node->exec;
/* If we want to pass an arg, the match must be in parens */
if (val->regex.re_nsub)
{
/* pmatch[0] is the whole string, not the first
* subexpression */
char *substr;
regmatch_t cpmatch;
for (i = 1; i < REG_MAX_SUB; i++)
{
cpmatch = pmatch[i];
substr = StrSubstr(pathPart, cpmatch.rm_so, cpmatch.rm_eo);
if (pmatch[i].rm_so == -1)
{
break;
}
ArrayAdd(matches, substr);
}
}
}
Free(path);
}
if (!exec)
{
retval = 0;
goto finish;
}
if (ret)
{
*ret = exec(matches, args);
}
else
{
exec(matches, args);
}
retval = 1;
finish:
for (i = 0; i < ArraySize(matches); i++)
{
Free(ArrayGet(matches, i));
}
ArrayFree(matches);
return retval;
}

View file

@ -1,755 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <HttpServer.h>
#include <Memory.h>
#include <Queue.h>
#include <Array.h>
#include <Util.h>
#include <Tls.h>
#include <Log.h>
#include <Str.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <string.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
static const char ENABLE = 1;
struct HttpServer
{
HttpServerConfig config;
int sd;
pthread_t socketThread;
volatile unsigned int stop:1;
volatile unsigned int isRunning:1;
Queue *connQueue;
pthread_mutex_t connQueueMutex;
Array *threadPool;
};
struct HttpServerContext
{
HashMap *requestHeaders;
HttpRequestMethod requestMethod;
char *requestPath;
HashMap *requestParams;
HashMap *responseHeaders;
HttpStatus responseStatus;
Stream *stream;
};
typedef struct HttpServerWorkerThreadArgs
{
HttpServer *server;
pthread_t thread;
} HttpServerWorkerThreadArgs;
static HttpServerContext *
HttpServerContextCreate(HttpRequestMethod requestMethod,
char *requestPath, HashMap * requestParams, Stream * stream)
{
HttpServerContext *c;
c = Malloc(sizeof(HttpServerContext));
if (!c)
{
return NULL;
}
c->responseHeaders = HashMapCreate();
if (!c->responseHeaders)
{
Free(c->requestHeaders);
Free(c);
return NULL;
}
c->requestMethod = requestMethod;
c->requestPath = requestPath;
c->requestParams = requestParams;
c->stream = stream;
c->responseStatus = HTTP_OK;
return c;
}
static void
HttpServerContextFree(HttpServerContext * c)
{
char *key;
void *val;
if (!c)
{
return;
}
while (HashMapIterate(c->requestHeaders, &key, &val))
{
Free(val);
}
HashMapFree(c->requestHeaders);
while (HashMapIterate(c->responseHeaders, &key, &val))
{
/*
* These are generated by code. As such, they may be either
* on the heap, or on the stack, depending on how they were
* added.
*
* Basically, if the memory API knows about a pointer, then
* it can be freed. If it doesn't know about a pointer, skip
* freeing it because it's probably a stack pointer.
*/
if (MemoryInfoGet(val))
{
Free(val);
}
}
HashMapFree(c->responseHeaders);
while (HashMapIterate(c->requestParams, &key, &val))
{
Free(val);
}
HashMapFree(c->requestParams);
Free(c->requestPath);
StreamClose(c->stream);
Free(c);
}
HashMap *
HttpRequestHeaders(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->requestHeaders;
}
HttpRequestMethod
HttpRequestMethodGet(HttpServerContext * c)
{
if (!c)
{
return HTTP_METHOD_UNKNOWN;
}
return c->requestMethod;
}
char *
HttpRequestPath(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->requestPath;
}
HashMap *
HttpRequestParams(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->requestParams;
}
char *
HttpResponseHeader(HttpServerContext * c, char *key, char *val)
{
if (!c)
{
return NULL;
}
return HashMapSet(c->responseHeaders, key, val);
}
void
HttpResponseStatus(HttpServerContext * c, HttpStatus status)
{
if (!c)
{
return;
}
c->responseStatus = status;
}
HttpStatus
HttpResponseStatusGet(HttpServerContext * c)
{
if (!c)
{
return HTTP_STATUS_UNKNOWN;
}
return c->responseStatus;
}
Stream *
HttpServerStream(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->stream;
}
void
HttpSendHeaders(HttpServerContext * c)
{
Stream *fp = c->stream;
char *key;
char *val;
StreamPrintf(fp, "HTTP/1.0 %d %s\n", c->responseStatus, HttpStatusToString(c->responseStatus));
while (HashMapIterate(c->responseHeaders, &key, (void **) &val))
{
StreamPrintf(fp, "%s: %s\n", key, val);
}
StreamPuts(fp, "\n");
}
static Stream *
DequeueConnection(HttpServer * server)
{
Stream *fp;
if (!server)
{
return NULL;
}
pthread_mutex_lock(&server->connQueueMutex);
fp = QueuePop(server->connQueue);
pthread_mutex_unlock(&server->connQueueMutex);
return fp;
}
HttpServer *
HttpServerCreate(HttpServerConfig * config)
{
HttpServer *server;
struct sockaddr_in sa;
if (!config)
{
errno = EINVAL;
return NULL;
}
if (!config->handler)
{
errno = EINVAL;
return NULL;
}
#ifndef TLS_IMPL
if (config->flags & HTTP_FLAG_TLS)
{
Log(LOG_ERR, "TLS support is disabled.");
errno = EINVAL;
return NULL;
}
#endif
server = Malloc(sizeof(HttpServer));
if (!server)
{
goto error;
}
memset(server, 0, sizeof(HttpServer));
server->config = *config;
server->config.tlsCert = StrDuplicate(config->tlsCert);
server->config.tlsKey = StrDuplicate(config->tlsKey);
server->sd = -1;
server->threadPool = ArrayCreate();
if (!server->threadPool)
{
goto error;
}
server->connQueue = QueueCreate(config->maxConnections);
if (!server->connQueue)
{
goto error;
}
if (pthread_mutex_init(&server->connQueueMutex, NULL) != 0)
{
goto error;
}
server->sd = socket(AF_INET, SOCK_STREAM, 0);
if (server->sd < 0)
{
goto error;
}
if (fcntl(server->sd, F_SETFL, O_NONBLOCK) == -1)
{
goto error;
}
if (setsockopt(server->sd, SOL_SOCKET, SO_REUSEADDR, &ENABLE, sizeof(int)) < 0)
{
goto error;
}
#ifdef SO_REUSEPORT
if (setsockopt(server->sd, SOL_SOCKET, SO_REUSEPORT, &ENABLE, sizeof(int)) < 0)
{
goto error;
}
#endif
memset(&sa, 0, sizeof(struct sockaddr_in));
sa.sin_family = AF_INET;
sa.sin_port = htons(config->port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(server->sd, (struct sockaddr *) & sa, sizeof(sa)) < 0)
{
goto error;
}
if (listen(server->sd, config->maxConnections) < 0)
{
goto error;
}
server->stop = 0;
server->isRunning = 0;
return server;
error:
if (server)
{
if (server->connQueue)
{
QueueFree(server->connQueue);
}
pthread_mutex_destroy(&server->connQueueMutex);
if (server->threadPool)
{
ArrayFree(server->threadPool);
}
if (server->sd > -1)
{
close(server->sd);
}
Free(server);
}
return NULL;
}
HttpServerConfig *
HttpServerConfigGet(HttpServer * server)
{
if (!server)
{
return NULL;
}
return &server->config;
}
void
HttpServerFree(HttpServer * server)
{
if (!server)
{
return;
}
close(server->sd);
QueueFree(server->connQueue);
pthread_mutex_destroy(&server->connQueueMutex);
ArrayFree(server->threadPool);
Free(server->config.tlsCert);
Free(server->config.tlsKey);
Free(server);
}
static void *
HttpServerWorkerThread(void *args)
{
HttpServerWorkerThreadArgs *wArgs = (HttpServerWorkerThreadArgs *) args;
HttpServer *server = wArgs->server;
while (!server->stop)
{
Stream *fp;
HttpServerContext *context;
char *line = NULL;
size_t lineSize = 0;
ssize_t lineLen = 0;
char *requestMethodPtr;
char *pathPtr;
char *requestPath;
char *requestProtocol;
HashMap *requestParams;
ssize_t requestPathLen;
ssize_t i = 0;
HttpRequestMethod requestMethod;
UInt64 firstRead;
fp = DequeueConnection(server);
if (!fp)
{
/* Block for 1 millisecond before continuing so we don't
* murder the CPU if the queue is empty. */
UtilSleepMillis(UInt64Create(0, 1));
continue;
}
/* Get the first line of the request.
*
* Every once in a while, we're too fast for the client. When this
* happens, UtilGetLine() sets errno to EAGAIN. If we get
* EAGAIN, then clear the error on the stream and try again
* after a few ms. This is typically more than enough time for
* the client to send data. */
firstRead = UtilServerTs();
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) == -1
&& errno == EAGAIN)
{
StreamClearError(fp);
/* If the server is stopped, or it's been a while, just
* give up so we aren't wasting a thread on this client. */
if (server->stop || UInt64Gt(UInt64Sub(UtilServerTs(), firstRead), UInt64Create(0, 1000 * 30)))
{
goto finish;
}
UtilSleepMillis(UInt64Create(0, 5));
}
if (lineLen == -1)
{
goto bad_request;
}
requestMethodPtr = line;
for (i = 0; i < lineLen; i++)
{
if (line[i] == ' ')
{
line[i] = '\0';
break;
}
}
if (i == lineLen)
{
goto bad_request;
}
requestMethod = HttpRequestMethodFromString(requestMethodPtr);
if (requestMethod == HTTP_METHOD_UNKNOWN)
{
goto bad_request;
}
pathPtr = line + i + 1;
for (i = 0; i < (line + lineLen) - pathPtr; i++)
{
if (pathPtr[i] == ' ')
{
pathPtr[i] = '\0';
break;
}
}
requestPathLen = i;
requestPath = Malloc(((requestPathLen + 1) * sizeof(char)));
strncpy(requestPath, pathPtr, requestPathLen + 1);
requestProtocol = &pathPtr[i + 1];
line[lineLen - 2] = '\0'; /* Get rid of \r and \n */
if (!StrEquals(requestProtocol, "HTTP/1.1") && !StrEquals(requestProtocol, "HTTP/1.0"))
{
Free(requestPath);
goto bad_request;
}
/* Find request params */
for (i = 0; i < requestPathLen; i++)
{
if (requestPath[i] == '?')
{
break;
}
}
requestPath[i] = '\0';
requestParams = (i == requestPathLen) ? NULL : HttpParamDecode(requestPath + i + 1);
context = HttpServerContextCreate(requestMethod, requestPath, requestParams, fp);
if (!context)
{
Free(requestPath);
goto internal_error;
}
context->requestHeaders = HttpParseHeaders(fp);
if (!context->requestHeaders)
{
goto internal_error;
}
server->config.handler(context, server->config.handlerArgs);
HttpServerContextFree(context);
fp = NULL; /* The above call will close this
* Stream */
goto finish;
internal_error:
StreamPuts(fp, "HTTP/1.0 500 Internal Server Error\n");
StreamPuts(fp, "Connection: close\n");
goto finish;
bad_request:
StreamPuts(fp, "HTTP/1.0 400 Bad Request\n");
StreamPuts(fp, "Connection: close\n");
goto finish;
finish:
Free(line);
if (fp)
{
StreamClose(fp);
}
}
return NULL;
}
static void *
HttpServerEventThread(void *args)
{
HttpServer *server = (HttpServer *) args;
struct pollfd pollFds[1];
Stream *fp;
size_t i;
server->isRunning = 1;
server->stop = 0;
pollFds[0].fd = server->sd;
pollFds[0].events = POLLIN;
for (i = 0; i < server->config.threads; i++)
{
HttpServerWorkerThreadArgs *workerThread = Malloc(sizeof(HttpServerWorkerThreadArgs));
if (!workerThread)
{
/* TODO: Make the event thread return an error to the main
* thread */
return NULL;
}
workerThread->server = server;
if (pthread_create(&workerThread->thread, NULL, HttpServerWorkerThread, workerThread) != 0)
{
/* TODO: Make the event thread return an error to the main
* thread */
return NULL;
}
ArrayAdd(server->threadPool, workerThread);
}
while (!server->stop)
{
struct sockaddr_storage addr;
socklen_t addrLen = sizeof(addr);
int connFd;
int pollResult;
pollResult = poll(pollFds, 1, 500);
if (pollResult < 0)
{
/* The poll either timed out, or was interrupted. */
continue;
}
pthread_mutex_lock(&server->connQueueMutex);
/* Don't even accept connections if the queue is full. */
if (!QueueFull(server->connQueue))
{
connFd = accept(server->sd, (struct sockaddr *) & addr, &addrLen);
if (connFd < 0)
{
pthread_mutex_unlock(&server->connQueueMutex);
continue;
}
#ifdef TLS_IMPL
if (server->config.flags & HTTP_FLAG_TLS)
{
fp = TlsServerStream(connFd, server->config.tlsCert, server->config.tlsKey);
}
else
{
fp = StreamFd(connFd);
}
#else
fp = StreamFd(connFd);
#endif
if (!fp)
{
close(connFd);
pthread_mutex_unlock(&server->connQueueMutex);
continue;
}
QueuePush(server->connQueue, fp);
}
pthread_mutex_unlock(&server->connQueueMutex);
}
for (i = 0; i < server->config.threads; i++)
{
HttpServerWorkerThreadArgs *workerThread = ArrayGet(server->threadPool, i);
pthread_join(workerThread->thread, NULL);
Free(workerThread);
}
while ((fp = DequeueConnection(server)))
{
StreamClose(fp);
}
server->isRunning = 0;
return NULL;
}
int
HttpServerStart(HttpServer * server)
{
if (!server)
{
return 0;
}
if (server->isRunning)
{
return 1;
}
if (pthread_create(&server->socketThread, NULL, HttpServerEventThread, server) != 0)
{
return 0;
}
return 1;
}
void
HttpServerJoin(HttpServer * server)
{
if (!server)
{
return;
}
pthread_join(server->socketThread, NULL);
}
void
HttpServerStop(HttpServer * server)
{
if (!server)
{
return;
}
server->stop = 1;
}

View file

@ -1,399 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Int64.h>
#include <stddef.h>
#include <signal.h>
#include <Log.h>
#ifdef INT64_NATIVE
#define Int64Sign(x) ((int) (((UInt64) (x)) >> 63))
#else
#define Int64Sign(x) ((int) ((x).i[1] >> 31))
#endif
size_t
Int64Str(Int64 x, int base, char *out, size_t len)
{
static const char symbols[] = "0123456789ABCDEF";
size_t i = len - 1;
size_t j = 0;
int neg = Int64Sign(x);
Int64 base64 = Int64Create(0, base);
/* We only have symbols up to base 16 */
if (base < 2 || base > 16)
{
return 0;
}
/*
* This algorithm doesn't work on INT64_MIN.
*
* But it works on all other integers in the range, so we
* just scoot the range in by one for now. It's a hack and
* I'm not a huge fan of it, but this function is mostly
* used in Json, which shouldn't have a range this large
* anyway (Json is limited to -2^53 -> 2^53-1).
*
* Proper fixes are always welcome.
*/
if (Int64Eq(x, Int64Create(0x80000000, 0x00000000)))
{
x = Int64Add(x, Int64Create(0, 1));
}
#if 0
else if (Int64Eq(x, Int64Create(0x7FFFFFFF, 0xFFFFFFFF)))
{
x = Int64Sub(x, Int64Create(0, 1));
}
#endif
if (base != 2 && base != 8 && base != 16 && neg)
{
x = Int64Neg(x);
}
do
{
Int64 mod = Int64Rem(x, base64);
Int32 low = Int64Low(mod);
out[i] = symbols[low];
i--;
x = Int64Div(x, base64);
} while (Int64Gt(x, Int64Create(0, 0)));
if (base != 2 && base != 8 && base != 16)
{
/*
* Binary, octal, and hexadecimal are known to
* be bit representations. Everything else (notably
* decimal) should include the negative sign.
*/
if (neg)
{
out[i] = '-';
i--;
}
}
while (++i < len)
{
out[j++] = out[i];
}
out[j] = '\0';
return j;
}
#ifndef INT64_NATIVE
/* No native 64-bit support, add our own */
Int64
Int64Create(UInt32 high, UInt32 low)
{
Int64 x;
x.i[0] = low;
x.i[1] = high;
return x;
}
Int64
Int64Add(Int64 x, Int64 y)
{
Int64 z = Int64Create(0, 0);
int carry;
z.i[0] = x.i[0] + y.i[0];
carry = z.i[0] < x.i[0];
z.i[1] = x.i[1] + y.i[1] + carry;
return z;
}
Int64
Int64Sub(Int64 x, Int64 y)
{
return Int64Add(x, Int64Neg(y));
}
Int64
Int64Mul(Int64 x, Int64 y)
{
Int64 z = Int64Create(0, 0);
int xneg = Int64Sign(x);
int yneg = Int64Sign(y);
if (xneg)
{
x = Int64Neg(x);
}
if (yneg)
{
y = Int64Neg(y);
}
/* while (y > 0) */
while (Int64Gt(y, Int64Create(0, 0)))
{
/* if (y & 1 != 0) */
if (Int64Neq(Int64And(y, Int64Create(0, 1)), Int64Create(0, 0)))
{
z = Int64Add(z, x);
}
x = Int64Sll(x, 1);
y = Int64Sra(y, 1);
}
if (xneg != yneg)
{
z = Int64Neg(z);
}
return z;
}
typedef struct
{
Int64 q;
Int64 r;
} Int64Ldiv;
static Int64Ldiv
Int64LongDivision(Int64 n, Int64 d)
{
Int64Ldiv o;
int i;
int nneg = Int64Sign(n);
int dneg = Int64Sign(d);
o.q = Int64Create(0, 0);
o.r = Int64Create(0, 0);
if (Int64Eq(d, Int64Create(0, 0)))
{
raise(SIGFPE);
return o;
}
if (nneg)
{
n = Int64Neg(n);
}
if (dneg)
{
d = Int64Neg(d);
}
for (i = 63; i >= 0; i--)
{
Int64 bit = Int64And(Int64Sra(n, i), Int64Create(0, 1));
o.r = Int64Sll(o.r, 1);
o.r = Int64Or(o.r, bit);
if (Int64Geq(o.r, d))
{
o.r = Int64Sub(o.r, d);
o.q = Int64Or(o.q, Int64Sll(Int64Create(0, 1), i));
}
}
if (nneg != dneg)
{
o.r = Int64Neg(o.r);
o.q = Int64Neg(o.q);
}
return o;
}
Int64
Int64Div(Int64 x, Int64 y)
{
return Int64LongDivision(x, y).q;
}
Int64
Int64Rem(Int64 x, Int64 y)
{
return Int64LongDivision(x, y).r;
}
Int64
Int64Sll(Int64 x, int y)
{
Int64 z;
if (!y)
{
return x;
}
z = Int64Create(0, 0);
if (y < 32)
{
z.i[1] = (x.i[0] >> (32 - y)) | (x.i[1] << y);
z.i[0] = x.i[0] << y;
}
else
{
z.i[1] = x.i[0] << (y - 32);
}
return z;
}
Int64
Int64Sra(Int64 x, int y)
{
Int64 z;
int neg = Int64Sign(x);
if (!y)
{
return x;
}
z = Int64Create(0, 0);
if (y < 32)
{
z.i[0] = (x.i[1] << (32 - y)) | (x.i[0] >> y);
z.i[1] = x.i[1] >> y;
}
else
{
z.i[0] = x.i[1] >> (y - 32);
}
if (neg)
{
Int64 mask = Int64Create(0xFFFFFFFF, 0xFFFFFFFF);
z = Int64Or(Int64Sll(mask, (64 - y)), z);
}
return z;
}
Int64
Int64And(Int64 x, Int64 y)
{
return Int64Create(x.i[1] & y.i[1], x.i[0] & y.i[0]);
}
Int64
Int64Or(Int64 x, Int64 y)
{
return Int64Create(x.i[1] | y.i[1], x.i[0] | y.i[0]);
}
Int64
Int64Xor(Int64 x, Int64 y)
{
return Int64Create(x.i[1] ^ y.i[1], x.i[0] ^ y.i[0]);
}
Int64
Int64Not(Int64 x)
{
return Int64Create(~(x.i[1]), ~(x.i[0]));
}
int
Int64Eq(Int64 x, Int64 y)
{
return x.i[0] == y.i[0] && x.i[1] == y.i[1];
}
int
Int64Lt(Int64 x, Int64 y)
{
int xneg = Int64Sign(x);
int yneg = Int64Sign(y);
if (xneg != yneg)
{
return xneg > yneg;
}
else
{
if (xneg)
{
/* Both negative */
return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]);
}
else
{
/* Both positive */
return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]);
}
}
}
int
Int64Gt(Int64 x, Int64 y)
{
int xneg = Int64Sign(x);
int yneg = Int64Sign(y);
if (xneg != yneg)
{
return xneg < yneg;
}
else
{
if (xneg)
{
/* Both negative */
return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]);
}
else
{
/* Both positive */
return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]);
}
}
}
#endif

View file

@ -1,212 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Io.h>
#include <Memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
struct Io
{
IoFunctions io;
void *cookie;
};
Io *
IoCreate(void *cookie, IoFunctions funcs)
{
Io *io;
/* Must have at least read or write */
if (!funcs.read && !funcs.write)
{
return NULL;
}
io = Malloc(sizeof(Io));
if (!io)
{
return NULL;
}
io->cookie = cookie;
io->io.read = funcs.read;
io->io.write = funcs.write;
io->io.seek = funcs.seek;
io->io.close = funcs.close;
return io;
}
ssize_t
IoRead(Io * io, void *buf, size_t nBytes)
{
if (!io || !io->io.read)
{
errno = EBADF;
return -1;
}
return io->io.read(io->cookie, buf, nBytes);
}
ssize_t
IoWrite(Io * io, void *buf, size_t nBytes)
{
if (!io || !io->io.write)
{
errno = EBADF;
return -1;
}
return io->io.write(io->cookie, buf, nBytes);
}
off_t
IoSeek(Io * io, off_t offset, int whence)
{
if (!io)
{
errno = EBADF;
return -1;
}
if (!io->io.seek)
{
errno = EINVAL;
return -1;
}
return io->io.seek(io->cookie, offset, whence);
}
int
IoClose(Io * io)
{
int ret;
if (!io)
{
errno = EBADF;
return -1;
}
if (io->io.close)
{
ret = io->io.close(io->cookie);
}
else
{
ret = 0;
}
Free(io);
return ret;
}
int
IoVprintf(Io * io, const char *fmt, va_list ap)
{
char *buf = NULL;
size_t bufSize = 0;
FILE *fp;
int ret;
if (!io || !fmt)
{
return -1;
}
fp = open_memstream(&buf, &bufSize);
if (!fp)
{
return -1;
}
ret = vfprintf(fp, fmt, ap);
fclose(fp);
if (ret >= 0)
{
ret = IoWrite(io, buf, bufSize);
}
free(buf); /* Allocated by stdlib, not Memory
* API */
return ret;
}
int
IoPrintf(Io * io, const char *fmt,...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = IoVprintf(io, fmt, ap);
va_end(ap);
return ret;
}
ssize_t
IoCopy(Io * in, Io * out)
{
ssize_t nBytes = 0;
char buf[IO_BUFFER];
ssize_t rRes;
ssize_t wRes;
if (!in || !out)
{
errno = EBADF;
return -1;
}
while ((rRes = IoRead(in, &buf, IO_BUFFER)) != 0)
{
if (rRes == -1)
{
return -1;
}
wRes = IoWrite(out, &buf, rRes);
if (wRes == -1)
{
return -1;
}
nBytes += wRes;
}
return nBytes;
}

View file

@ -1,95 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Io.h>
#include <Memory.h>
#include <fcntl.h>
static ssize_t
IoReadFd(void *cookie, void *buf, size_t nBytes)
{
int fd = *((int *) cookie);
return read(fd, buf, nBytes);
}
static ssize_t
IoWriteFd(void *cookie, void *buf, size_t nBytes)
{
int fd = *((int *) cookie);
return write(fd, buf, nBytes);
}
static off_t
IoSeekFd(void *cookie, off_t offset, int whence)
{
int fd = *((int *) cookie);
return lseek(fd, offset, whence);
}
static int
IoCloseFd(void *cookie)
{
int fd = *((int *) cookie);
Free(cookie);
return close(fd);
}
Io *
IoFd(int fd)
{
int *cookie = Malloc(sizeof(int));
IoFunctions f;
if (!cookie)
{
return NULL;
}
*cookie = fd;
f.read = IoReadFd;
f.write = IoWriteFd;
f.seek = IoSeekFd;
f.close = IoCloseFd;
return IoCreate(cookie, f);
}
Io *
IoOpen(const char *path, int flags, mode_t mode)
{
int fd = open(path, flags, mode);
if (fd == -1)
{
return NULL;
}
return IoFd(fd);
}

View file

@ -1,91 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Io.h>
#include <stdio.h>
static ssize_t
IoReadFile(void *cookie, void *buf, size_t nBytes)
{
FILE *fp = cookie;
return fread(buf, 1, nBytes, fp);
}
static ssize_t
IoWriteFile(void *cookie, void *buf, size_t nBytes)
{
FILE *fp = cookie;
size_t res = fwrite(buf, 1, nBytes, fp);
/*
* fwrite() may be buffered on some platforms, but at this low level,
* it should not be; buffering happens in Stream, not Io.
*/
fflush(fp);
return res;
}
static off_t
IoSeekFile(void *cookie, off_t offset, int whence)
{
FILE *fp = cookie;
off_t ret = fseeko(fp, offset, whence);
if (ret > -1)
{
return ftello(fp);
}
else
{
return ret;
}
}
static int
IoCloseFile(void *cookie)
{
FILE *fp = cookie;
return fclose(fp);
}
Io *
IoFile(FILE * fp)
{
IoFunctions f;
if (!fp)
{
return NULL;
}
f.read = IoReadFile;
f.write = IoWriteFile;
f.seek = IoSeekFile;
f.close = IoCloseFile;
return IoCreate(fp, f);
}

File diff suppressed because it is too large Load diff

View file

@ -1,393 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Log.h>
#include <Memory.h>
#include <Util.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <ctype.h>
#include <stdarg.h>
#include <pthread.h>
#define LOG_TSBUFFER 64
struct LogConfig
{
int level;
size_t indent;
Stream *out;
int flags;
char *tsFmt;
pthread_mutex_t lock;
};
LogConfig *globalConfig = NULL;
LogConfig *
LogConfigCreate(void)
{
LogConfig *config;
config = Malloc(sizeof(LogConfig));
if (!config)
{
return NULL;
}
memset(config, 0, sizeof(LogConfig));
pthread_mutex_init(&config->lock, NULL);
LogConfigLevelSet(config, LOG_INFO);
LogConfigIndentSet(config, 0);
LogConfigOutputSet(config, NULL); /* Will set to stdout */
LogConfigFlagSet(config, LOG_FLAG_COLOR);
LogConfigTimeStampFormatSet(config, "%y-%m-%d %H:%M:%S");
return config;
}
LogConfig *
LogConfigGlobal(void)
{
if (!globalConfig)
{
globalConfig = LogConfigCreate();
}
return globalConfig;
}
void
LogConfigFlagClear(LogConfig * config, int flags)
{
if (!config)
{
return;
}
config->flags &= ~flags;
}
static int
LogConfigFlagGet(LogConfig * config, int flags)
{
if (!config)
{
return 0;
}
return config->flags & flags;
}
void
LogConfigFlagSet(LogConfig * config, int flags)
{
if (!config)
{
return;
}
config->flags |= flags;
}
void
LogConfigFree(LogConfig * config)
{
if (!config)
{
return;
}
pthread_mutex_destroy(&config->lock);
Free(config);
if (config == globalConfig)
{
globalConfig = NULL;
}
}
void
LogConfigIndent(LogConfig * config)
{
if (config)
{
config->indent += 2;
}
}
void
LogConfigIndentSet(LogConfig * config, size_t indent)
{
if (!config)
{
return;
}
config->indent = indent;
}
int
LogConfigLevelGet(LogConfig * config)
{
if (!config)
{
return -1;
}
return config->level;
}
void
LogConfigLevelSet(LogConfig * config, int level)
{
if (!config)
{
return;
}
switch (level)
{
case LOG_ERR:
case LOG_WARNING:
case LOG_INFO:
case LOG_DEBUG:
config->level = level;
default:
break;
}
}
void
LogConfigOutputSet(LogConfig * config, Stream * out)
{
if (!config)
{
return;
}
if (out)
{
config->out = out;
}
else
{
config->out = StreamStdout();
}
}
void
LogConfigTimeStampFormatSet(LogConfig * config, char *tsFmt)
{
if (config)
{
config->tsFmt = tsFmt;
}
}
void
LogConfigUnindent(LogConfig * config)
{
if (config && config->indent >= 2)
{
config->indent -= 2;
}
}
void
Logv(LogConfig * config, int level, const char *msg, va_list argp)
{
size_t i;
int doColor;
char indicator;
/*
* Only proceed if we have a config and its log level is set to a
* value that permits us to log. This is as close as we can get
* to a no-op function if we aren't logging anything, without doing
* some crazy macro magic.
*/
if (!config || level > config->level)
{
return;
}
/* Misconfiguration */
if (!config->out)
{
return;
}
pthread_mutex_lock(&config->lock);
if (LogConfigFlagGet(config, LOG_FLAG_SYSLOG))
{
/* No further print logic is needed; syslog will handle it all
* for us. */
vsyslog(level, msg, argp);
pthread_mutex_unlock(&config->lock);
return;
}
doColor = LogConfigFlagGet(config, LOG_FLAG_COLOR)
&& isatty(StreamFileno(config->out));
if (doColor)
{
char *ansi;
switch (level)
{
case LOG_EMERG:
case LOG_ALERT:
case LOG_CRIT:
case LOG_ERR:
/* Bold Red */
ansi = "\033[1;31m";
break;
case LOG_WARNING:
/* Bold Yellow */
ansi = "\033[1;33m";
break;
case LOG_NOTICE:
/* Bold Magenta */
ansi = "\033[1;35m";
break;
case LOG_INFO:
/* Bold Green */
ansi = "\033[1;32m";
break;
case LOG_DEBUG:
/* Bold Blue */
ansi = "\033[1;34m";
break;
default:
ansi = "";
break;
}
StreamPuts(config->out, ansi);
}
StreamPutc(config->out, '[');
if (config->tsFmt)
{
time_t timer = time(NULL);
struct tm *timeInfo = localtime(&timer);
char tsBuffer[LOG_TSBUFFER];
int tsLength = strftime(tsBuffer, LOG_TSBUFFER, config->tsFmt,
timeInfo);
if (tsLength)
{
StreamPuts(config->out, tsBuffer);
if (!isspace((unsigned char) tsBuffer[tsLength - 1]))
{
StreamPutc(config->out, ' ');
}
}
}
StreamPrintf(config->out, "(%lu) ", UtilThreadNo());
switch (level)
{
case LOG_EMERG:
indicator = '#';
break;
case LOG_ALERT:
indicator = '@';
break;
case LOG_CRIT:
indicator = 'X';
break;
case LOG_ERR:
indicator = 'x';
break;
case LOG_WARNING:
indicator = '!';
break;
case LOG_NOTICE:
indicator = '~';
break;
case LOG_INFO:
indicator = '>';
break;
case LOG_DEBUG:
indicator = '*';
break;
default:
indicator = '?';
break;
}
StreamPrintf(config->out, "%c]", indicator);
if (doColor)
{
/* ANSI Reset */
StreamPuts(config->out, "\033[0m");
}
StreamPutc(config->out, ' ');
for (i = 0; i < config->indent; i++)
{
StreamPutc(config->out, ' ');
}
StreamVprintf(config->out, msg, argp);
StreamPutc(config->out, '\n');
StreamFlush(config->out);
pthread_mutex_unlock(&config->lock);
}
void
LogTo(LogConfig * config, int level, const char *fmt,...)
{
va_list argp;
va_start(argp, fmt);
Logv(config, level, fmt, argp);
va_end(argp);
}
extern void
Log(int level, const char *fmt,...)
{
va_list argp;
va_start(argp, fmt);
Logv(LogConfigGlobal(), level, fmt, argp);
va_end(argp);
}

View file

@ -1,625 +0,0 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Memory.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <Int.h>
#ifndef MEMORY_TABLE_CHUNK
#define MEMORY_TABLE_CHUNK 256
#endif
#ifndef MEMORY_HEXDUMP_WIDTH
#define MEMORY_HEXDUMP_WIDTH 16
#endif
struct MemoryInfo
{
size_t size;
const char *file;
int line;
void *pointer;
};
#define MEM_BOUND_TYPE UInt32
#define MEM_BOUND 0xDEADBEEF
#define MEM_BOUND_LOWER(p) *((MEM_BOUND_TYPE *) p)
#define MEM_BOUND_UPPER(p, x) *(((MEM_BOUND_TYPE *) (((UInt8 *) p) + x)) + 1)
#define MEM_SIZE_ACTUAL(x) (((x) * sizeof(UInt8)) + (2 * sizeof(MEM_BOUND_TYPE)))
static pthread_mutex_t lock;
static void (*hook) (MemoryAction, MemoryInfo *, void *) = MemoryDefaultHook;
static void *hookArgs = NULL;
static MemoryInfo **allocations = NULL;
static size_t allocationsSize = 0;
static size_t allocationsLen = 0;
int
MemoryRuntimeInit(void)
{
pthread_mutexattr_t attr;
int ret = 0;
if (pthread_mutexattr_init(&attr) != 0)
{
goto finish;
}
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
ret = pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
ret = (ret == 0);
finish:
return ret;
}
int
MemoryRuntimeDestroy(void)
{
MemoryFreeAll();
return pthread_mutex_destroy(&lock) == 0;
}
static size_t
MemoryHash(void *p)
{
return (((size_t) p) >> 2 * 7) % allocationsSize;
}
static int
MemoryInsert(MemoryInfo * a)
{
size_t hash;
if (!allocations)
{
allocationsSize = MEMORY_TABLE_CHUNK;
allocations = calloc(allocationsSize, sizeof(void *));
if (!allocations)
{
return 0;
}
}
/* If the next insertion would cause the table to be at least 3/4
* full, re-allocate and re-hash. */
if ((allocationsLen + 1) >= ((allocationsSize * 3) >> 2))
{
size_t i;
size_t tmpAllocationsSize = allocationsSize;
MemoryInfo **tmpAllocations;
allocationsSize += MEMORY_TABLE_CHUNK;
tmpAllocations = calloc(allocationsSize, sizeof(void *));
if (!tmpAllocations)
{
return 0;
}
for (i = 0; i < tmpAllocationsSize; i++)
{
if (allocations[i])
{
hash = MemoryHash(allocations[i]->pointer);
while (tmpAllocations[hash])
{
hash = (hash + 1) % allocationsSize;
}
tmpAllocations[hash] = allocations[i];
}
}
free(allocations);
allocations = tmpAllocations;
}
hash = MemoryHash(a->pointer);
while (allocations[hash])
{
hash = (hash + 1) % allocationsSize;
}
allocations[hash] = a;
allocationsLen++;
return 1;
}
static void
MemoryDelete(MemoryInfo * a)
{
size_t hash = MemoryHash(a->pointer);
size_t count = 0;
while (count <= allocationsSize)
{
if (allocations[hash] && allocations[hash] == a)
{
allocations[hash] = NULL;
allocationsLen--;
return;
}
else
{
hash = (hash + 1) % allocationsSize;
count++;
}
}
}
static int
MemoryCheck(MemoryInfo * a)
{
if (MEM_BOUND_LOWER(a->pointer) != MEM_BOUND ||
MEM_BOUND_UPPER(a->pointer, a->size - (2 * sizeof(MEM_BOUND_TYPE))) != MEM_BOUND)
{
if (hook)
{
hook(MEMORY_CORRUPTED, a, hookArgs);
}
return 0;
}
return 1;
}
void *
MemoryAllocate(size_t size, const char *file, int line)
{
void *p;
MemoryInfo *a;
MemoryIterate(NULL, NULL);
pthread_mutex_lock(&lock);
a = malloc(sizeof(MemoryInfo));
if (!a)
{
pthread_mutex_unlock(&lock);
return NULL;
}
p = malloc(MEM_SIZE_ACTUAL(size));
if (!p)
{
free(a);
pthread_mutex_unlock(&lock);
return NULL;
}
memset(p, 0, MEM_SIZE_ACTUAL(size));
MEM_BOUND_LOWER(p) = MEM_BOUND;
MEM_BOUND_UPPER(p, size) = MEM_BOUND;
a->size = MEM_SIZE_ACTUAL(size);
a->file = file;
a->line = line;
a->pointer = p;
if (!MemoryInsert(a))
{
free(a);
free(p);
pthread_mutex_unlock(&lock);
return NULL;
}
if (hook)
{
hook(MEMORY_ALLOCATE, a, hookArgs);
}
pthread_mutex_unlock(&lock);
return ((MEM_BOUND_TYPE *) p) + 1;
}
void *
MemoryReallocate(void *p, size_t size, const char *file, int line)
{
MemoryInfo *a;
void *new = NULL;
MemoryIterate(NULL, NULL);
if (!p)
{
return MemoryAllocate(size, file, line);
}
a = MemoryInfoGet(p);
if (a)
{
pthread_mutex_lock(&lock);
new = realloc(a->pointer, MEM_SIZE_ACTUAL(size));
if (new)
{
MemoryDelete(a);
a->size = MEM_SIZE_ACTUAL(size);
a->file = file;
a->line = line;
a->pointer = new;
MemoryInsert(a);
MEM_BOUND_LOWER(a->pointer) = MEM_BOUND;
MEM_BOUND_UPPER(a->pointer, size) = MEM_BOUND;
if (hook)
{
hook(MEMORY_REALLOCATE, a, hookArgs);
}
}
pthread_mutex_unlock(&lock);
}
else if (hook)
{
a = malloc(sizeof(MemoryInfo));
if (a)
{
a->size = 0;
a->file = file;
a->line = line;
a->pointer = p;
hook(MEMORY_BAD_POINTER, a, hookArgs);
free(a);
}
}
return ((MEM_BOUND_TYPE *) new) + 1;
}
void
MemoryFree(void *p, const char *file, int line)
{
MemoryInfo *a;
MemoryIterate(NULL, NULL);
if (!p)
{
return;
}
a = MemoryInfoGet(p);
if (a)
{
pthread_mutex_lock(&lock);
if (hook)
{
a->file = file;
a->line = line;
hook(MEMORY_FREE, a, hookArgs);
}
MemoryDelete(a);
free(a->pointer);
free(a);
pthread_mutex_unlock(&lock);
}
else if (hook)
{
a = malloc(sizeof(MemoryInfo));
if (a)
{
a->file = file;
a->line = line;
a->size = 0;
a->pointer = p;
hook(MEMORY_BAD_POINTER, a, hookArgs);
free(a);
}
}
}
size_t
MemoryAllocated(void)
{
size_t i;
size_t total = 0;
pthread_mutex_lock(&lock);
for (i = 0; i < allocationsSize; i++)
{
if (allocations[i])
{
total += MemoryInfoGetSize(allocations[i]);
}
}
pthread_mutex_unlock(&lock);
return total;
}
void
MemoryFreeAll(void)
{
size_t i;
pthread_mutex_lock(&lock);
for (i = 0; i < allocationsSize; i++)
{
if (allocations[i])
{
free(allocations[i]->pointer);
free(allocations[i]);
}
}
free(allocations);
allocations = NULL;
allocationsSize = 0;
allocationsLen = 0;
pthread_mutex_unlock(&lock);
}
MemoryInfo *
MemoryInfoGet(void *p)
{
size_t hash, count;
pthread_mutex_lock(&lock);
p = ((MEM_BOUND_TYPE *) p) - 1;
hash = MemoryHash(p);
count = 0;
while (count <= allocationsSize)
{
if (!allocations[hash] || allocations[hash]->pointer != p)
{
hash = (hash + 1) % allocationsSize;
count++;
}
else
{
MemoryCheck(allocations[hash]);
pthread_mutex_unlock(&lock);
return allocations[hash];
}
}
pthread_mutex_unlock(&lock);
return NULL;
}
size_t
MemoryInfoGetSize(MemoryInfo * a)
{
if (!a)
{
return 0;
}
return a->size ? a->size - (2 * sizeof(MEM_BOUND_TYPE)) : 0;
}
const char *
MemoryInfoGetFile(MemoryInfo * a)
{
if (!a)
{
return NULL;
}
return a->file;
}
int
MemoryInfoGetLine(MemoryInfo * a)
{
if (!a)
{
return -1;
}
return a->line;
}
void *
MemoryInfoGetPointer(MemoryInfo * a)
{
if (!a)
{
return NULL;
}
return ((MEM_BOUND_TYPE *) a->pointer) + 1;
}
void
MemoryIterate(void (*iterFunc) (MemoryInfo *, void *), void *args)
{
size_t i;
pthread_mutex_lock(&lock);
for (i = 0; i < allocationsSize; i++)
{
if (allocations[i])
{
MemoryCheck(allocations[i]);
if (iterFunc)
{
iterFunc(allocations[i], args);
}
}
}
pthread_mutex_unlock(&lock);
}
void
MemoryHook(void (*memHook) (MemoryAction, MemoryInfo *, void *), void *args)
{
pthread_mutex_lock(&lock);
hook = memHook;
hookArgs = args;
pthread_mutex_unlock(&lock);
}
static size_t
HexPtr(unsigned long ptr, char *