From 95342f7ad1145dfbad7ffed9a6127e6d7fd720b8 Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Sat, 23 Jul 2022 00:19:12 +0000 Subject: [PATCH] Simplify directory structure. Before this commit, the directory structure was a mess. So messy, in fact, that it would be incredibly inconvenient to modify it in the existing repo. So, here's a new repo. That shouldn't happen again. --- .cvsignore | 4 - .idea/Telodendria.iml | 8 + .idea/modules.xml | 8 + .idea/workspace.xml | 52 ++++ Telodendria.css | 62 +++++ Telodendria.html | 541 +++++++++++++++++++++++++++++++++++++++ contrib/httpd.conf | 9 + contrib/telodendria.conf | 13 + make.sh | 57 +++++ src/Array.c | 115 ++------- src/Base64.c | 90 +++---- src/Config.c | 286 +++++++-------------- src/HashMap.c | 158 +++--------- src/Log.c | 404 +++++++++++++---------------- src/Telodendria.c | 436 ++++--------------------------- src/include/Array.h | 45 +--- src/include/Base64.h | 39 +-- src/include/Config.h | 189 +------------- src/include/HashMap.h | 107 +++++--- src/include/Http.h | 72 ++---- src/include/Json.h | 274 +++----------------- src/include/Log.h | 212 +++------------ 22 files changed, 1350 insertions(+), 1831 deletions(-) create mode 100644 .idea/Telodendria.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/workspace.xml create mode 100644 Telodendria.css create mode 100644 Telodendria.html create mode 100644 contrib/httpd.conf create mode 100644 contrib/telodendria.conf create mode 100644 make.sh diff --git a/.cvsignore b/.cvsignore index 94cf257..378eac2 100644 --- a/.cvsignore +++ b/.cvsignore @@ -1,5 +1 @@ build -data -.env -*.log -vgcore.* diff --git a/.idea/Telodendria.iml b/.idea/Telodendria.iml new file mode 100644 index 0000000..bc2cd87 --- /dev/null +++ b/.idea/Telodendria.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..857f442 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..997c9ec --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + { + "keyToString": { + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.cidr.known.project.marker": "true", + "WebServerToolWindowFactoryState": "false", + "cf.first.check.clang-format": "false", + "cidr.known.project.marker": "true" + } +} + + + + + 1658323016759 + + + + + + \ No newline at end of file diff --git a/Telodendria.css b/Telodendria.css new file mode 100644 index 0000000..0519a73 --- /dev/null +++ b/Telodendria.css @@ -0,0 +1,62 @@ +body { + margin: auto; + max-width: 8.5in; + padding: 0.25in; +} +.code { + background-color: #eee; + border-radius: 5px; + display: block; + padding: 0.5em 1em 1.5em 1em; + + font-family: monospace; + white-space: pre; + overflow-x: scroll; +} +kbd { + background-color: #eee; + border-radius: 3px; + border: 1px solid #b4b4b4; + box-shadow: 0 1px 1px rgba(0, 0, 0, .2), 0 2px 0 0 rgba(255, 255, 255, .7) inset; + color: #333; + display: inline-block; + font-size: .85em; + font-weight: 700; + line-height: 1; + padding: 2px 4px; + white-space: nowrap; +} +h1, h4, h5, h6 { + border-bottom: 1px dashed gray; +} +h4, h5, h6 { + width: fit-content; +} +a { + color: #0969da; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +table { + width: 100%; + border-collapse: collapse; +} +td, th { + border: 1px solid #eee; + text-align: left; + padding: 8px; +} +tr:nth-child(even) { + background-color: #eee; +} +@media (prefers-color-scheme: dark) { + body { + background-color: #212121; + color: white; + } + .code, tr:nth-child(even) { + background-color: #333333; + } +} \ No newline at end of file diff --git a/Telodendria.html b/Telodendria.html new file mode 100644 index 0000000..61bd78f --- /dev/null +++ b/Telodendria.html @@ -0,0 +1,541 @@ + + + + + + + + + + + + + + + + +Telodendria | A Matrix Homeserver written in ANSI C. + + +

Telodendria

+

+Telodendria: The terminal branches of an axon. +

+

+Note: Telodendria is under heavy development. +Please see the Project Status. +

+

+Telodendria is a Matrix homeserver implementation written from +scratch in ANSI C. It is designed to be lightweight and simple, yet +functional. Telodendria differentiates itself from other Matrix +homeserver implementations because it: +

+

+Telodendria is on Matrix! Check out the official Matrix rooms: +

+ + + + + + + + + + + + + + + + + + + + + +
RoomDescription
+#telodendria-releases:bancino.net + +Get notified of new releases. +
+#telodendria-general:bancino.net + +General discussion and support for Telodendria. +
+#telodendria-issues:bancino.net + +Report issues with Telodendria. +
+#telodendria-patches:bancino.net + +Submit code patches to the Telodendria project. +
+

Table of Contents

+ +

Getting Started

+

Install Telodendria

+

+If your operating system has an official package or port of +Telodendria, you should prefer to use that. If your operating +system's package or port is too out of date for your tastes, please +contact the package's maintainers to notify them, or offer to update +the package yourself. +

+

+If your operating system does not have an official package, see below +for instructions on building from source and use them to create one. +

+

OpenBSD

+

+Telodendria is available in the ports tree and as a binary +package. You can install it with the following command: +

+
+$ pkg_add telodendria +
+

Building From Source

+

+Telodendria is designed to be light enough that it can be built +from source on just about any operating system. It only has the +following requirements, all of which should be already available to +you on a sufficiently complete operating system: +

+ +
+$ ./make.sh +
+

+If everything went well, that will produce +telodendria.cgi, which you can then place under your web +root and configure your web server to execute. You'll need to make sure +/.well-known/matrix and /_matrix and all the +paths under them actually execute telodendria.cgi. See the +provided OpenBSD httpd.conf for reference. Even if you +aren't using OpenBSD's httpd(8), you should find its +configuration syntax simple enough to adequately demonstrate the proper +configuration. +

+

Configure Telodendria

+

+Once you get Telodendria built and hooked into your web server, +you will have to write a configuration file for it. The configuration +file is just JSON, and it should be called +Telodendria.json. +

+

Project Status

+

+Telodendria is a very ambitious project. There's a lot that needs +to happen yet before it is even remotely usable. At the moment, there's +nothing that even remotely resembles a Matrix homeserver here; we're still +getting off the ground and building a foundation. +

+

+Just because there's nothing here yet doesn't mean you should go away +though! We desparately need help, so you are more than welcome to help +out if you want things to go quicker. Please see the +Contributing section for details on how you +can get involved. +

+

Phase 1: Getting Off The Ground

+ +

Phase 2: Building A Foundation

+ +

Phase 3: Welcome To Matrix

+ +

Phase 4: A Real Homeserver

+ +

Documentation Status

+

+This documentation needs just a little work. Here's the things +on my list for that: +

+ +

Rationale

+

+This section explains +

+

+I want a lightweight Matrix homeserver designed for OpenBSD. I want a +homeserver that can be developed in vi(1) and compiled +with a C compiler. I want it to function entirely on a base OpenBSD +install without having to install any extra packages whatsoever. I've +found that the existing homeserver implementations are way +over-engineered and written in such a way that many programs and +libraries have to be pulled in to use them. I also want to learn how +Matrix works, and I want to understand the code I'm running on my +server. +

+

+So I wrote Telodendria. +

+

+Telodendria is written entirely in portable ANSI C. It depends on no +third-party C libraries other than the standard C library. The only +thing you need to run it is a web server that supports executing CGI +programs, and a directory that data can be written to. Everything +Telodendria needs to run itself is compiled into a single static +binary, and the source code can be built anywhere, right out of the +box. +

+

+Telodendria doesn't use a database like all the other homeservers. +Instead, it operates more like email: it uses a flat-file data +structure similar to maildir to store data. The advantage of this is +that it saves server maintainers from also having to maintain a +database. It greatly simplifies the process of getting a Matrix +homeserver up and running, and it makes it highly portable. It also is +extremely easy to back up and restore with base tools; just +tar(1) up the directory, and you're good to go. +

+

+Telodendria is developed and tested on OpenBSD, but you'll find that it +should run under any web server that supports CGI. I chose to write +Telodendria as a CGI program because anyone running an existing Matrix +server is likely running a web server acting as a reverse proxy in +front of it anyway, so why not just hook the homeserver directly into +the web server? That's one less daemon to run, which means memory and +CPU savings. CGI also allows Telodendria to remain single-threaded. +Each request that comes in is handled as its own process, and +operations are entirely isolated. +

+

Project Goals

+

+The goals of this project are as follows: +

+ +

Getting Support

+

+Telodendria is designed to be fairly straightforward, but that +doesn't mean there won't be hiccups along the way. If you are struggling +to get Telodendria up and running, you're more than welcome to +reach out for support. Just join the +#telodendria-general:bancino.net Matrix channel. Before +you do though, make sure you're running the latest version of +Telodendria and you've thoroughly read through all the +relevant documentation. +

+

Contributing

+

+Telodendria is an open source project. As such, it welcomes +contributions. There are many ways you can contribute, and any way you +can is greatly appreciated. +

+

Reporting Issues

+

+If—after you've reached out to +#telodendria-general:bancino.net—it has been +determined that there is a problem with Telodendria, it should +be reported to #telodendria-issues:bancino.net. There it +can be discussed further. The issues channel serves as the official +issue tracker of Telodendria; although issues may be copied +into a TODO file in the CVS repository just so they +don't get lost. +

+

Developing

+

+The primary language used to write Telodendria code is ANSI C. +Yes, that's the original C standard from 1989. The reason this standard +is chosen, and the reason that it will not be changed, is because the +original C is the most portable. Other languages you'll find in the +Telodendria repository are shell scripts and HTML. If you have +any experience at all with any of these languages, your contributions +are valuable. Please follow the guidelines in this section to ensure +the contribution workflow goes as smoothly as possible. +

+

Getting The Code

+

+There are multiple ways to get the source code for Telodendria. +You can download an official release tarball from +here if you would like, +but the preferred way is to check out the source code from CVS. This +makes generating patches a lot easier. If you do not have CVS, consult +your operating system's package repository to install it. CVS was the +chosen version control system for this project primarily because it is +built into OpenBSD. +

+
+$ export CVSROOT=anoncvs@bancino.net:/cvs +$ cvs checkout Telodendria +$ cd Telodendria +
+

+You should now have the latest Telodendria source code. Follow +the Code Style as you make your changes. +

+

Code Style

+

+Telodendria's code style is very unique. In general, these are +the conventions used by the code base. +

+ +

+This guide may be subject to change. The source code is the absolute +source of truth, so as long as you make your code look like the +code surrounding it, you should be fine. +

+

Submitting Patches

+

+Submitting patches is fairly easy to do if you've got the CVS sources +checked out. Once you have made your changes, just run +cvs diff: +

+
+$ cvs diff -uNp > your-changes.patch +
+

+Then, send the resulting patches to +#telodendria-patches:bancino.net, where they will be +promptly reviewed by the community. +

+

License

+

+All of the code and documentation for Telodendria is licensed +under the following terms and conditions: +

+
+Copyright (C) 2022 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 substantial 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. +
+

Change Log

+

+At this time, Telodendria does not have any tagged releases because it +is not yet functional as a Matrix homeserver. Please check out the Project Status to see where things are +currently at. +

+ + diff --git a/contrib/httpd.conf b/contrib/httpd.conf new file mode 100644 index 0000000..bc1297f --- /dev/null +++ b/contrib/httpd.conf @@ -0,0 +1,9 @@ +# +# httpd.conf: An OpenBSD httpd(8) configuration file for running +# Telodendria. Note that this is a development configuration that +# should be adapted using the httpd.conf(5) man page for production +# use. +# +server "matrix" { + listen on localhost port http +} diff --git a/contrib/telodendria.conf b/contrib/telodendria.conf new file mode 100644 index 0000000..8d32a41 --- /dev/null +++ b/contrib/telodendria.conf @@ -0,0 +1,13 @@ +# Telodendria configuration file + +log "/var/log/telodendria.log" { + level "message"; + timestampFormat "none"; + color "true"; +}; + +threads "4"; + +data-dir "/var/telodendria"; + +federation "true"; \ No newline at end of file diff --git a/make.sh b/make.sh new file mode 100644 index 0000000..dc24dd2 --- /dev/null +++ b/make.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env sh + +TELODENDRIA_VERSION="0.0.1" + +HEADERS="-D_POSIX_C_SOURCE=199506L -DTELODENDRIA_VERSION=\"$TELODENDRIA_VERSION\"" +INCLUDES="-Isrc/include" + +CC="${CC:-cc}" +CFLAGS="-Wall -Werror -pedantic -std=c89 -O3 $HEADERS $INCLUDES" +LDFLAGS="-static -flto -fdata-sections -ffunction-sections -s -Wl,-static -Wl,-gc-sections" +PROG="telodendria" + +mod_time() { + if [ -n "$1" ] && [ -f "$1" ]; then + case "$(uname)" in + Linux) + stat -c %Y "$1" + ;; + *BSD) + stat -f %m "$1" + ;; + *) + echo "0" + ;; + esac + else + echo "0" + fi +} + +mkdir -p build + +do_rebuild=0 +objs="" +for src in $(find src -name '*.c'); do + obj=$(echo "$src" | sed -e 's/^src/build/' -e 's/\.c$/\.o/') + objs="$objs $obj" + + if [ $(mod_time "$src") -gt $(mod_time "$obj") ]; then + echo "CC $obj" + obj_dir=$(dirname "$obj") + mkdir -p "$obj_dir" + if ! $CC $CFLAGS -c -o "$obj" "$src"; then + exit 1 + fi + do_rebuild=1 + fi +done + +if [ $do_rebuild -eq 1 ] || [ ! -f "build/$PROG" ]; then + echo "LD build/$PROG" + $CC $LDFLAGS -o "build/$PROG" $objs +else + echo "Up to date." +fi + +ls -lh "build/$PROG" diff --git a/src/Array.c b/src/Array.c index a32c2ef..25e8db6 100644 --- a/src/Array.c +++ b/src/Array.c @@ -1,26 +1,3 @@ -/* - * Copyright (C) 2022 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 #ifndef ARRAY_BLOCK @@ -28,18 +5,16 @@ #endif #include -#include +#include -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 */ +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) +ArrayAdd(Array *array, void *value) { if (!array) { @@ -52,7 +27,7 @@ ArrayAdd(Array * array, void *value) Array * ArrayCreate(void) { - Array *array = Malloc(sizeof(Array)); + Array *array = malloc(sizeof(Array)); if (!array) { @@ -61,11 +36,11 @@ ArrayCreate(void) array->size = 0; array->allocated = ARRAY_BLOCK; - array->entries = Malloc(sizeof(void *) * ARRAY_BLOCK); + array->entries = malloc(sizeof(void *) * ARRAY_BLOCK); if (!array->entries) { - Free(array); + free(array); return NULL; } @@ -73,12 +48,12 @@ ArrayCreate(void) } void * -ArrayDelete(Array * array, size_t index) +ArrayDelete(Array *array, size_t index) { size_t i; void *element; - if (!array || array->size <= index) + if (!array) { return NULL; } @@ -96,17 +71,17 @@ ArrayDelete(Array * array, size_t index) } void -ArrayFree(Array * array) +ArrayFree(Array *array) { if (array) { - Free(array->entries); - Free(array); + free(array->entries); + free(array); } } void * -ArrayGet(Array * array, size_t index) +ArrayGet(Array *array, size_t index) { if (!array) { @@ -123,7 +98,7 @@ ArrayGet(Array * array, size_t index) extern int -ArrayInsert(Array * array, void *value, size_t index) +ArrayInsert(Array *array, void *value, size_t index) { size_t i; @@ -139,7 +114,7 @@ ArrayInsert(Array * array, void *value, size_t index) tmp = array->entries; - array->entries = Realloc(array->entries, + array->entries = realloc(array->entries, sizeof(void *) * newSize); if (!array->entries) @@ -164,7 +139,7 @@ ArrayInsert(Array * array, void *value, size_t index) } size_t -ArraySize(Array * array) +ArraySize(Array *array) { if (!array) { @@ -175,7 +150,7 @@ ArraySize(Array * array) } int -ArrayTrim(Array * array) +ArrayTrim(Array *array) { void **tmp; @@ -186,7 +161,7 @@ ArrayTrim(Array * array) tmp = array->entries; - array->entries = Realloc(array->entries, + array->entries = realloc(array->entries, sizeof(void *) * array->size); if (!array->entries) @@ -197,53 +172,3 @@ ArrayTrim(Array * array) return 1; } - -static void -ArraySwap(Array * array, size_t i, size_t j) -{ - void *p = array->entries[i]; - - array->entries[i] = array->entries[j]; - array->entries[j] = p; -} - -static size_t -ArrayPartition(Array * array, size_t low, size_t high, int (*compare) (void *, void *)) -{ - void *pivot = array->entries[high]; - size_t i = low - 1; - size_t j; - - for (j = low; j <= high - 1; j++) - { - if (compare(array->entries[j], pivot) < 0) - { - i++; - ArraySwap(array, i, j); - } - } - ArraySwap(array, i + 1, high); - return i + 1; -} - -static void -ArrayQuickSort(Array * array, size_t low, size_t high, int (*compare) (void *, void *)) -{ - if (low < high) - { - size_t pi = ArrayPartition(array, low, high, compare); - - ArrayQuickSort(array, low, pi - 1, compare); - ArrayQuickSort(array, pi + 1, high, compare); - } -} - -void -ArraySort(Array * array, int (*compare) (void *, void *)) -{ - if (!array) - { - return; - } - ArrayQuickSort(array, 0, array->size, compare); -} diff --git a/src/Base64.c b/src/Base64.c index bf9ee5b..12b60aa 100644 --- a/src/Base64.c +++ b/src/Base64.c @@ -1,40 +1,17 @@ -/* - * Copyright (C) 2022 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 -#include +#include static const char Base64EncodeMap[] = -"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + "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 + 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 @@ -66,14 +43,10 @@ Base64DecodedSize(const char *base64, size_t len) ret = len / 4 * 3; - for (i = len; i > 0; i--) - { - if (base64[i] == '=') - { + for (i = len; i > 0; i--) { + if (base64[i] == '=') { ret--; - } - else - { + } else { break; } } @@ -94,7 +67,7 @@ Base64Encode(const char *input, size_t len) } outLen = Base64EncodedSize(len); - out = Malloc(outLen + 1); + out = malloc(outLen + 1); if (!out) { return NULL; @@ -113,17 +86,12 @@ Base64Encode(const char *input, size_t len) if (i + 1 < len) { out[j + 2] = Base64EncodeMap[(v >> 6) & 0x3F]; - } - else - { + } else { out[j + 2] = '='; } - if (i + 2 < len) - { + if (i + 2 < len) { out[j + 3] = Base64EncodeMap[v & 0x3F]; - } - else - { + } else { out[j + 3] = '='; } } @@ -135,11 +103,11 @@ static int Base64IsValidChar(char c) { return (c >= '0' && c <= '9') || - (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - (c == '+') || - (c == '/') || - (c == '='); + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c == '+') || + (c == '/') || + (c == '='); } char * @@ -158,11 +126,11 @@ Base64Decode(const char *input, size_t len) outLen = Base64DecodedSize(input, len); if (len % 4) { - /* Invalid length; must have incorrect padding */ + /* Invalid length; must have incorrect padding */ return NULL; } - /* Scan for invalid characters. */ + /* Scan for invalid characters. */ for (i = 0; i < len; i++) { if (!Base64IsValidChar(input[i])) @@ -171,7 +139,7 @@ Base64Decode(const char *input, size_t len) } } - out = Malloc(outLen + 1); + out = malloc(outLen + 1); if (!out) { return NULL; @@ -179,8 +147,7 @@ Base64Decode(const char *input, size_t len) out[outLen] = '\0'; - for (i = 0, j = 0; i < len; i += 4, j += 3) - { + 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]; @@ -221,17 +188,17 @@ Base64Pad(char **base64Ptr, size_t length) if (length % 4 == 0) { - return length; /* Success: no padding needed */ + return length; /* Success: no padding needed */ } newSize = length + (4 - (length % 4)); - tmp = Realloc(*base64Ptr, newSize + 100);; + tmp = realloc(*base64Ptr, newSize + 100);; if (!tmp) { - return 0; /* Memory error */ + return 0; /* Memory error */ } - *base64Ptr = tmp; + *base64Ptr = tmp; for (i = length; i < newSize; i++) { @@ -242,3 +209,4 @@ Base64Pad(char **base64Ptr, size_t length) return newSize; } + diff --git a/src/Config.c b/src/Config.c index 8dabc94..57b06b2 100644 --- a/src/Config.c +++ b/src/Config.c @@ -1,30 +1,5 @@ -/* - * Copyright (C) 2022 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 -#include - #include #include #include @@ -33,24 +8,20 @@ #define CONFIG_BUFFER_BLOCK 32 #endif -struct ConfigDirective -{ +struct ConfigDirective { Array *values; HashMap *children; }; -struct ConfigParseResult -{ - unsigned int ok:1; - union - { +struct ConfigParseResult { + unsigned int ok : 1; + union { size_t lineNumber; HashMap *confMap; } data; }; -typedef enum ConfigToken -{ +typedef enum ConfigToken { TOKEN_UNKNOWN, TOKEN_NAME, TOKEN_MACRO_ASSIGNMENT, @@ -62,14 +33,13 @@ typedef enum ConfigToken TOKEN_EOF } ConfigToken; -typedef struct ConfigParserState -{ - FILE *stream; - unsigned int line; +typedef struct ConfigParserState { + FILE *stream; + unsigned int line; - char *token; - size_t tokenSize; - size_t tokenLen; + char *token; + size_t tokenSize; + size_t tokenLen; ConfigToken tokenType; HashMap *macroMap; @@ -77,48 +47,54 @@ typedef struct ConfigParserState } ConfigParserState; unsigned int -ConfigParseResultOk(ConfigParseResult * result) +ConfigParseResultOk(ConfigParseResult *result) { return result ? result->ok : 0; } size_t -ConfigParseResultLineNumber(ConfigParseResult * result) +ConfigParseResultLineNumber(ConfigParseResult *result) { return result && !result->ok ? result->data.lineNumber : 0; } HashMap * -ConfigParseResultGet(ConfigParseResult * result) +ConfigParseResultGet(ConfigParseResult *result) { return result && result->ok ? result->data.confMap : NULL; } void -ConfigParseResultFree(ConfigParseResult * result) +ConfigParseResultFree(ConfigParseResult *result) { /* * Note that if the parse was valid, the hash map * needs to be freed separately. */ - Free(result); + free(result); } Array * -ConfigValuesGet(ConfigDirective * directive) +ConfigValuesGet(ConfigDirective *directive) { return directive ? directive->values : NULL; } HashMap * -ConfigChildrenGet(ConfigDirective * directive) +ConfigChildrenGet(ConfigDirective *directive) { return directive ? directive->children : NULL; } +/* + * Takes a void pointer because it is only used with + * HashMapIterate(), which requires a pointer to a function + * that takes a void pointer. + */ static void -ConfigDirectiveFree(ConfigDirective * directive) +ConfigDirectiveFree(void *ptr) { + ConfigDirective *directive = ptr; size_t i; if (!directive) @@ -128,36 +104,26 @@ ConfigDirectiveFree(ConfigDirective * directive) for (i = 0; i < ArraySize(directive->values); i++) { - Free(ArrayGet(directive->values, i)); + free(ArrayGet(directive->values, i)); } ArrayFree(directive->values); - ConfigFree(directive->children); - Free(directive); + free(directive); } void -ConfigFree(HashMap * conf) +ConfigFree(HashMap *conf) { - char *key; - void *value; - - while (HashMapIterate(conf, &key, &value)) - { - ConfigDirectiveFree((ConfigDirective *) value); - Free(key); - } - + HashMapIterate(conf, ConfigDirectiveFree); HashMapFree(conf); } static ConfigParserState * ConfigParserStateCreate(FILE * stream) { - ConfigParserState *state = Malloc(sizeof(ConfigParserState)); - + ConfigParserState *state = malloc(sizeof(ConfigParserState)); if (!state) { return NULL; @@ -167,7 +133,7 @@ ConfigParserStateCreate(FILE * stream) if (!state->macroMap) { - Free(state); + free(state); return NULL; } @@ -182,28 +148,19 @@ ConfigParserStateCreate(FILE * stream) } static void -ConfigParserStateFree(ConfigParserState * state) +ConfigParserStateFree(ConfigParserState *state) { - char *key; - void *value; - if (!state) { return; } + free(state->token); - Free(state->token); - - while (HashMapIterate(state->macroMap, &key, &value)) - { - Free(key); - Free(value); - } - + HashMapIterate(state->macroMap, free); HashMapFree(state->macroMap); - Free(state); + free(state); } static int @@ -213,10 +170,9 @@ ConfigIsNameChar(int c) } static char -ConfigConsumeWhitespace(ConfigParserState * state) +ConfigConsumeWhitespace(ConfigParserState *state) { int c; - while (isspace(c = fgetc(state->stream))) { if (c == '\n') @@ -228,24 +184,22 @@ ConfigConsumeWhitespace(ConfigParserState * state) } static void -ConfigConsumeLine(ConfigParserState * state) +ConfigConsumeLine(ConfigParserState *state) { while (fgetc(state->stream) != '\n'); state->line++; } static void -ConfigTokenSeek(ConfigParserState * state) +ConfigTokenSeek(ConfigParserState *state) { int c; /* If we already hit EOF, don't do anything */ - if (state->tokenType == TOKEN_EOF) - { + if (state->tokenType == TOKEN_EOF) { return; } - while ((c = ConfigConsumeWhitespace(state)) == '#') - { + while ((c = ConfigConsumeWhitespace(state)) == '#') { ConfigConsumeLine(state); } @@ -254,59 +208,47 @@ ConfigTokenSeek(ConfigParserState * state) * token by looking at the next character */ - if (feof(state->stream)) - { + if (feof(state->stream)) { state->tokenType = TOKEN_EOF; return; } - if (ConfigIsNameChar(c)) - { + if (ConfigIsNameChar(c)) { state->tokenLen = 0; /* Read the key/macro into state->token */ - if (!state->token) - { + if (!state->token) { state->tokenSize = CONFIG_BUFFER_BLOCK; - state->token = Malloc(CONFIG_BUFFER_BLOCK); + state->token = malloc(CONFIG_BUFFER_BLOCK); } state->token[state->tokenLen] = c; state->tokenLen++; - while (ConfigIsNameChar((c = fgetc(state->stream)))) - { + while (ConfigIsNameChar((c = fgetc(state->stream)))) { state->token[state->tokenLen] = c; state->tokenLen++; - if (state->tokenLen >= state->tokenSize) - { + if (state->tokenLen >= state->tokenSize) { state->tokenSize += CONFIG_BUFFER_BLOCK; - state->token = Realloc(state->token, - state->tokenSize); + state->token = realloc(state->token, + state->tokenSize); } } state->token[state->tokenLen] = '\0'; state->tokenLen++; - if (!isspace(c)) - { + if (!isspace(c)) { state->tokenType = TOKEN_UNKNOWN; - } - else - { + } else { state->tokenType = TOKEN_NAME; - if (c == '\n') - { + if (c == '\n') { state->line++; } } - } - else - { - switch (c) - { + } else { + switch (c) { case '=': state->tokenType = TOKEN_MACRO_ASSIGNMENT; break; @@ -315,29 +257,25 @@ ConfigTokenSeek(ConfigParserState * state) state->tokenType = TOKEN_VALUE; /* read the value into state->curtok */ - while ((c = fgetc(state->stream)) != '"') - { - if (c == '\n') - { + while ((c = fgetc(state->stream)) != '"') { + if (c == '\n') { state->line++; } /* * End of the stream reached without finding * a closing quote */ - if (feof(state->stream)) - { + if (feof(state->stream)) { state->tokenType = TOKEN_EOF; break; } state->token[state->tokenLen] = c; state->tokenLen++; - if (state->tokenLen >= state->tokenSize) - { + if (state->tokenLen >= state->tokenSize) { state->tokenSize += CONFIG_BUFFER_BLOCK; - state->token = Realloc(state->token, - state->tokenSize); + state->token = realloc(state->token, + state->tokenSize); } } state->token[state->tokenLen] = '\0'; @@ -355,16 +293,14 @@ ConfigTokenSeek(ConfigParserState * state) case '$': state->tokenLen = 0; /* read the macro name into state->curtok */ - while (ConfigIsNameChar(c = fgetc(state->stream))) - { + while (ConfigIsNameChar(c = fgetc(state->stream))) { state->token[state->tokenLen] = c; state->tokenLen++; - if (state->tokenLen >= state->tokenSize) - { + if (state->tokenLen >= state->tokenSize) { state->tokenSize += CONFIG_BUFFER_BLOCK; - state->token = Realloc(state->token, - state->tokenSize); + state->token = realloc(state->token, + state->tokenSize); } } state->token[state->tokenLen] = '\0'; @@ -380,81 +316,67 @@ ConfigTokenSeek(ConfigParserState * state) } /* Resize curtok to only use the bytes it needs */ - if (state->tokenLen) - { + if (state->tokenLen) { state->tokenSize = state->tokenLen; - state->token = Realloc(state->token, state->tokenSize); + state->token = realloc(state->token, state->tokenSize); } } static int -ConfigExpect(ConfigParserState * state, ConfigToken tokenType) +ConfigExpect(ConfigParserState *state, ConfigToken tokenType) { return state->tokenType == tokenType; } static HashMap * -ConfigParseBlock(ConfigParserState * state, int level) +ConfigParseBlock(ConfigParserState *state, int level) { HashMap *block = HashMapCreate(); ConfigTokenSeek(state); - while (ConfigExpect(state, TOKEN_NAME)) - { - char *name = Malloc(state->tokenLen + 1); - + while (ConfigExpect(state, TOKEN_NAME)) { + char *name = malloc(state->tokenLen + 1); strcpy(name, state->token); ConfigTokenSeek(state); - if (ConfigExpect(state, TOKEN_VALUE) || ConfigExpect(state, TOKEN_MACRO)) - { + if (ConfigExpect(state, TOKEN_VALUE) || ConfigExpect(state, TOKEN_MACRO)) { ConfigDirective *directive; - directive = Malloc(sizeof(ConfigDirective)); + directive = malloc(sizeof(ConfigDirective)); directive->children = NULL; directive->values = ArrayCreate(); while (ConfigExpect(state, TOKEN_VALUE) || - ConfigExpect(state, TOKEN_MACRO)) - { + ConfigExpect(state, TOKEN_MACRO)) { char *dval; char *dvalCpy; - if (ConfigExpect(state, TOKEN_VALUE)) - { + if (ConfigExpect(state, TOKEN_VALUE)) { dval = state->token; - } - else if (ConfigExpect(state, TOKEN_MACRO)) - { + } else if (ConfigExpect(state, TOKEN_MACRO)) { dval = HashMapGet(state->macroMap, state->token); - if (!dval) - { + if (!dval) { goto error; } - } - else - { - dval = NULL; /* Should never happen */ + } else { + dval = NULL; /* Should never happen */ } - /* dval is a pointer which is overwritten with the next - * token. */ - dvalCpy = Malloc(strlen(dval) + 1); + /* dval is a pointer which is overwritten with the next token. */ + dvalCpy = malloc(strlen(dval) + 1); strcpy(dvalCpy, dval); ArrayAdd(directive->values, dvalCpy); ConfigTokenSeek(state); } - if (ConfigExpect(state, TOKEN_BLOCK_OPEN)) - { + if (ConfigExpect(state, TOKEN_BLOCK_OPEN)) { /* token_seek(state); */ directive->children = ConfigParseBlock(state, level + 1); - if (!directive->children) - { + if (!directive->children) { goto error; } } @@ -469,49 +391,37 @@ ConfigParseBlock(ConfigParserState * state, int level) * NULL is sent to ConfigDirectiveFree(), making it a no-op. */ ConfigDirectiveFree(HashMapSet(block, name, directive)); - } - else if (ConfigExpect(state, TOKEN_MACRO_ASSIGNMENT)) - { - ConfigTokenSeek(state); - if (ConfigExpect(state, TOKEN_VALUE)) - { - char *valueCopy = Malloc(strlen(state->token) + 1); + } else if (ConfigExpect(state, TOKEN_MACRO_ASSIGNMENT)) { + ConfigTokenSeek(state); + if (ConfigExpect(state, TOKEN_VALUE)) { + char * valueCopy = malloc(strlen(state->token) + 1); strcpy(valueCopy, state->token); - Free(HashMapSet(state->macroMap, name, valueCopy)); + free(HashMapSet(state->macroMap, name, state->token)); ConfigTokenSeek(state); - } - else - { + } else { goto error; } - } - else - { + } else { goto error; } - if (!ConfigExpect(state, TOKEN_SEMICOLON)) - { + if (!ConfigExpect(state, TOKEN_SEMICOLON)) { goto error; } ConfigTokenSeek(state); } - if (ConfigExpect(state, level ? TOKEN_BLOCK_CLOSE : TOKEN_EOF)) - { + if (ConfigExpect(state, level ? TOKEN_BLOCK_CLOSE : TOKEN_EOF)) { ConfigTokenSeek(state); return block; - } - else - { + } else { goto error; } -error: + error: /* Only free the very top level, because this will recurse */ - if (!level) - { + if (!level) { ConfigFree(block); } return NULL; @@ -524,17 +434,14 @@ ConfigParse(FILE * stream) HashMap *conf; ConfigParserState *state; - result = Malloc(sizeof(ConfigParseResult)); + result = malloc(sizeof(ConfigParseResult)); state = ConfigParserStateCreate(stream); conf = ConfigParseBlock(state, 0); - if (!conf) - { + if (!conf) { result->ok = 0; result->data.lineNumber = state->line; - } - else - { + } else { result->ok = 1; result->data.confMap = conf; } @@ -542,3 +449,4 @@ ConfigParse(FILE * stream) ConfigParserStateFree(state); return result; } + diff --git a/src/HashMap.c b/src/HashMap.c index e09a5f7..351c4a2 100644 --- a/src/HashMap.c +++ b/src/HashMap.c @@ -1,61 +1,31 @@ -/* - * Copyright (C) 2022 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 -#include - +#include #include -#include +#include -typedef struct HashMapBucket -{ - unsigned long hash; - char *key; +typedef struct HashMapBucket { + uint32_t hash; void *value; } HashMapBucket; -struct HashMap -{ +struct HashMap { size_t count; size_t capacity; HashMapBucket **entries; - unsigned long (*hashFunc) (const char *); - float maxLoad; - size_t iterator; }; -static unsigned long +static uint32_t HashMapHashKey(const char *key) { - unsigned long hash = 2166136261u; + uint32_t hash = 2166136261u; size_t i = 0; while (key[i]) { - hash ^= (unsigned char) key[i]; + hash ^= (uint8_t) key[i]; hash *= 16777619; i++; @@ -64,8 +34,9 @@ HashMapHashKey(const char *key) return hash; } + static int -HashMapGrow(HashMap * map) +HashMapGrow(HashMap *map) { size_t oldCapacity; size_t i; @@ -79,15 +50,12 @@ HashMapGrow(HashMap * map) oldCapacity = map->capacity; map->capacity *= 2; - newEntries = Malloc(map->capacity * sizeof(HashMapBucket *)); + newEntries = calloc(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 */ @@ -102,7 +70,7 @@ HashMapGrow(HashMap * map) { if (!newEntries[index]->hash) { - Free(newEntries[index]); + free(newEntries[index]); newEntries[index] = map->entries[i]; break; } @@ -119,11 +87,11 @@ HashMapGrow(HashMap * map) else { /* Either NULL or a tombstone */ - Free(map->entries[i]); + free(map->entries[i]); } } - Free(map->entries); + free(map->entries); map->entries = newEntries; return 1; } @@ -131,8 +99,7 @@ HashMapGrow(HashMap * map) HashMap * HashMapCreate(void) { - HashMap *map = Malloc(sizeof(HashMap)); - + HashMap *map = malloc(sizeof(HashMap)); if (!map) { return NULL; @@ -141,25 +108,21 @@ HashMapCreate(void) map->maxLoad = 0.75; map->count = 0; map->capacity = 16; - map->iterator = 0; - map->hashFunc = HashMapHashKey; - map->entries = Malloc(map->capacity * sizeof(HashMapBucket *)); + map->entries = calloc(map->capacity, sizeof(HashMapBucket *)); if (!map->entries) { - Free(map); + free(map); return NULL; } - memset(map->entries, 0, map->capacity * sizeof(HashMapBucket *)); - return map; } void * -HashMapDelete(HashMap * map, const char *key) +HashMapDelete(HashMap *map, const char *key) { - unsigned long hash; + uint32_t hash; size_t index; if (!map || !key) @@ -167,7 +130,7 @@ HashMapDelete(HashMap * map, const char *key) return NULL; } - hash = map->hashFunc(key); + hash = HashMapHashKey(key); index = hash % map->capacity; for (;;) @@ -192,7 +155,7 @@ HashMapDelete(HashMap * map, const char *key) } void -HashMapFree(HashMap * map) +HashMapFree(HashMap *map) { if (map) { @@ -202,18 +165,18 @@ HashMapFree(HashMap * map) { if (map->entries[i]) { - Free(map->entries[i]); + free(map->entries[i]); } } - Free(map->entries); - Free(map); } + + free(map); } void * -HashMapGet(HashMap * map, const char *key) +HashMapGet(HashMap *map, const char *key) { - unsigned long hash; + uint32_t hash; size_t index; if (!map || !key) @@ -221,7 +184,7 @@ HashMapGet(HashMap * map, const char *key) return NULL; } - hash = map->hashFunc(key); + hash = HashMapHashKey(key); index = hash % map->capacity; for (;;) @@ -244,44 +207,31 @@ HashMapGet(HashMap * map, const char *key) return NULL; } -int -HashMapIterate(HashMap * map, char **key, void **value) +void +HashMapIterate(HashMap *map, void (*iteratorFunc)(void *)) { + size_t i; + if (!map) { - return 0; + return; } - if (map->iterator >= map->capacity) + for (i = 0; i < map->capacity; i++) { - map->iterator = 0; - *key = NULL; - *value = NULL; - return 0; - } - - while (map->iterator < map->capacity) - { - HashMapBucket *bucket = map->entries[map->iterator]; - - map->iterator++; + HashMapBucket *bucket = map->entries[i]; if (bucket) { - *key = bucket->key; - *value = bucket->value; - return 1; + iteratorFunc(bucket->value); } } - - map->iterator = 0; - return 0; } void -HashMapMaxLoadSet(HashMap * map, float load) +HashMapMaxLoadSet(HashMap *map, float load) { - if (!map || (load > 1.0 || load <= 0)) + if (!map) { return; } @@ -289,21 +239,11 @@ HashMapMaxLoadSet(HashMap * map, float load) 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) +HashMapSet(HashMap *map, const char *key, void *value) { - unsigned long hash; + uint32_t hash; size_t index; if (!map || !key || !value) @@ -316,7 +256,7 @@ HashMapSet(HashMap * map, char *key, void *value) HashMapGrow(map); } - hash = map->hashFunc(key); + hash = HashMapHashKey(key); index = hash % map->capacity; for (;;) @@ -325,14 +265,13 @@ HashMapSet(HashMap * map, char *key, void *value) if (!bucket) { - bucket = Malloc(sizeof(HashMapBucket)); + bucket = malloc(sizeof(HashMapBucket)); if (!bucket) { break; } bucket->hash = hash; - bucket->key = key; bucket->value = value; map->entries[index] = bucket; map->count++; @@ -342,7 +281,6 @@ HashMapSet(HashMap * map, char *key, void *value) if (!bucket->hash) { bucket->hash = hash; - bucket->key = key; bucket->value = value; break; } @@ -350,7 +288,6 @@ HashMapSet(HashMap * map, char *key, void *value) if (bucket->hash == hash) { void *oldValue = bucket->value; - bucket->value = value; return oldValue; } @@ -361,16 +298,3 @@ HashMapSet(HashMap * map, char *key, void *value) return NULL; } -void -HashMapIterateFree(char *key, void *value) -{ - if (key) - { - Free(key); - } - - if (value) - { - Free(value); - } -} diff --git a/src/Log.c b/src/Log.c index 7b62d28..c33d29a 100644 --- a/src/Log.c +++ b/src/Log.c @@ -1,210 +1,25 @@ -/* - * Copyright (C) 2022 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 -#include - -#include +#include #include #include #include #include -#include #define LOG_TSBUFFER 64 -struct LogConfig -{ - int level; +struct LogConfig { + LogLevel level; size_t indent; FILE *out; int flags; char *tsFmt; - - pthread_mutex_t lock; }; -LogConfig * -LogConfigCreate(void) -{ - LogConfig *config; - - config = Malloc(sizeof(LogConfig)); - - if (!config) - { - return NULL; - } - - memset(config, 0, sizeof(LogConfig)); - - LogConfigLevelSet(config, LOG_MESSAGE); - 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; -} - void -LogConfigFlagClear(LogConfig * config, int flags) +Log(LogConfig *config, LogLevel level, const char *msg, ...) { - 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; - } - - fclose(config->out); - Free(config); -} - -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_ERROR: - case LOG_WARNING: - case LOG_MESSAGE: - case LOG_DEBUG: - config->level = level; - default: - break; - } -} - -void -LogConfigOutputSet(LogConfig * config, FILE * out) -{ - if (!config) - { - return; - } - - if (out) - { - config->out = out; - } - else - { - config->out = stdout; - } - -} - -void -LogConfigTimeStampFormatSet(LogConfig * config, char *tsFmt) -{ - if (config) - { - config->tsFmt = tsFmt; - } -} - -void -LogConfigUnindent(LogConfig * config) -{ - if (config && config->indent >= 2) - { - config->indent -= 2; - } -} - -void -Log(LogConfig * config, int level, const char *msg,...) -{ - size_t i; + int i; int doColor; char indicator; va_list argp; @@ -226,32 +41,20 @@ Log(LogConfig * config, int level, const char *msg,...) return; } - pthread_mutex_lock(&config->lock); - - if (LogConfigFlagGet(config, LOG_FLAG_SYSLOG)) + for (i = 0; i < config->indent; i++) { - /* No further print logic is needed; syslog will handle it all - * for us. */ - va_start(argp, msg); - vsyslog(level, msg, argp); - va_end(argp); - pthread_mutex_unlock(&config->lock); - return; + fputc(' ', config->out); } doColor = LogConfigFlagGet(config, LOG_FLAG_COLOR) - && isatty(fileno(config->out)); + && isatty(fileno(config->out)); if (doColor) { char *ansi; - switch (level) { - case LOG_EMERG: - case LOG_ALERT: - case LOG_CRIT: - case LOG_ERR: + case LOG_ERROR: /* Bold Red */ ansi = "\033[1;31m"; break; @@ -259,11 +62,11 @@ Log(LogConfig * config, int level, const char *msg,...) /* Bold Yellow */ ansi = "\033[1;33m"; break; - case LOG_NOTICE: + case LOG_TASK: /* Bold Magenta */ ansi = "\033[1;35m"; break; - case LOG_INFO: + case LOG_MESSAGE: /* Bold Green */ ansi = "\033[1;32m"; break; @@ -293,7 +96,7 @@ Log(LogConfig * config, int level, const char *msg,...) if (tsLength) { fputs(tsBuffer, config->out); - if (!isspace((unsigned char) tsBuffer[tsLength - 1])) + if (!isspace(tsBuffer[tsLength - 1])) { fputc(' ', config->out); } @@ -302,15 +105,6 @@ Log(LogConfig * config, int level, const char *msg,...) switch (level) { - case LOG_EMERG: - indicator = '#'; - break; - case LOG_ALERT: - indicator = '@'; - break; - case LOG_CRIT: - indicator = 'X'; - break; case LOG_ERROR: indicator = 'x'; break; @@ -327,7 +121,7 @@ Log(LogConfig * config, int level, const char *msg,...) indicator = '*'; break; default: - indicator = '?'; + indicator = ' '; break; } @@ -340,23 +134,177 @@ Log(LogConfig * config, int level, const char *msg,...) } fputc(' ', config->out); - for (i = 0; i < config->indent; i++) - { - fputc(' ', config->out); - } va_start(argp, msg); vfprintf(config->out, msg, argp); fputc('\n', config->out); va_end(argp); - /* If we are debugging, there might be something that's going to - * segfault the program coming up, so flush the output stream - * immediately. */ + /* If we are debugging, there might be something that's + * going to segfault the program coming up, so flush the + * output stream immediately. + */ if (config->level == LOG_DEBUG) { fflush(config->out); } - - pthread_mutex_unlock(&config->lock); +} + +LogConfig * +LogConfigCreate(void) +{ + LogConfig *config; + + config = calloc(1, sizeof(LogConfig)); + + if (!config) + { + return NULL; + } + + LogConfigLevelSet(config, LOG_MESSAGE); + 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; +} + +void +LogConfigFlagClear(LogConfig *config, int flags) +{ + if (!config) + { + return; + } + + config->flags &= ~flags; +} + +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) +{ + free(config); +} + +void +LogConfigIndent(LogConfig *config) +{ + if (config) + { + config->indent += 2; + } +} + +size_t +LogConfigIndentGet(LogConfig *config) +{ + if (!config) + { + return 0; + } + + return config->indent; +} + +void +LogConfigIndentSet(LogConfig *config, size_t indent) +{ + if (!config) + { + return; + } + + config->indent = indent; +} + +LogLevel +LogConfigLevelGet(LogConfig *config) +{ + if (!config) + { + return -1; + } + + return config->level; +} + +void +LogConfigLevelSet(LogConfig *config, LogLevel level) +{ + if (!config) + { + return; + } + + switch (level) + { + case LOG_ERROR: + case LOG_WARNING: + case LOG_MESSAGE: + case LOG_DEBUG: + config->level = level; + default: + break; + } +} + +void +LogConfigOutputSet(LogConfig *config, FILE *out) +{ + if (!config) + { + return; + } + + if (out) + { + config->out = out; + } + else + { + config->out = stdout; + } + +} + +void +LogConfigTimeStampFormatSet(LogConfig *config, char *tsFmt) +{ + if (config) + { + config->tsFmt = tsFmt; + } +} + +void +LogConfigUnindent(LogConfig *config) +{ + if (config && config->indent >= 2) + { + config->indent -= 2; + } } diff --git a/src/Telodendria.c b/src/Telodendria.c index d0ff212..f5290bb 100644 --- a/src/Telodendria.c +++ b/src/Telodendria.c @@ -1,108 +1,21 @@ -/* - * Copyright (C) 2022 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 #include #include #include -#include -#include -#include -#include - -#include -#include #include #include #include -#include -#include -#include - -static void -TelodendriaMemoryHook(MemoryAction a, MemoryInfo * i, void *args) -{ - LogConfig *lc = (LogConfig *) args; - char *action; - - switch (a) - { - case MEMORY_ALLOCATE: - action = "Allocated"; - break; - case MEMORY_REALLOCATE: - action = "Re-allocated"; - break; - case MEMORY_FREE: - action = "Freed"; - break; - case MEMORY_BAD_POINTER: - action = "Bad pointer to"; - break; - default: - action = "Unknown operation on"; - break; - } - - Log(lc, LOG_DEBUG, "%s:%d: %s %lu bytes of memory at %p.", - MemoryInfoGetFile(i), MemoryInfoGetLine(i), - action, MemoryInfoGetSize(i), - MemoryInfoGetPointer(i)); -} - -static void -TelodendriaMemoryIterator(MemoryInfo * i, void *args) -{ - LogConfig *lc = (LogConfig *) args; - - /* We haven't freed the logger memory yet */ - if (MemoryInfoGetPointer(i) != lc) - { - Log(lc, LOG_WARNING, "%s:%d: %lu bytes of memory at %p leaked.", - MemoryInfoGetFile(i), MemoryInfoGetLine(i), - MemoryInfoGetSize(i), MemoryInfoGetPointer(i)); - } -} - -static HttpServer *httpServer = NULL; - -static void -TelodendriaSignalHandler(int signalNo) -{ - (void) signalNo; - HttpServerStop(httpServer); -} +#include typedef enum ArgFlag { ARG_VERSION = (1 << 0), - ARG_CONFIGTEST = (1 << 1), - ARG_VERBOSE = (1 << 2) + ARG_USAGE = (1 << 1) } ArgFlag; static void -TelodendriaPrintHeader(LogConfig * lc) +TelodendriaPrintHeader(LogConfig *lc) { Log(lc, LOG_MESSAGE, " _____ _ _ _ _"); @@ -113,18 +26,26 @@ TelodendriaPrintHeader(LogConfig * lc) Log(lc, LOG_MESSAGE, " | | __/ | (_) | (_| | __/ | | | (_| | | | | (_| |"); Log(lc, LOG_MESSAGE, - " |_|\\___|_|\\___/ \\__,_|\\___|_| |_|\\__,_|_| |_|\\__,_|"); + " |_|\\___|_|\\___/ \\__,_|\\___|_| |_|\\__,_|_| |_|\\__,_|"); Log(lc, LOG_MESSAGE, "Telodendria v" TELODENDRIA_VERSION); Log(lc, LOG_MESSAGE, ""); Log(lc, LOG_MESSAGE, "Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>"); Log(lc, LOG_MESSAGE, - "Documentation/Support: https://telodendria.io"); + "Documentation/Support: https://bancino.net/pub/Telodendria"); Log(lc, LOG_MESSAGE, ""); } -int -main(int argc, char **argv) +static void +TelodendriaPrintUsage(LogConfig *lc) +{ + Log(lc, LOG_MESSAGE, "Usage:"); + Log(lc, LOG_MESSAGE, " -c Configuration file ('-' for stdin)."); + Log(lc, LOG_MESSAGE, " -V Print the header, then exit."); + Log(lc, LOG_MESSAGE, " -h Print this usage, then exit."); +} + +int main(int argc, char **argv) { LogConfig *lc; int exit = EXIT_SUCCESS; @@ -132,69 +53,38 @@ main(int argc, char **argv) /* Arg parsing */ int opt; int flags = 0; - char *configArg = "/etc/telodendria.conf"; + char *configArg = NULL; /* Config file */ FILE *configFile = NULL; ConfigParseResult *configParseResult = NULL; HashMap *config = NULL; - /* Program configuration */ - TelodendriaConfig *tConfig = NULL; - - /* User validation */ - struct passwd *userInfo; - struct group *groupInfo; - - /* Signal handling */ - struct sigaction sigAction; - - MatrixHttpHandlerArgs matrixArgs; - - memset(&matrixArgs, 0, sizeof(matrixArgs)); - lc = LogConfigCreate(); + /* TODO: Remove */ + LogConfigLevelSet(lc, LOG_DEBUG); + if (!lc) { printf("Fatal error: unable to allocate memory for logger.\n"); return EXIT_FAILURE; } - MemoryHook(TelodendriaMemoryHook, lc); - TelodendriaPrintHeader(lc); -#ifdef __OpenBSD__ - Log(lc, LOG_DEBUG, "Attempting pledge..."); - - if (pledge("stdio rpath wpath cpath inet dns getpw id unveil", NULL) != 0) - { - Log(lc, LOG_ERROR, "Pledge failed: %s", strerror(errno)); - exit = EXIT_FAILURE; - goto finish; - } -#endif - - while ((opt = getopt(argc, argv, "f:Vvn")) != -1) + while ((opt = getopt(argc, argv, "c:Vh")) != -1) { switch (opt) { - case 'f': + case 'c': configArg = optarg; break; case 'V': flags |= ARG_VERSION; break; - case 'v': - flags |= ARG_VERBOSE; - break; - case 'n': - flags |= ARG_CONFIGTEST; - break; - case '?': - exit = EXIT_FAILURE; - goto finish; + case 'h': + flags |= ARG_USAGE; default: break; } @@ -205,21 +95,26 @@ main(int argc, char **argv) goto finish; } + if (flags & ARG_USAGE) + { + TelodendriaPrintUsage(lc); + goto finish; + } + + if (!configArg) + { + Log(lc, LOG_ERROR, "No configuration file specified."); + TelodendriaPrintUsage(lc); + exit = EXIT_FAILURE; + goto finish; + } + if (strcmp(configArg, "-") == 0) { - configFile = stdin; + configFile = stdout; } else { - fclose(stdin); -#ifdef __OpenBSD__ - if (unveil(configArg, "r") != 0) - { - Log(lc, LOG_ERROR, "Unable to unveil() configuration file '%s' for reading.", configArg); - exit = EXIT_FAILURE; - goto finish; - } -#endif configFile = fopen(configArg, "r"); if (!configFile) { @@ -229,9 +124,11 @@ main(int argc, char **argv) } } - Log(lc, LOG_TASK, "Processing configuration file '%s'.", configArg); + Log(lc, LOG_MESSAGE, "Using configuration file '%s'.", configArg); + /* Read config here */ configParseResult = ConfigParse(configFile); + if (!ConfigParseResultOk(configParseResult)) { Log(lc, LOG_ERROR, "Syntax error on line %d.", @@ -243,257 +140,18 @@ main(int argc, char **argv) config = ConfigParseResultGet(configParseResult); ConfigParseResultFree(configParseResult); + Log(lc, LOG_DEBUG, "Closing configuration file."); fclose(configFile); - tConfig = TelodendriaConfigParse(config, lc); - if (!tConfig) - { - exit = EXIT_FAILURE; - goto finish; - } - - ConfigFree(config); - - if (flags & ARG_CONFIGTEST) - { - Log(lc, LOG_MESSAGE, "Configuration is OK."); - goto finish; - } - -#ifdef __OpenBSD__ - if (unveil(tConfig->dataDir, "rwc") != 0) - { - Log(lc, LOG_ERROR, "Unveil of data directory failed: %s", strerror(errno)); - exit = EXIT_FAILURE; - goto finish; - } - - unveil(NULL, NULL); /* Done with unveil(), so disable it */ -#endif - - LogConfigTimeStampFormatSet(lc, tConfig->logTimestamp); - - if (tConfig->flags & TELODENDRIA_LOG_COLOR) - { - LogConfigFlagSet(lc, LOG_FLAG_COLOR); - } - else - { - LogConfigFlagClear(lc, LOG_FLAG_COLOR); - } - - LogConfigLevelSet(lc, flags & ARG_VERBOSE ? LOG_DEBUG : tConfig->logLevel); - - if (chdir(tConfig->dataDir) != 0) - { - Log(lc, LOG_ERROR, "Unable to change into data directory: %s.", strerror(errno)); - exit = EXIT_FAILURE; - goto finish; - } - else - { - Log(lc, LOG_DEBUG, "Changed working directory to: %s", tConfig->dataDir); - } - - - if (tConfig->flags & TELODENDRIA_LOG_FILE) - { - FILE *logFile = fopen("telodendria.log", "a"); - - if (!logFile) - { - Log(lc, LOG_ERROR, "Unable to open log file for appending."); - exit = EXIT_FAILURE; - goto finish; - } - - Log(lc, LOG_MESSAGE, "Logging to the log file. Check there for all future messages."); - LogConfigOutputSet(lc, logFile); - } - else if (tConfig->flags & TELODENDRIA_LOG_STDOUT) - { - Log(lc, LOG_DEBUG, "Already logging to standard output."); - } - else if (tConfig->flags & TELODENDRIA_LOG_SYSLOG) - { - Log(lc, LOG_MESSAGE, "Logging to the syslog. Check there for all future messages."); - LogConfigFlagSet(lc, LOG_FLAG_SYSLOG); - - openlog("telodendria", LOG_PID | LOG_NDELAY, LOG_DAEMON); - /* Always log everything, because the Log API will control what - * messages get passed to the syslog */ - setlogmask(LOG_UPTO(LOG_DEBUG)); - } - else - { - Log(lc, LOG_ERROR, "Unknown logging method in flags: '%d'", tConfig->flags); - Log(lc, LOG_ERROR, "This is a programmer error; please report it."); - exit = EXIT_FAILURE; - goto finish; - } - - Log(lc, LOG_DEBUG, "Configuration:"); - LogConfigIndent(lc); - Log(lc, LOG_DEBUG, "Listen On: %d", tConfig->listenPort); - Log(lc, LOG_DEBUG, "Server Name: %s", tConfig->serverName); - Log(lc, LOG_DEBUG, "Base URL: %s", tConfig->baseUrl); - Log(lc, LOG_DEBUG, "Identity Server: %s", tConfig->identityServer); - Log(lc, LOG_DEBUG, "Run As: %s:%s", tConfig->uid, tConfig->gid); - Log(lc, LOG_DEBUG, "Data Directory: %s", tConfig->dataDir); - Log(lc, LOG_DEBUG, "Threads: %d", tConfig->threads); - Log(lc, LOG_DEBUG, "Max Connections: %d", tConfig->maxConnections); - Log(lc, LOG_DEBUG, "Max Cache: %ld", tConfig->maxCache); - Log(lc, LOG_DEBUG, "Flags: %x", tConfig->flags); - LogConfigUnindent(lc); - - Log(lc, LOG_DEBUG, "Running as uid:gid: %d:%d.", getuid(), getgid()); - - userInfo = getpwnam(tConfig->uid); - groupInfo = getgrnam(tConfig->gid); - - if (!userInfo || !groupInfo) - { - Log(lc, LOG_ERROR, "Unable to locate the user/group specified in the configuration."); - exit = EXIT_FAILURE; - goto finish; - } - else - { - Log(lc, LOG_DEBUG, "Found user/group information using getpwnam() and getgrnam()."); - } - - /* Arguments to pass into the HTTP handler */ - matrixArgs.lc = lc; - matrixArgs.config = tConfig; - - /* Bind the socket before possibly dropping permissions */ - httpServer = HttpServerCreate(tConfig->listenPort, tConfig->threads, tConfig->maxConnections, - MatrixHttpHandler, &matrixArgs); - if (!httpServer) - { - Log(lc, LOG_ERROR, "Unable to create HTTP server on port %d: %s", - tConfig->listenPort, strerror(errno)); - exit = EXIT_FAILURE; - goto finish; - } - - if (getuid() == 0) - { -#ifndef __OpenBSD__ - if (chroot(".") == 0) - { - Log(lc, LOG_DEBUG, "Changed the root directory to: %s.", tConfig->dataDir); - } - else - { - Log(lc, LOG_WARNING, "Unable to chroot into directory: %s.", tConfig->dataDir); - } -#else - Log(lc, LOG_DEBUG, "Not attempting chroot() after pledge() and unveil()."); -#endif - - if (setgid(groupInfo->gr_gid) != 0 || setuid(userInfo->pw_uid) != 0) - { - Log(lc, LOG_WARNING, "Unable to set process uid/gid."); - } - else - { - Log(lc, LOG_DEBUG, "Set uid/gid to %s:%s.", tConfig->uid, tConfig->gid); - } - } - else - { - Log(lc, LOG_DEBUG, "Not changing root directory, because we are not root."); - - if (getuid() != userInfo->pw_uid || getgid() != groupInfo->gr_gid) - { - Log(lc, LOG_WARNING, "Not running as the uid/gid specified in the configuration."); - } - else - { - Log(lc, LOG_DEBUG, "Running as the uid/gid specified in the configuration."); - } - } - - /* These config values are no longer needed; don't hold them in - * memory anymore */ - Free(tConfig->dataDir); - Free(tConfig->uid); - Free(tConfig->gid); - - tConfig->dataDir = NULL; - tConfig->uid = NULL; - tConfig->gid = NULL; - - matrixArgs.db = DbOpen(".", tConfig->maxCache); - - if (!tConfig->maxCache) - { - Log(lc, LOG_WARNING, "Max-cache is set to zero; caching is disabled."); - } - - if (!matrixArgs.db) - { - Log(lc, LOG_ERROR, "Unable to open data directory as a database."); - exit = EXIT_FAILURE; - goto finish; - } - - Log(lc, LOG_TASK, "Starting server..."); - - if (!HttpServerStart(httpServer)) - { - Log(lc, LOG_ERROR, "Unable to start HTTP server."); - exit = EXIT_FAILURE; - goto finish; - } - - Log(lc, LOG_MESSAGE, "Listening on port: %d", tConfig->listenPort); - - sigAction.sa_handler = TelodendriaSignalHandler; - sigfillset(&sigAction.sa_mask); - sigAction.sa_flags = SA_RESTART; - - if (sigaction(SIGINT, &sigAction, NULL) < 0) - { - Log(lc, LOG_ERROR, "Unable to install signal handler."); - exit = EXIT_FAILURE; - goto finish; - } - - /* Block this thread until the server is terminated by a signal - * handler */ - HttpServerJoin(httpServer); + /* Configure log file */ finish: - Log(lc, LOG_TASK, "Shutting down..."); - if (httpServer) + if (config) { - HttpServerFree(httpServer); - Log(lc, LOG_DEBUG, "Freed HTTP Server."); + Log(lc, LOG_DEBUG, "Freeing configuration structure."); + ConfigFree(config); } - - /* - * If we're not logging to standard output, then we can close it. Otherwise, - * if we are logging to stdout, LogConfigFree() will close it for us. - */ - if (!tConfig || !(tConfig->flags & TELODENDRIA_LOG_STDOUT)) - { - fclose(stdout); - } - - TelodendriaConfigFree(tConfig); - DbClose(matrixArgs.db); - - Log(lc, LOG_DEBUG, ""); - MemoryIterate(TelodendriaMemoryIterator, lc); - Log(lc, LOG_DEBUG, ""); - - Log(lc, LOG_DEBUG, "Exiting with code '%d'.", exit); + Log(lc, LOG_DEBUG, "Freeing log configuration and exiting with code '%d'.", exit); LogConfigFree(lc); - - MemoryFreeAll(); - - fclose(stderr); return exit; } diff --git a/src/include/Array.h b/src/include/Array.h index d699574..f377a18 100644 --- a/src/include/Array.h +++ b/src/include/Array.h @@ -1,27 +1,3 @@ -/* - * Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net> - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - #ifndef TELODENDRIA_ARRAY_H #define TELODENDRIA_ARRAY_H @@ -30,30 +6,27 @@ typedef struct Array Array; extern Array * - ArrayCreate(void); +ArrayCreate(void); extern size_t - ArraySize(Array *); +ArraySize(Array *array); extern void * - ArrayGet(Array *, size_t); +ArrayGet(Array *array, size_t index); extern int - ArrayInsert(Array *, void *, size_t); +ArrayInsert(Array *, void *value, size_t index); extern int - ArrayAdd(Array *, void *); +ArrayAdd(Array *array, void *value); extern void * - ArrayDelete(Array *, size_t); +ArrayDelete(Array *array, size_t index); extern void - ArraySort(Array *, int (*) (void *, void *)); - -extern void - ArrayFree(Array *); +ArrayFree(Array *array); extern int - ArrayTrim(Array *); +ArrayTrim(Array *array); -#endif /* TELODENDRIA_ARRAY_H */ +#endif diff --git a/src/include/Base64.h b/src/include/Base64.h index 428506e..d45d014 100644 --- a/src/include/Base64.h +++ b/src/include/Base64.h @@ -1,48 +1,25 @@ -/* - * Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net> - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - #ifndef TELODENDRIA_BASE64_H #define TELODENDRIA_BASE64_H #include extern size_t - Base64EncodedSize(size_t); +Base64EncodedSize(size_t inputSize); extern size_t - Base64DecodedSize(const char *, size_t); +Base64DecodedSize(const char *base64, size_t len); extern char * - Base64Encode(const char *, size_t); +Base64Encode(const char *input, size_t len); extern char * - Base64Decode(const char *, size_t); +Base64Decode(const char *input, size_t len); extern void - Base64Unpad(char *, size_t); +Base64Unpad(char *base64, size_t length); extern int - Base64Pad(char **, size_t); +Base64Pad(char **base64Ptr, size_t length); + +#endif -#endif /* TELODENDRIA_BASE64_H */ diff --git a/src/include/Config.h b/src/include/Config.h index 314f439..0b445b1 100644 --- a/src/include/Config.h +++ b/src/include/Config.h @@ -1,42 +1,3 @@ -/* - * Copyright (C) 2022 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. - */ - -/* - * Config.h: A heavily-modified version of Conifer2, a configuration - * file format specification and C parsing library written by Jordan - * Bancino. This library differs from Conifer2 in that the function - * naming convention has been updated to be consistent with Telodendria, - * and the underlying data structures have been overhauled to use the - * data structure libraries provided by Telodendria. - * - * Conifer2 was originally a learning project. It was very thoroughly - * debugged, however, and the configuration syntax was elegant, - * certainly more elegant than using JSON for a configuration file, - * so it was chosen to be the format for Telodendria's configuration - * file. The original Conifer2 project is now dead; Conifer2 lives on - * only as Telodendria's Config parsing library. - */ #ifndef TELODENDRIA_CONFIG_H #define TELODENDRIA_CONFIG_H @@ -45,164 +6,32 @@ #include #include -/* - * A configuration directive is a single key that may have at least one - * value, and any number of children. - */ typedef struct ConfigDirective ConfigDirective; -/* - * The parser returns a parse result object. This stores whether or - * not the parse was successful, and then also additional information - * about the parse, such as the line number on which parsing failed, - * or the collection of directives if the parsing succeeded. - * - * There are a number of ConfigParseResult methods that can be used - * to query the result of parsing. - */ typedef struct ConfigParseResult ConfigParseResult; -/* - * Parse a configuration file, and generate the structures needed to - * make it easy to read. - * - * Params: - * - * (FILE *) The input stream to read from. - * - * Return: A ConfigParseResult, which can be used to check whether or - * not the parsing was successful. If the parsing was sucessful, then - * this object contains the root directive, which can be used to - * retrieve configuration values out of. If the parsing failed, then - * this object contains the line number at which the parsing was - * aborted. - */ extern ConfigParseResult * - ConfigParse(FILE *); +ConfigParse(FILE *stream); -/* - * Get whether or not a parse result indicates that parsing was - * successful or not. This function should be used to determine what - * to do next. If the parsing failed, your program should terminate - * with an error, otherwise, you can proceed to parse the configuration - * file. - * - * Params: - * - * (ConfigParseResult *) The output of ConfigParse() to check. - * - * Return: 0 if the configuration file is malformed, or otherwise - * could not be parsed. Any non-zero return value indicates that the - * configuration file was successfully parsed. - */ extern unsigned int - ConfigParseResultOk(ConfigParseResult *); +ConfigParseResultOk(ConfigParseResult *result); -/* - * If, and only if, the configuration file parsing failed, then this - * function can be used to get the line number it failed at. Typically, - * this will be reported to the user and then the program will be - * terminated. - * - * Params: - * - * (ConfigParseResult *) The output of ConfigParse() to get the - * line number from. - * - * Return: The line number on which the configuration file parser - * choked, or 0 if the parsing was actually successful. - */ extern size_t - ConfigParseResultLineNumber(ConfigParseResult *); +ConfigParseResultLineNumber(ConfigParseResult *result); -/* - * Convert a ConfigParseResult into a HashMap containing the entire - * configuration file, if, and only if, the parsing was successful. - * - * Params: - * - * (ConfigParseResult *) The output of ConfigParse() to get the - * actual configuration data from. - * - * Return: A HashMap containing all the configuration data, or NULL - * if the parsing was not successful. This HashMap is a map of string - * keys to ConfigDirective objects. Use the standard HashMap methods - * to get ConfigDirectives, and then use the ConfigDirective functions - * to get information out of them. - */ extern HashMap * - ConfigParseResultGet(ConfigParseResult *); +ConfigParseResultGet(ConfigParseResult *result); -/* - * Free the memory being used by the given ConfigParseResult. Note that - * it is safe to free the ConfigParseResult immediately after you have - * retrieved either the line number or the configuration data from it. - * Freeing the parse result does not free the configuration data. - * - * Params: - * - * (ConfigParseResult *) The output of ConfigParse() to free. This - * object will be invalidated, but pointers to - * the actual configuration data will still be - * valid. - */ extern void - ConfigParseResultFree(ConfigParseResult *); +ConfigParseResultFree(ConfigParseResult *result); -/* - * Get an array of values associated with the given configuration - * directive. Directives can have any number of values, which are - * made accessible via the Array API. - * - * Params: - * - * (ConfigDirective *) The configuration directive to get the values - * for. - * - * Return: An array that contains at least 1 value. Configuration files - * cannot have value-less directives. If the passed directive is NULL, - * or there is an error allocating memory for an array, then NULL is - * returned. - */ extern Array * - ConfigValuesGet(ConfigDirective *); +ConfigValuesGet(ConfigDirective *directive); -/* - * Get a map of children associated with the given configuration - * directive. Configuration files can recurse with no practical limit, - * so directives can have any number of child directives. - * - * Params: - * - * (ConfigDirective *) The configuratio ndirective to get the - * children of. - * - * Return: A HashMap containing child directives, or NULL if the passed - * directive is NULL or has no children. - */ extern HashMap * - ConfigChildrenGet(ConfigDirective *); +ConfigChildrenGet(ConfigDirective *directive); -/* - * Free all the memory associated with the given configuration hash - * map. Note: this will free *everything*. All Arrays, HashMaps, - * ConfigDirectives, and even strings will be invalidated. As such, - * this should be done after you either copy the values you want, or - * are done using them. It is highly recommended to use this function - * near the end of your program's execution during cleanup, otherwise - * copy any values you need into your own buffers. - * - * Note that this should only be run on the root configuration object, - * not any children. Running on children will produce undefined - * behavior. This function is recursive; it will get all the children - * under it. - * - * Params: - * - * (HashMap *) The configuration data to free. - * - */ extern void - ConfigFree(HashMap *); +ConfigFree(HashMap *conf); -#endif /* TELODENDRIA_CONFIG_H */ +#endif /* TELODENDRIA_CONFIG_H */ diff --git a/src/include/HashMap.h b/src/include/HashMap.h index c75c962..659f3a3 100644 --- a/src/include/HashMap.h +++ b/src/include/HashMap.h @@ -1,54 +1,85 @@ /* + * HashMap.h: The public interface for Telodendria's hash map + * implementation. This hash map is designed to be simple, well + * documented, and generally readable and understandable, yet also + * performant enough to be useful, because it is used extensively in + * Telodendria. + * + * Fundamentally, this is an entirely generic map implementation. It + * can be used for many general purposes, but it is designed to only + * implement the features that Telodendria needs to function well. + * * Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net> - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. */ - #ifndef TELODENDRIA_HASHMAP_H #define TELODENDRIA_HASHMAP_H +#include + +/* + * HashMap: The primary data structure used by this hash map algorithm. + * All data necessary for the algorithm to function is stored inside + * it. This is an opaque structure; use the methods defined by this + * interface to manipulate it. + */ typedef struct HashMap HashMap; +/* + * HashMapCreate: Create a new HashMap object. + * + * Returns: A HashMap object that is ready to be used by the rest of + * the HashMap functions, or NULL if memory could not be allocated on + * the heap. + */ extern HashMap * - HashMapCreate(void); +HashMapCreate(void); extern void - HashMapMaxLoadSet(HashMap *, float); +HashMapMaxLoadSet(HashMap *map, float load); + +/* + * HashMapSet: Set the given key in the HashMap to the given value. Note + * that the value is not copied into the HashMap's own memory space; + * only the pointer is stored. It is the caller's job to ensure that the + * value's memory remains valid for the life of the HashMap. + * + * Returns: The previous value at the given key, or NULL if the key did + * not previously exist or any of the parameters provided are NULL. All + * keys must have values; you can't set a key to NULL. To delete a key, + * use HashMapDelete. + */ +extern void * +HashMapSet(HashMap * map, const char *key, void *value); + +/* + * HashMapGet: Get the value for the given key. + * + * Returns: The value at the given key, or NULL if the key does not + * exist, no map was provided, or no key was provided. + */ +extern void * +HashMapGet(HashMap * map, const char *key); + +/* + * HashMapDelete: Delete the value for the given key. + * + * Returns: The value at the given key, or NULL if the key does not + * exist or the map or key was not provided. + */ +extern void * +HashMapDelete(HashMap *map, const char *key); extern void - HashMapFunctionSet(HashMap *, unsigned long (*) (const char *)); - -extern void * - HashMapSet(HashMap *, char *, void *); - -extern void * - HashMapGet(HashMap *, const char *); - -extern void * - HashMapDelete(HashMap *, const char *); - -extern int - HashMapIterate(HashMap *, char **, void **); +HashMapIterate(HashMap *map, void (*iteratorFunc)(void *)); +/* + * HashMapFree: Free the hash map, returning its memory to the operating + * system. Note that this function does not free the values stored in + * the map since this hash map implementation has no way of knowing + * what actually is stored in it. You should use HashMapIterate to + * free the values using your own algorithm. + */ extern void - HashMapFree(HashMap *); +HashMapFree(HashMap *map); -#endif /* TELODENDRIA_HASHMAP_H */ +#endif /* TELODENDRIA_HASHMAP_H */ diff --git a/src/include/Http.h b/src/include/Http.h index 18f8cce..c27f589 100644 --- a/src/include/Http.h +++ b/src/include/Http.h @@ -1,36 +1,7 @@ -/* - * Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net> - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ #ifndef TELODENDRIA_HTTP_H #define TELODENDRIA_HTTP_H -#include - -#include - -typedef enum HttpRequestMethod -{ - HTTP_METHOD_UNKNOWN, +typedef enum HttpRequestMethod { HTTP_GET, HTTP_HEAD, HTTP_POST, @@ -42,13 +13,12 @@ typedef enum HttpRequestMethod HTTP_PATCH } HttpRequestMethod; -typedef enum HttpStatus -{ +typedef enum HttpStatus { /* Informational responses */ HTTP_CONTINUE = 100, HTTP_SWITCHING_PROTOCOLS = 101, HTTP_EARLY_HINTS = 103, - + /* Successful responses */ HTTP_OK = 200, HTTP_CREATED = 201, @@ -57,7 +27,7 @@ typedef enum HttpStatus HTTP_NO_CONTENT = 204, HTTP_RESET_CONTENT = 205, HTTP_PARTIAL_CONTENT = 206, - + /* Redirection messages */ HTTP_MULTIPLE_CHOICES = 300, HTTP_MOVED_PERMANENTLY = 301, @@ -66,7 +36,7 @@ typedef enum HttpStatus HTTP_NOT_MODIFIED = 304, HTTP_TEMPORARY_REDIRECT = 307, HTTP_PERMANENT_REDIRECT = 308, - + /* Client error messages */ HTTP_BAD_REQUEST = 400, HTTP_UNAUTHORIZED = 401, @@ -91,7 +61,7 @@ typedef enum HttpStatus HTTP_TOO_MANY_REQUESTS = 429, HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451, - + /* Server error responses */ HTTP_INTERNAL_SERVER_ERROR = 500, HTTP_NOT_IMPLEMENTED = 501, @@ -104,25 +74,23 @@ typedef enum HttpStatus HTTP_NETWORK_AUTH_REQUIRED = 511 } HttpStatus; -extern const char * - HttpStatusToString(const HttpStatus); +struct HttpRequest { + HttpRequestMethod method; +}; + +struct HttpResponse { + HttpStatus status; +}; + +extern char * +HttpGetStatusString(const HttpStatus httpStatus); extern HttpRequestMethod - HttpRequestMethodFromString(const char *); +HttpRequestMethodFromString(const char *requestMethod); -extern const char * - HttpRequestMethodToString(const HttpRequestMethod); +typedef struct HttpRequest HttpRequest; +typedef struct HttpResponse HttpResponse; -extern char * - HttpUrlEncode(char *); - -extern char * - HttpUrlDecode(char *); - -extern HashMap * - HttpParamDecode(char *); - -extern char * - HttpParamEncode(HashMap *); +typedef void (*HttpHandler)(HttpRequest *, HttpResponse *); #endif diff --git a/src/include/Json.h b/src/include/Json.h index 5e5005f..ead2453 100644 --- a/src/include/Json.h +++ b/src/include/Json.h @@ -1,271 +1,69 @@ -/* - * Copyright (C) 2022 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. - */ - -/* - * Json.h: A fully-featured JSON API for C using Arrays and HashMaps. - * This API builds on the foundations of Arrays and HashMaps, because - * that's all a JSON object really is. It provides a JsonValue, which - * is used to encapsulate arbitrary values while being able to identify - * them in the future, so that JSON can be effectively handled. - * - * This implementation is just to get the job done in parsing and - * generating JSON. It is extremely strict; it will fail on syntax - * errors. This is fine for Matrix, because we can just return - * M_BAD_JSON anything in here fails. - * - * One thing to note about this implementation is that it focuses - * primarily on serialization and deserialization to and from streams. - * What this means is that it does not provide facilities for handling - * JSON strings; it only writes JSON to output streams, and reading - * them from input streams. Of course, you could use the POSIX - * fmemopen() and open_memstream() functions if you really want to deal - * with JSON strings, but JSON is intended to be an exchange format. - * Data should be converted to JSON when it is leaving, and converted - * from JSON when it is coming in. Ideally, most of the program would - * have no idea what JSON actually is. - */ #ifndef TELODENDRIA_JSON_H #define TELODENDRIA_JSON_H #include #include -#include -#include - -/* - * All the possible JSON types. This enumeration is used to identify - * the type of the value stored in a JsonValue. - */ -typedef enum JsonType -{ - JSON_NULL, /* Maps to nothing. */ - JSON_OBJECT, /* Maps to a HashMap of JsonValues */ - JSON_ARRAY, /* Maps to an Array of JsonValues */ - JSON_STRING, /* Maps to a C string */ - JSON_INTEGER, /* Maps to a C long */ - JSON_FLOAT, /* Maps to a C double */ - JSON_BOOLEAN /* Maps to a C 1 or 0 */ +typedef enum JsonType { + JSON_OBJECT, + JSON_ARRAY, + JSON_STRING, + JSON_INTEGER, + JSON_FLOAT, + JSON_BOOLEAN, + JSON_NULL } JsonType; -/* - * A JsonValue encapsulates all the possible values that can be stored - * in a JSON object as a single type, so as to provide a consistent - * API for accessing and setting them. It is an opaque structure that - * can be managed entirely by the functions defined in this API. - * - * Note that in the case of objects, arrays, and strings, this structure - * only stores pointers to allocated data, it doesn't store the data - * itself. JsonValues only store integers, floats, booleans, and NULL - * in their memory. Anything else must be freed separately. - */ -typedef struct JsonValue JsonValue; +typedef struct JsonValue { + JsonType type; + union as { + HashMap *object; + Array *array; + char *string; + int64_t integer; + double floating; + int boolean : 1; + }; +} JsonValue; + -/* - * Get the type of a JsonValue. - * - * Params: - * - * (JsonValue *) The value to get the type of. - * - * Return: A JsonType that tells what the provided value is, or - * JSON_NULL if the passed value is NULL. Note that even a fully - * valid JsonValue may still be of type JSON_NULL, so this function - * should not be used to check whether or not the JSON value is valid. - */ extern JsonType - JsonValueType(JsonValue *); +JsonValueType(JsonValue *value); -/* - * Wrap a HashMap into a JsonValue that represents a JSON object. Note - * that the HashMap should contain only JsonValues. Any other contents - * are not supported and will lead to undefined behavior. - * - * Params: - * - * (HashMap *) The hash map of JsonValues to wrap in a JsonValue. - * - * Return: A JsonValue that holds a pointer to the given object, or - * NULL if there was an error allocating memory. - */ extern JsonValue * - JsonValueObject(HashMap *); +JsonValueObject(HashMap *object); -/* - * Get a HashMap from a JsonValue that represents a JSON object. - * - * Params: - * - * (JsonValue *) The value to extract the object from. - * - * Return: A HashMap of JsonValues, or NULL if no value was provided, - * or the value is not of type JSON_OBJECT. - */ extern HashMap * - JsonValueAsObject(JsonValue *); - -/* - * The following methods very closely resemble the ones above, and - * behave pretty much the exact same. To save on time and effort, - * I'm choosing not to explicitly document all of these. If something - * is unclear about how these functions work, consult the source code, - * and then feel free to write the documentation yourself. - * - * Otherwise, reach out to the official Matrix room, and someone will - * be able to help you. - */ +JsonValueAsObject(JsonValue *value); extern JsonValue * - JsonValueArray(Array * array); +JsonValueArray(Array *array); extern Array * - JsonValueAsArray(JsonValue * value); +JsonValueAsArray(JsonValue *value); extern JsonValue * - JsonValueString(char *string); +JsonValueString(char *string); extern JsonValue * - JsonValueInteger(long integer); +JsonValueInteger(int64_t integer); extern JsonValue * - JsonValueFloat(double floating); +JsonValueFloat(double floating); extern JsonValue * - JsonValueBoolean(int boolean); +JsonValueBoolean(int boolean); -/* - * Create a JsonValue that represents a JSON null. Because Arrays and - * HashMaps should not contain NULL values, I thought it appropriate - * to provide support for JSON nulls. Yes, a small amount of memory is - * allocated just to point to a NULL, but this keeps all the APIs - * clean. - * - * Return: A JsonValue that represents a JSON null, or NULL if memory - * could not be allocated. - */ extern JsonValue * - JsonValueNull(void); +JsonValueNull(void); -/* - * Free the memory being used by a JSON value. Note that this will - * recursively free all Arrays, HashMaps, and other JsonValues that - * are reachable from this one. It will invoke free() on strings as - * well, so make sure passed string pointers point to strings on the - * heap, not the stack. This will be the case for all strings returned - * by JsonDecode(), which is why this assumption is made. However, if - * you are manually creating JsonObjects and stitching them together, - * you'll have to manually free them as well. Calling this on a - * JsonValue that contains a pointer to a stack string is undefined. - * - * Params: - * - * (JsonValue *) The JsonValue to recursively free. - */ -extern void - JsonValueFree(JsonValue *); +extern void * +JsonValueFree(JsonValue *value); -/* - * Recursively free a HashMap of JsonValues. This iterates over all - * the JsonValues in a HashMap and frees them using JsonValueFree(), - * which will in turn call JsonFree() on values of type JSON_OBJECT. - * - * Params: - * - * (HashMap *) The hash map of JsonValues to recursively free. - */ -extern void - JsonFree(HashMap *); +extern char * +JsonEncode(HashMap *object); -/* - * Encode the given string in such a way that it can be embedded in a - * JSON stream. This entails: - * - * - Escaping quotes, backslashes, and other special characters using - * their backslash escape - * - Encoding bytes that are not UTF-8 using \u escapes. - * - Wrapping the entire string in double quotes. - * - * This function is provided via the public API so it is accessible to - * custom JSON encoders, such as the CanonicalJson API. This will - * typically be used for encoding JSON keys; for values, just use - * JsonEncodeValue(). - * - * Params: - * - * (const char *) The C string to serialize as a JSON string. - * (FILE *) The output stream to write the encoded string to. - */ -extern void - JsonEncodeString(const char *, FILE *); - -/* - * Serialize a JsonValue as it would appear in JSON output. This is - * a recursive function that will also encode all child values - * reachable from the given JsonValue. - * - * This is exposed via the public API so that custom JSON encoders - * such as CanonicalJson can take advantage of it. Normal users that - * are writing custom encoders should just use JsonEncode() to encode - * an entire object. - * - * Params: - * - * (JsonValue *) The value to encode. - * (FILE *) The output stream to write the given value to. - */ -extern void - JsonEncodeValue(JsonValue * value, FILE * out); - -/* - * Encode a HashMap of JsonValues into a fully-valid, minimized JSON - * object. This function is recursive; it will serialize everything - * accessible from the passed object into JSON. - * - * Params: - * - * (HashMap *) The HashMap of JsonValues to encode and write to the - * output stream. - * (FILE *) The output stream to write the given HashMap to. - * - * Return: Whether or not the operation was successful. This function - * will fail if either the passed HashMap or file stream are NULL. In - * all other cases, this function succeeds. - */ -extern int - JsonEncode(HashMap *, FILE *); - -/* - * Decode the given input stream into a HashMap of JsonValues. - * - * Params: - * - * (FILE *) The input stream to parse JSON from. - * - * Return: A HashMap of JsonValues, or NULL if there was an error - * parsing the JSON. - */ extern HashMap * - JsonDecode(FILE *); +JsonDecode(char *string); -#endif /* TELODENDRIA_JSON_H */ +#endif diff --git a/src/include/Log.h b/src/include/Log.h index 46464a0..3011692 100644 --- a/src/include/Log.h +++ b/src/include/Log.h @@ -1,197 +1,63 @@ -/* - * Copyright (C) 2022 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. - */ - -/* - * Log.h: A heavily-modified version of Shlog, a simple C logging - * facility that allows for colorful output, timestamps, and custom - * log levels. This library differs from Shlog in that the naming - * conventions have been updated to be consistent with Telodendria. - * - * Shlog was originally a learning project. It worked well, however, - * and produced elegant logging output, so it was chosen to be the - * main logging mechanism of Telodendria. The original Shlog project - * is now dead; Shlog lives on now only as Telodendria's logging - * mechanism. - * - * In the name of simplicity and portability, I opted to use an - * in-house logging system instead of syslog(), or other system logging - * mechanisms. However, this API could easily be patched to allow - * logging via other mechanisms that support the same features. - */ #ifndef TELODENDRIA_LOG_H #define TELODENDRIA_LOG_H #include #include -#include -/* - * I used to define all my own constants, but now I use - * those defined in syslog.h. Instead of replacing all the - * references, I just map the old names to the new ones. If - * you're ever bored one day, you can remove these, and then - * go fix all the compiler errors that arise. Should be pretty - * easy, just mind numbing. - */ -#define LOG_ERROR LOG_ERR -#define LOG_TASK LOG_NOTICE -#define LOG_MESSAGE LOG_INFO +typedef enum LogLevel { + LOG_ERROR, + LOG_WARNING, + LOG_TASK, + LOG_MESSAGE, + LOG_DEBUG +} LogLevel; -/* - * The possible flags that can be applied to alter the behavior of - * the logger. - */ -typedef enum LogFlag -{ - LOG_FLAG_COLOR = (1 << 0), /* Enable color output on TTYs */ - LOG_FLAG_SYSLOG = (1 << 1) /* Log to the syslog instead of a - * file */ +typedef enum LogFlag { + LOG_FLAG_COLOR = (1 << 0) } LogFlag; -/* - * The log configurations structure in which all settings exist. - * It's not super elegant to pass around a pointer to the logging - * configuration, but this really is the best way without having a - * global variable. It allows multiple loggers to exist if necessary, - * and makes things more thread safe. - */ typedef struct LogConfig LogConfig; -/* - * Create a new log configuration on the heap. This will be passed to - * every Log() call after it is configured. - * - * Return: A pointer to a new LogConfig that can be configured and used - * for logging. It should have sane defaults; in other words, you should - * be able to immediately start logging with it. - */ extern LogConfig * - LogConfigCreate(void); - -/* - * Free a log configuration. Future attempts to log with the passed - * configuration will fail in an undefined way, such as by hanging the - * process or segfaulting. - * - * Params: - * - * (LogConfig *) The configuration to free. All memory associated with - * configuring the logging mechanism will be - * invalidated. - */ -extern void - LogConfigFree(LogConfig *); - -/* - * Set the current log level on the specified log configuration. This - * indicates that only messages at or above this level should be - * logged; all other messages are ignored by the Log() function. - * - * Params: - * - * (LogConfig *) The log configuration to set the log level on. - * (int) The log level to set. - */ -extern void - LogConfigLevelSet(LogConfig *, int); - -/* - * Indent the log output by two spaces. This can be helpful in - * generating stack traces, or otherwise producing hierarchical output. - * After calling this function, all future log messages using this - * configuration will be indented. - * - * Params: - * - * (LogConfig *) The log configuration to indent. - */ -extern void - LogConfigIndent(LogConfig *); - -/* - * Decrease the log output indent by two spaces. This can be helpful in - * generating stack traces, or otherwise producing hierarchical output. - * After calling this function, all future log messages using this - * configuration will be unindented, unless there was no indentation - * to begin with; in that case, this function will do nothing. - * - * Params: - * - * (LogConfig *) The log configuration to unindent. - */ -extern void - LogConfigUnindent(LogConfig *); - -/* -* Set the log output indent to an arbitrary amount. This can be helpful -* in generating stack traces, or otherwise producing hierarchical -* output. After calling this function, all future log messages using -* this configuration will be indented by the given amount. -* -* Params: -* -* (LogConfig *) The log configuration to apply the indent to. -*/ -extern void - LogConfigIndentSet(LogConfig *, size_t); +LogConfigCreate(void); extern void - LogConfigOutputSet(LogConfig *, FILE *); +LogConfigFree(LogConfig *config); extern void - LogConfigFlagSet(LogConfig *, int); +LogConfigLevelSet(LogConfig *config, LogLevel level); + +extern LogLevel +LogConfigLevelGet(LogConfig *config); extern void - LogConfigFlagClear(LogConfig *, int); +LogConfigIndentSet(LogConfig *config, size_t indent); + +extern size_t +LogConfigIndentGet(LogConfig *config); extern void - LogConfigTimeStampFormatSet(LogConfig *, char *); +LogConfigIndent(LogConfig *config); -/* - * Actually log a message to a console, file, or other output device, - * using the given log configuration. This function is thread-safe; it - * locks a mutex before writing a message, and then unlocks it after - * the message was written. It should therefore work well in - * multithreaded environments, and with multiple different log - * configurations, as each one has its own mutex. - * - * This function only logs messages if they are above the currently - * configured log level. In this way, it is easy to turn some messages - * on and off. - * - * This function is a printf() style function; it takes a format - * string and any number of parameters to format. - * - * Params: - * - * (LogConfig *) The logging configuration. - * (int) The level of the message to log. - * (const char *) The format string, or a plain message string. - * (...) Any items to map into the format string, printf() - * style. - */ extern void - Log(LogConfig *, int, const char *,...); +LogConfigUnindent(LogConfig *config); + +extern void +LogConfigOutputSet(LogConfig *config, FILE *out); + +extern void +LogConfigFlagSet(LogConfig *config, int flags); + +extern void +LogConfigFlagClear(LogConfig *config, int flags); + +extern int +LogConfigFlagGet(LogConfig *config, int flags); + +extern void +LogConfigTimeStampFormatSet(LogConfig *config, char *tsFmt); + +extern void +Log(LogConfig *config, LogLevel level, const char *msg, ...); #endif