Compare commits

...

1 commit

Author SHA1 Message Date
95342f7ad1 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.
2022-07-23 00:19:12 +00:00
22 changed files with 1350 additions and 1831 deletions

View file

@ -1,5 +1 @@
build build
data
.env
*.log
vgcore.*

8
.idea/Telodendria.iml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Telodendria.iml" filepath="$PROJECT_DIR$/.idea/Telodendria.iml" />
</modules>
</component>
</project>

52
.idea/workspace.xml Normal file
View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeSettings">
<configurations>
<configuration PROFILE_NAME="Debug" ENABLED="true" CONFIG_NAME="Debug" />
</configurations>
</component>
<component name="ChangeListManager">
<list default="true" id="fb1df1f2-2a47-495d-89b9-277de6049bd1" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ClangdSettings">
<option name="formatViaClangd" value="false" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectId" id="2CD2pGJ1okjE4N8lOPrGKmwbXGw" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
&quot;cf.first.check.clang-format&quot;: &quot;false&quot;,
&quot;cidr.known.project.marker&quot;: &quot;true&quot;
}
}</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="fb1df1f2-2a47-495d-89b9-277de6049bd1" name="Changes" comment="" />
<created>1658323016759</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1658323016759</updated>
<workItem from="1658323018463" duration="25597000" />
<workItem from="1658450495358" duration="6933000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
</project>

62
Telodendria.css Normal file
View file

@ -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;
}
}

541
Telodendria.html Normal file
View file

@ -0,0 +1,541 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="Jordan Bancino">
<meta name="description"
content="Telodendria, a Matrix homeserver written in ANSI C.">
<meta property="og:title"
content="Telodendria | A Matrix Homeserver written in ANSI C.">
<meta property="og:type" content="website">
<meta property="og:url"
content="https://bancino.net/pub/telodendria/telodendria.html">
<meta property="og:description"
content="Telodendria, a Matrix homeserver written in ANSI C.">
<link rel="stylesheet" href="Telodendria.css">
<title>Telodendria | A Matrix Homeserver written in ANSI C.</title>
</head>
<body>
<h1 id="telodendria">Telodendria</h1>
<p>
<b>Telodendria:</b> The terminal branches of an axon.
</p>
<p>
<b><i>Note:</i></b> <b>Telodendria</b> is under <i>heavy</i> development.
Please see the <a href="#project-status">Project Status</a>.
</p>
<p>
<b>Telodendria</b> is a Matrix homeserver implementation written from
scratch in ANSI C. It is designed to be lightweight and simple, yet
functional. <b>Telodendria</b> differentiates itself from other Matrix
homeserver implementations because it:
<ul>
<li>
Is written in C, a stable, low-level programming language with a long
history, low build and runtime overhead, and wide compatibility.
</li>
<li>
Is written with minimalism as a primary design goal. Whenever possible
and practical, no third-party libraries are pulled in to the source
code. Everything <b>Telodendria</b> needs is custom written. As a
result, <b>Telodendria</b> depends only on a standard C compiler and
library to be built, and only a web server with CGI capabilities to
run.
</li>
<li>
Uses a flat-file directory structure to store data instead of a
database. This has a number of advantages:
<ul>
<li>It makes setup and maintenance much easier.</li>
<li>
It allows <b>Telodendria</b> to run on systems with fewer resources.
</li>
<li>
It provides both runtime and data safety and stability. Since no
database is running, there's fewer things that could go wrong because
there's a lot less code running on the system.
</li>
</ul>
</li>
<li>
Runs as a CGI application. <b>Telodendria</b> is delivered as a single
small, highly-optimized binary that can be dropped in a web server's
web root to be executed. This allows it to consume very few resources
and be very easy to set up.
</li>
</ul>
<p>
<b>Telodendria</b> is on Matrix! Check out the official Matrix rooms:
</p>
<table>
<tr>
<th>Room</th>
<th>Description</th>
</tr>
<tr>
<td>
<code>#telodendria-releases:bancino.net</code>
</td>
<td>
Get notified of new releases.
</td>
</tr>
<tr>
<td>
<code>#telodendria-general:bancino.net</code>
</td>
<td>
General discussion and support for <b>Telodendria</b>.
</td>
</tr>
<tr>
<td>
<code>#telodendria-issues:bancino.net</code>
</td>
<td>
Report issues with <b>Telodendria</b>.
</td>
</tr>
<tr>
<td>
<code>#telodendria-patches:bancino.net</code>
</td>
<td>
Submit code patches to the <b>Telodendria</b> project.
</td>
</tr>
</table>
<h2 id="table-of-contents">Table of Contents</h2>
<ul>
<li>
<a href="#telodendria">Telodendria</a>
<ul>
<li><a href="#table-of-contents">Table of Contents</a></li>
<li>
<a href="#getting-started">Getting Started</a>
<ul>
<li>
<a href="#install">Install Telodendria</a>
<ul>
<li><a href="#openbsd">OpenBSD</a></li>
<li><a href="#building-from-source">Building From Source</a></li>
</ul>
</li>
<li><a href="#configure">Configure Telodendria</a></li>
</ul>
</li>
<li>
<a href="#project-status">Project Status</a>
<ul>
<li><a href="#phase-1">Phase 1: Getting Off The Ground</a></li>
<li><a href="#phase-2">Phase 2: Building A Foundation</a></li>
<li><a href="#phase-3">Phase 3: Welcome To Matrix</a></li>
<li><a href="#phase-4">Phase 4: A Real Homeserver</a></li>
</ul>
</li>
<li><a href="#rationale">Rationale</a></li>
<li><a href="#project-goals">Project Goals</a></li>
<li><a href="#getting-support">Getting Support</a></li>
<li>
<li>
<a href="#documentation-status">Documentation Status</a>
</li>
<a href="#contributing">Contributing</a>
<ul>
<li><a href="#reporting-issues">Reporting Issues</a></li>
<li>
<a href="#Developing">Developing</a>
<ul>
<li><a href="#getting-the-code">Getting The Code</a></li>
<li><a href="#code-style">Code Style</a></li>
<li><a href="#submitting-patches">Subitting Patches</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#license">License</a></li>
<li><a href="#change-log">Change Log</a></li>
</ul>
</li>
</ul>
<h2 id="getting-started">Getting Started</h2>
<h3 id="install">Install Telodendria</h3>
<p>
If your operating system has an official package or port of
<b>Telodendria</b>, 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.
</p>
<p>
If your operating system does not have an official package, see below
for instructions on building from source and use them to create one.
</p>
<h4 id="openbsd">OpenBSD</h4>
<p>
<b>Telodendria</b> is available in the ports tree and as a binary
package. You can install it with the following command:
</p>
<div class="code">
$ pkg_add telodendria
</div>
<h4 id="building-from-source">Building From Source</h4>
<p>
<b>Telodendria</b> 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:
</p>
<ul>
<li>
A standards-compliant C compiler with the C standard library. Because
<b>Telodendria</b> is written in ANSI C, it should compile on just about
any compiler, but the following compilers are known to work:
<ul>
<li>GCC</li>
<li>Clang</li>
<li>
Tiny C Compiler (<b>Note:</b> must edit <code>make.sh</code> and remove
<code>-Wl,-static -Wl,-gc-sections</code> from <code>LDFLAGS</code>)
</li>
</ul>
Other compilers should work as well, but you may have to play with the
flags in <code>make.sh</code>.
</li>
<li>
POSIX base utilities, including <code>find</code>, <code>stat</code>,
<code>env</code>, and compliant <code>sh</code>-like shell.
</li>
</ul>
<div class="code">
$ ./make.sh
</div>
<p>
If everything went well, that will produce
<code>telodendria.cgi</code>, which you can then place under your web
root and configure your web server to execute. You'll need to make sure
<code>/.well-known/matrix</code> and <code>/_matrix</code> and all the
paths under them actually execute <code>telodendria.cgi</code>. See the
provided OpenBSD <code>httpd.conf</code> for reference. Even if you
aren't using OpenBSD's <code>httpd(8)</code>, you should find its
configuration syntax simple enough to adequately demonstrate the proper
configuration.
</p>
<h3 id="configure">Configure Telodendria</h3>
<p>
Once you get <b>Telodendria</b> 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
<code>Telodendria.json</code>.
</p>
<h2 id="project-status">Project Status</h2>
<p>
<b>Telodendria</b> 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.
</p>
<p>
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
<a href="#contributing">Contributing</a> section for details on how you
can get involved.
</p>
<h3 id="phase-1">Phase 1: Getting Off The Ground</h3>
<ul>
<li><s>Name this project</s></li>
<li><s>Set up a CVS repository</s></li>
<li><s>Make CVS repository public</s></li>
<li><s>Write a coding style guide</s></li>
<li><s>Write a build script</s></li>
<li><s>Add a license</s></li>
<li><s>Add support and issue reporting guide</s></li>
<li><s>Add table of contents to this document</s></li>
</ul>
<h3 id="phase-2">Phase 2: Building A Foundation</h3>
<ul>
<li><s>Implement an array</s></li>
<li><s>Implement a logging facility</s></li>
<li><s>Implement argument parsing (<code>-c file -Vh</code>)</s></li>
<li><s>Implement a hash map</s></li>
<li><s>Combine library code files</s></li>
<li><s>Implement configuration file parsing using the hash map</s></li>
<li>Implement a JSON library using the hash map and array</li>
<li>Figure out how to write unit tests for array/hashmap/etc</li>
<li>Implement a simple HTTP server</li>
<li>
Design the server architecture
<ul>
<li>Route requests</li>
<li>Handle requests</li>
<li>Data abstraction layer</li>
<li>Error generation</li>
</ul>
</li>
</ul>
<h3 id="phase-3">Phase 3: Welcome To Matrix</h3>
<ul>
<li>
Implement the Client-Server API
</li>
<li>
Implement the Server-Server API
</li>
<li>
Implement the other Matrix APIs
</li>
</ul>
<h3 id="phase-4">Phase 4: A Real Homeserver</h3>
<ul>
<li>
Create an OpenBSD package and get it submitted to ports
</li>
<li>
Create a command line tool to manage Telodendria
<ul>
<li>Configuration file generation</li>
<li>User management</li>
<li>Room management</li>
</ul>
</li>
<li>
Migrate from Synapse. I run a Synapse homeserver right now, so somehow
I have to get all my data into the Telodendria format.
</li>
</ul>
<h2 id="documentation-status">Documentation Status</h2>
<p>
This documentation needs just a little work. Here's the things
on my list for that:
</p>
<ul>
<li>Update Rationale section</li>
<li>Update Project description (no longer a CGI binary)</li>
<li>Update project code requirements (ANSI C, POSIX.1c)</li>
</ul>
<h2 id="rationale">Rationale</h2>
<p>
This section explains
</p>
<p>
I want a lightweight Matrix homeserver designed for OpenBSD. I want a
homeserver that can be developed in <code>vi(1)</code> 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.
</p>
<p>
So I wrote Telodendria.
</p>
<p>
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.
</p>
<p>
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
<code>tar(1)</code> up the directory, and you're good to go.
</p>
<p>
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.
</p>
<h2 id="project-goals">Project Goals</h2>
<p>
The goals of this project are as follows:
</p>
<ul>
<li>
To be a production-ready Matrix server capable of handling a lot of
users. Telodendria should have good performance in many diverse
environments.
</li>
<li>
To have as few external build and run dependencies as possible. It
should be possible to compile Telodendria on any operating system out
of the box, and have it be totally statically linked, ready to run
under a <code>chroot(8)</code>-ed web server. You'll even notice that
the documentation is written in HTML directly, not Markdown, to remove
the dependency on a Markdown parser and renderer.
</li>
<li>
To be written in clean, elegant, and well-documented code. The goal is
to build a Matrix homeserver from the ground up, not just because I
don't the way existing homeservers are implemented, but also so I can
learn how Matrix really works, and maybe even teach others along the
way.
</li>
</ul>
<h2 id="getting-support">Getting Support</h2>
<p>
<b>Telodendria</b> 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 <b>Telodendria</b> up and running, you're more than welcome to
reach out for support. Just join the
<code>#telodendria-general:bancino.net</code> Matrix channel. Before
you do though, make sure you're running the latest version of
<b>Telodendria</b> and you've thoroughly read through all the
relevant documentation.
</p>
<h2 id="contributing">Contributing</h2>
<p>
<b>Telodendria</b> 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.
</p>
<h3 id="reporting-issues">Reporting Issues</h3>
<p>
If&mdash;after you've reached out to
<code>#telodendria-general:bancino.net</code>&mdash;it has been
determined that there is a problem with <b>Telodendria</b>, it should
be reported to <code>#telodendria-issues:bancino.net</code>. There it
can be discussed further. The issues channel serves as the official
issue tracker of <b>Telodendria</b>; although issues may be copied
into a <code>TODO</code> file in the CVS repository just so they
don't get lost.
</p>
<h3 id="developing">Developing</h3>
<p>
The primary language used to write <b>Telodendria</b> 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
<b>Telodendria</b> 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.
</p>
<h4 id="getting-the-code">Getting The Code</h4>
<p>
There are multiple ways to get the source code for <b>Telodendria</b>.
You can download an official release tarball from
<a href="https://bancino.net/pub/telodendria">here</a> 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.
</p>
<div class="code">
$ export CVSROOT=anoncvs@bancino.net:/cvs
$ cvs checkout Telodendria
$ cd Telodendria
</div>
<p>
You should now have the latest <b>Telodendria</b> source code. Follow
the <a href="#code-style">Code Style</a> as you make your changes.
</p>
<h4 id="code-style">Code Style</h4>
<p>
<b>Telodendria</b>'s code style is very unique. In general, these are
the conventions used by the code base.
</p>
<ul>
<li>
All function, enumeration, structure, and header names are
<code>CamelCase</code>. This is preferred to <code>snake_case</code>
because it is more compact.
</li>
<li>
<code>enum</code>s and <code>struct</code>s are always
<code>typedef</code>-ed to their same name. The <code>typedef</code>
occurs in the public API header, and the actual declaration occurs in
the private implementation header.
</li>
<li>
Indentation is done with spaces. This ensures that files look the same
for everyone. It also makes line wrapping rules much easier because
the indentations are the same size. Please configure your editor to
make the <kbd>Tab</kbd> key insert spaces, and if it does automatic
indentation, make sure it indents with spaces. If you cannot configure
your editor to insert spaces, then you can try running the code files
you were working on through <code>expand</code>. A unit of indentation
is 4 spaces.
</li>
<li>
Lines should not exceed 72 characters, including indentations. Some
developers use <code>vi(1)</code> in an 80x24 terminal to write code.
</li>
</ul>
<p>
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.
</p>
<h4 id="submitting-patches">Submitting Patches</h4>
<p>
Submitting patches is fairly easy to do if you've got the CVS sources
checked out. Once you have made your changes, just run
<code>cvs diff</code>:
</p>
<div class="code">
$ cvs diff -uNp > your-changes.patch
</div>
<p>
Then, send the resulting patches to
<code>#telodendria-patches:bancino.net</code>, where they will be
promptly reviewed by the community.
</p>
<h2 id="license">License</h2>
<p>
All of the code and documentation for <b>Telodendria</b> is licensed
under the following terms and conditions:
</p>
<div class="code">
Copyright (C) 2022 Jordan Bancino &lt;@jordan:bancino.net&gt;
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.
</div>
<h2 id="change-log">Change Log</h2>
<p>
At this time, Telodendria does not have any tagged releases because it
is not yet functional as a Matrix homeserver. Please check out the <a
href="#project-status">Project Status</a> to see where things are
currently at.
</p>
</body>
</html>

9
contrib/httpd.conf Normal file
View file

@ -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
}

13
contrib/telodendria.conf Normal file
View file

@ -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";

57
make.sh Normal file
View file

@ -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"

View file

@ -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 <Array.h> #include <Array.h>
#ifndef ARRAY_BLOCK #ifndef ARRAY_BLOCK
@ -28,18 +5,16 @@
#endif #endif
#include <stddef.h> #include <stddef.h>
#include <Memory.h> #include <stdlib.h>
struct Array struct Array {
{ void **entries; /* An array of void pointers, to store any data */
void **entries; /* An array of void pointers, to
* store any data */
size_t allocated; /* Elements allocated on the heap */ size_t allocated; /* Elements allocated on the heap */
size_t size; /* Elements actually filled */ size_t size; /* Elements actually filled */
}; };
int int
ArrayAdd(Array * array, void *value) ArrayAdd(Array *array, void *value)
{ {
if (!array) if (!array)
{ {
@ -52,7 +27,7 @@ ArrayAdd(Array * array, void *value)
Array * Array *
ArrayCreate(void) ArrayCreate(void)
{ {
Array *array = Malloc(sizeof(Array)); Array *array = malloc(sizeof(Array));
if (!array) if (!array)
{ {
@ -61,11 +36,11 @@ ArrayCreate(void)
array->size = 0; array->size = 0;
array->allocated = ARRAY_BLOCK; array->allocated = ARRAY_BLOCK;
array->entries = Malloc(sizeof(void *) * ARRAY_BLOCK); array->entries = malloc(sizeof(void *) * ARRAY_BLOCK);
if (!array->entries) if (!array->entries)
{ {
Free(array); free(array);
return NULL; return NULL;
} }
@ -73,12 +48,12 @@ ArrayCreate(void)
} }
void * void *
ArrayDelete(Array * array, size_t index) ArrayDelete(Array *array, size_t index)
{ {
size_t i; size_t i;
void *element; void *element;
if (!array || array->size <= index) if (!array)
{ {
return NULL; return NULL;
} }
@ -96,17 +71,17 @@ ArrayDelete(Array * array, size_t index)
} }
void void
ArrayFree(Array * array) ArrayFree(Array *array)
{ {
if (array) if (array)
{ {
Free(array->entries); free(array->entries);
Free(array); free(array);
} }
} }
void * void *
ArrayGet(Array * array, size_t index) ArrayGet(Array *array, size_t index)
{ {
if (!array) if (!array)
{ {
@ -123,7 +98,7 @@ ArrayGet(Array * array, size_t index)
extern int extern int
ArrayInsert(Array * array, void *value, size_t index) ArrayInsert(Array *array, void *value, size_t index)
{ {
size_t i; size_t i;
@ -139,7 +114,7 @@ ArrayInsert(Array * array, void *value, size_t index)
tmp = array->entries; tmp = array->entries;
array->entries = Realloc(array->entries, array->entries = realloc(array->entries,
sizeof(void *) * newSize); sizeof(void *) * newSize);
if (!array->entries) if (!array->entries)
@ -164,7 +139,7 @@ ArrayInsert(Array * array, void *value, size_t index)
} }
size_t size_t
ArraySize(Array * array) ArraySize(Array *array)
{ {
if (!array) if (!array)
{ {
@ -175,7 +150,7 @@ ArraySize(Array * array)
} }
int int
ArrayTrim(Array * array) ArrayTrim(Array *array)
{ {
void **tmp; void **tmp;
@ -186,7 +161,7 @@ ArrayTrim(Array * array)
tmp = array->entries; tmp = array->entries;
array->entries = Realloc(array->entries, array->entries = realloc(array->entries,
sizeof(void *) * array->size); sizeof(void *) * array->size);
if (!array->entries) if (!array->entries)
@ -197,53 +172,3 @@ ArrayTrim(Array * array)
return 1; 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);
}

View file

@ -1,32 +1,9 @@
/*
* 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 <Base64.h> #include <Base64.h>
#include <Memory.h> #include <stdlib.h>
static const char Base64EncodeMap[] = static const char Base64EncodeMap[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const int Base64DecodeMap[] = { static const int Base64DecodeMap[] = {
62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58,
@ -66,14 +43,10 @@ Base64DecodedSize(const char *base64, size_t len)
ret = len / 4 * 3; ret = len / 4 * 3;
for (i = len; i > 0; i--) for (i = len; i > 0; i--) {
{ if (base64[i] == '=') {
if (base64[i] == '=')
{
ret--; ret--;
} } else {
else
{
break; break;
} }
} }
@ -94,7 +67,7 @@ Base64Encode(const char *input, size_t len)
} }
outLen = Base64EncodedSize(len); outLen = Base64EncodedSize(len);
out = Malloc(outLen + 1); out = malloc(outLen + 1);
if (!out) if (!out)
{ {
return NULL; return NULL;
@ -113,17 +86,12 @@ Base64Encode(const char *input, size_t len)
if (i + 1 < len) if (i + 1 < len)
{ {
out[j + 2] = Base64EncodeMap[(v >> 6) & 0x3F]; out[j + 2] = Base64EncodeMap[(v >> 6) & 0x3F];
} } else {
else
{
out[j + 2] = '='; out[j + 2] = '=';
} }
if (i + 2 < len) if (i + 2 < len) {
{
out[j + 3] = Base64EncodeMap[v & 0x3F]; out[j + 3] = Base64EncodeMap[v & 0x3F];
} } else {
else
{
out[j + 3] = '='; out[j + 3] = '=';
} }
} }
@ -171,7 +139,7 @@ Base64Decode(const char *input, size_t len)
} }
} }
out = Malloc(outLen + 1); out = malloc(outLen + 1);
if (!out) if (!out)
{ {
return NULL; return NULL;
@ -179,8 +147,7 @@ Base64Decode(const char *input, size_t len)
out[outLen] = '\0'; 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 = Base64DecodeMap[input[i] - 43];
v = (v << 6) | Base64DecodeMap[input[i + 1] - 43]; v = (v << 6) | Base64DecodeMap[input[i + 1] - 43];
v = input[i + 2] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 2] - 43]; v = input[i + 2] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 2] - 43];
@ -226,7 +193,7 @@ Base64Pad(char **base64Ptr, size_t length)
newSize = length + (4 - (length % 4)); newSize = length + (4 - (length % 4));
tmp = Realloc(*base64Ptr, newSize + 100);; tmp = realloc(*base64Ptr, newSize + 100);;
if (!tmp) if (!tmp)
{ {
return 0; /* Memory error */ return 0; /* Memory error */
@ -242,3 +209,4 @@ Base64Pad(char **base64Ptr, size_t length)
return newSize; return newSize;
} }

View file

@ -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 <Config.h> #include <Config.h>
#include <Memory.h>
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h> #include <ctype.h>
#include <string.h> #include <string.h>
@ -33,24 +8,20 @@
#define CONFIG_BUFFER_BLOCK 32 #define CONFIG_BUFFER_BLOCK 32
#endif #endif
struct ConfigDirective struct ConfigDirective {
{
Array *values; Array *values;
HashMap *children; HashMap *children;
}; };
struct ConfigParseResult struct ConfigParseResult {
{ unsigned int ok : 1;
unsigned int ok:1; union {
union
{
size_t lineNumber; size_t lineNumber;
HashMap *confMap; HashMap *confMap;
} data; } data;
}; };
typedef enum ConfigToken typedef enum ConfigToken {
{
TOKEN_UNKNOWN, TOKEN_UNKNOWN,
TOKEN_NAME, TOKEN_NAME,
TOKEN_MACRO_ASSIGNMENT, TOKEN_MACRO_ASSIGNMENT,
@ -62,8 +33,7 @@ typedef enum ConfigToken
TOKEN_EOF TOKEN_EOF
} ConfigToken; } ConfigToken;
typedef struct ConfigParserState typedef struct ConfigParserState {
{
FILE *stream; FILE *stream;
unsigned int line; unsigned int line;
@ -77,48 +47,54 @@ typedef struct ConfigParserState
} ConfigParserState; } ConfigParserState;
unsigned int unsigned int
ConfigParseResultOk(ConfigParseResult * result) ConfigParseResultOk(ConfigParseResult *result)
{ {
return result ? result->ok : 0; return result ? result->ok : 0;
} }
size_t size_t
ConfigParseResultLineNumber(ConfigParseResult * result) ConfigParseResultLineNumber(ConfigParseResult *result)
{ {
return result && !result->ok ? result->data.lineNumber : 0; return result && !result->ok ? result->data.lineNumber : 0;
} }
HashMap * HashMap *
ConfigParseResultGet(ConfigParseResult * result) ConfigParseResultGet(ConfigParseResult *result)
{ {
return result && result->ok ? result->data.confMap : NULL; return result && result->ok ? result->data.confMap : NULL;
} }
void void
ConfigParseResultFree(ConfigParseResult * result) ConfigParseResultFree(ConfigParseResult *result)
{ {
/* /*
* Note that if the parse was valid, the hash map * Note that if the parse was valid, the hash map
* needs to be freed separately. * needs to be freed separately.
*/ */
Free(result); free(result);
} }
Array * Array *
ConfigValuesGet(ConfigDirective * directive) ConfigValuesGet(ConfigDirective *directive)
{ {
return directive ? directive->values : NULL; return directive ? directive->values : NULL;
} }
HashMap * HashMap *
ConfigChildrenGet(ConfigDirective * directive) ConfigChildrenGet(ConfigDirective *directive)
{ {
return directive ? directive->children : NULL; 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 static void
ConfigDirectiveFree(ConfigDirective * directive) ConfigDirectiveFree(void *ptr)
{ {
ConfigDirective *directive = ptr;
size_t i; size_t i;
if (!directive) if (!directive)
@ -128,36 +104,26 @@ ConfigDirectiveFree(ConfigDirective * directive)
for (i = 0; i < ArraySize(directive->values); i++) for (i = 0; i < ArraySize(directive->values); i++)
{ {
Free(ArrayGet(directive->values, i)); free(ArrayGet(directive->values, i));
} }
ArrayFree(directive->values); ArrayFree(directive->values);
ConfigFree(directive->children); ConfigFree(directive->children);
Free(directive); free(directive);
} }
void void
ConfigFree(HashMap * conf) ConfigFree(HashMap *conf)
{ {
char *key; HashMapIterate(conf, ConfigDirectiveFree);
void *value;
while (HashMapIterate(conf, &key, &value))
{
ConfigDirectiveFree((ConfigDirective *) value);
Free(key);
}
HashMapFree(conf); HashMapFree(conf);
} }
static ConfigParserState * static ConfigParserState *
ConfigParserStateCreate(FILE * stream) ConfigParserStateCreate(FILE * stream)
{ {
ConfigParserState *state = Malloc(sizeof(ConfigParserState)); ConfigParserState *state = malloc(sizeof(ConfigParserState));
if (!state) if (!state)
{ {
return NULL; return NULL;
@ -167,7 +133,7 @@ ConfigParserStateCreate(FILE * stream)
if (!state->macroMap) if (!state->macroMap)
{ {
Free(state); free(state);
return NULL; return NULL;
} }
@ -182,28 +148,19 @@ ConfigParserStateCreate(FILE * stream)
} }
static void static void
ConfigParserStateFree(ConfigParserState * state) ConfigParserStateFree(ConfigParserState *state)
{ {
char *key;
void *value;
if (!state) if (!state)
{ {
return; return;
} }
free(state->token);
Free(state->token); HashMapIterate(state->macroMap, free);
while (HashMapIterate(state->macroMap, &key, &value))
{
Free(key);
Free(value);
}
HashMapFree(state->macroMap); HashMapFree(state->macroMap);
Free(state); free(state);
} }
static int static int
@ -213,10 +170,9 @@ ConfigIsNameChar(int c)
} }
static char static char
ConfigConsumeWhitespace(ConfigParserState * state) ConfigConsumeWhitespace(ConfigParserState *state)
{ {
int c; int c;
while (isspace(c = fgetc(state->stream))) while (isspace(c = fgetc(state->stream)))
{ {
if (c == '\n') if (c == '\n')
@ -228,24 +184,22 @@ ConfigConsumeWhitespace(ConfigParserState * state)
} }
static void static void
ConfigConsumeLine(ConfigParserState * state) ConfigConsumeLine(ConfigParserState *state)
{ {
while (fgetc(state->stream) != '\n'); while (fgetc(state->stream) != '\n');
state->line++; state->line++;
} }
static void static void
ConfigTokenSeek(ConfigParserState * state) ConfigTokenSeek(ConfigParserState *state)
{ {
int c; int c;
/* If we already hit EOF, don't do anything */ /* If we already hit EOF, don't do anything */
if (state->tokenType == TOKEN_EOF) if (state->tokenType == TOKEN_EOF) {
{
return; return;
} }
while ((c = ConfigConsumeWhitespace(state)) == '#') while ((c = ConfigConsumeWhitespace(state)) == '#') {
{
ConfigConsumeLine(state); ConfigConsumeLine(state);
} }
@ -254,33 +208,28 @@ ConfigTokenSeek(ConfigParserState * state)
* token by looking at the next character * token by looking at the next character
*/ */
if (feof(state->stream)) if (feof(state->stream)) {
{
state->tokenType = TOKEN_EOF; state->tokenType = TOKEN_EOF;
return; return;
} }
if (ConfigIsNameChar(c)) if (ConfigIsNameChar(c)) {
{
state->tokenLen = 0; state->tokenLen = 0;
/* Read the key/macro into state->token */ /* Read the key/macro into state->token */
if (!state->token) if (!state->token) {
{
state->tokenSize = CONFIG_BUFFER_BLOCK; state->tokenSize = CONFIG_BUFFER_BLOCK;
state->token = Malloc(CONFIG_BUFFER_BLOCK); state->token = malloc(CONFIG_BUFFER_BLOCK);
} }
state->token[state->tokenLen] = c; state->token[state->tokenLen] = c;
state->tokenLen++; state->tokenLen++;
while (ConfigIsNameChar((c = fgetc(state->stream)))) while (ConfigIsNameChar((c = fgetc(state->stream)))) {
{
state->token[state->tokenLen] = c; state->token[state->tokenLen] = c;
state->tokenLen++; state->tokenLen++;
if (state->tokenLen >= state->tokenSize) if (state->tokenLen >= state->tokenSize) {
{
state->tokenSize += CONFIG_BUFFER_BLOCK; state->tokenSize += CONFIG_BUFFER_BLOCK;
state->token = Realloc(state->token, state->token = realloc(state->token,
state->tokenSize); state->tokenSize);
} }
} }
@ -288,25 +237,18 @@ ConfigTokenSeek(ConfigParserState * state)
state->token[state->tokenLen] = '\0'; state->token[state->tokenLen] = '\0';
state->tokenLen++; state->tokenLen++;
if (!isspace(c)) if (!isspace(c)) {
{
state->tokenType = TOKEN_UNKNOWN; state->tokenType = TOKEN_UNKNOWN;
} } else {
else
{
state->tokenType = TOKEN_NAME; state->tokenType = TOKEN_NAME;
if (c == '\n') if (c == '\n') {
{
state->line++; state->line++;
} }
} }
} } else {
else switch (c) {
{
switch (c)
{
case '=': case '=':
state->tokenType = TOKEN_MACRO_ASSIGNMENT; state->tokenType = TOKEN_MACRO_ASSIGNMENT;
break; break;
@ -315,28 +257,24 @@ ConfigTokenSeek(ConfigParserState * state)
state->tokenType = TOKEN_VALUE; state->tokenType = TOKEN_VALUE;
/* read the value into state->curtok */ /* read the value into state->curtok */
while ((c = fgetc(state->stream)) != '"') while ((c = fgetc(state->stream)) != '"') {
{ if (c == '\n') {
if (c == '\n')
{
state->line++; state->line++;
} }
/* /*
* End of the stream reached without finding * End of the stream reached without finding
* a closing quote * a closing quote
*/ */
if (feof(state->stream)) if (feof(state->stream)) {
{
state->tokenType = TOKEN_EOF; state->tokenType = TOKEN_EOF;
break; break;
} }
state->token[state->tokenLen] = c; state->token[state->tokenLen] = c;
state->tokenLen++; state->tokenLen++;
if (state->tokenLen >= state->tokenSize) if (state->tokenLen >= state->tokenSize) {
{
state->tokenSize += CONFIG_BUFFER_BLOCK; state->tokenSize += CONFIG_BUFFER_BLOCK;
state->token = Realloc(state->token, state->token = realloc(state->token,
state->tokenSize); state->tokenSize);
} }
} }
@ -355,15 +293,13 @@ ConfigTokenSeek(ConfigParserState * state)
case '$': case '$':
state->tokenLen = 0; state->tokenLen = 0;
/* read the macro name into state->curtok */ /* 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->token[state->tokenLen] = c;
state->tokenLen++; state->tokenLen++;
if (state->tokenLen >= state->tokenSize) if (state->tokenLen >= state->tokenSize) {
{
state->tokenSize += CONFIG_BUFFER_BLOCK; state->tokenSize += CONFIG_BUFFER_BLOCK;
state->token = Realloc(state->token, state->token = realloc(state->token,
state->tokenSize); state->tokenSize);
} }
} }
@ -380,81 +316,67 @@ ConfigTokenSeek(ConfigParserState * state)
} }
/* Resize curtok to only use the bytes it needs */ /* Resize curtok to only use the bytes it needs */
if (state->tokenLen) if (state->tokenLen) {
{
state->tokenSize = state->tokenLen; state->tokenSize = state->tokenLen;
state->token = Realloc(state->token, state->tokenSize); state->token = realloc(state->token, state->tokenSize);
} }
} }
static int static int
ConfigExpect(ConfigParserState * state, ConfigToken tokenType) ConfigExpect(ConfigParserState *state, ConfigToken tokenType)
{ {
return state->tokenType == tokenType; return state->tokenType == tokenType;
} }
static HashMap * static HashMap *
ConfigParseBlock(ConfigParserState * state, int level) ConfigParseBlock(ConfigParserState *state, int level)
{ {
HashMap *block = HashMapCreate(); HashMap *block = HashMapCreate();
ConfigTokenSeek(state); ConfigTokenSeek(state);
while (ConfigExpect(state, TOKEN_NAME)) while (ConfigExpect(state, TOKEN_NAME)) {
{ char *name = malloc(state->tokenLen + 1);
char *name = Malloc(state->tokenLen + 1);
strcpy(name, state->token); strcpy(name, state->token);
ConfigTokenSeek(state); ConfigTokenSeek(state);
if (ConfigExpect(state, TOKEN_VALUE) || ConfigExpect(state, TOKEN_MACRO)) if (ConfigExpect(state, TOKEN_VALUE) || ConfigExpect(state, TOKEN_MACRO)) {
{
ConfigDirective *directive; ConfigDirective *directive;
directive = Malloc(sizeof(ConfigDirective)); directive = malloc(sizeof(ConfigDirective));
directive->children = NULL; directive->children = NULL;
directive->values = ArrayCreate(); directive->values = ArrayCreate();
while (ConfigExpect(state, TOKEN_VALUE) || while (ConfigExpect(state, TOKEN_VALUE) ||
ConfigExpect(state, TOKEN_MACRO)) ConfigExpect(state, TOKEN_MACRO)) {
{
char *dval; char *dval;
char *dvalCpy; char *dvalCpy;
if (ConfigExpect(state, TOKEN_VALUE)) if (ConfigExpect(state, TOKEN_VALUE)) {
{
dval = state->token; dval = state->token;
} } else if (ConfigExpect(state, TOKEN_MACRO)) {
else if (ConfigExpect(state, TOKEN_MACRO))
{
dval = HashMapGet(state->macroMap, state->token); dval = HashMapGet(state->macroMap, state->token);
if (!dval) if (!dval) {
{
goto error; goto error;
} }
} } else {
else
{
dval = NULL; /* Should never happen */ dval = NULL; /* Should never happen */
} }
/* dval is a pointer which is overwritten with the next /* dval is a pointer which is overwritten with the next token. */
* token. */ dvalCpy = malloc(strlen(dval) + 1);
dvalCpy = Malloc(strlen(dval) + 1);
strcpy(dvalCpy, dval); strcpy(dvalCpy, dval);
ArrayAdd(directive->values, dvalCpy); ArrayAdd(directive->values, dvalCpy);
ConfigTokenSeek(state); ConfigTokenSeek(state);
} }
if (ConfigExpect(state, TOKEN_BLOCK_OPEN)) if (ConfigExpect(state, TOKEN_BLOCK_OPEN)) {
{
/* token_seek(state); */ /* token_seek(state); */
directive->children = ConfigParseBlock(state, level + 1); directive->children = ConfigParseBlock(state, level + 1);
if (!directive->children) if (!directive->children) {
{
goto error; goto error;
} }
} }
@ -469,49 +391,37 @@ ConfigParseBlock(ConfigParserState * state, int level)
* NULL is sent to ConfigDirectiveFree(), making it a no-op. * NULL is sent to ConfigDirectiveFree(), making it a no-op.
*/ */
ConfigDirectiveFree(HashMapSet(block, name, directive)); 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); strcpy(valueCopy, state->token);
Free(HashMapSet(state->macroMap, name, valueCopy)); free(HashMapSet(state->macroMap, name, state->token));
ConfigTokenSeek(state); ConfigTokenSeek(state);
} } else {
else
{
goto error; goto error;
} }
} } else {
else
{
goto error; goto error;
} }
if (!ConfigExpect(state, TOKEN_SEMICOLON)) if (!ConfigExpect(state, TOKEN_SEMICOLON)) {
{
goto error; goto error;
} }
ConfigTokenSeek(state); ConfigTokenSeek(state);
} }
if (ConfigExpect(state, level ? TOKEN_BLOCK_CLOSE : TOKEN_EOF)) if (ConfigExpect(state, level ? TOKEN_BLOCK_CLOSE : TOKEN_EOF)) {
{
ConfigTokenSeek(state); ConfigTokenSeek(state);
return block; return block;
} } else {
else
{
goto error; goto error;
} }
error: error:
/* Only free the very top level, because this will recurse */ /* Only free the very top level, because this will recurse */
if (!level) if (!level) {
{
ConfigFree(block); ConfigFree(block);
} }
return NULL; return NULL;
@ -524,17 +434,14 @@ ConfigParse(FILE * stream)
HashMap *conf; HashMap *conf;
ConfigParserState *state; ConfigParserState *state;
result = Malloc(sizeof(ConfigParseResult)); result = malloc(sizeof(ConfigParseResult));
state = ConfigParserStateCreate(stream); state = ConfigParserStateCreate(stream);
conf = ConfigParseBlock(state, 0); conf = ConfigParseBlock(state, 0);
if (!conf) if (!conf) {
{
result->ok = 0; result->ok = 0;
result->data.lineNumber = state->line; result->data.lineNumber = state->line;
} } else {
else
{
result->ok = 1; result->ok = 1;
result->data.confMap = conf; result->data.confMap = conf;
} }
@ -542,3 +449,4 @@ ConfigParse(FILE * stream)
ConfigParserStateFree(state); ConfigParserStateFree(state);
return result; return result;
} }

View file

@ -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 <HashMap.h> #include <HashMap.h>
#include <Memory.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <string.h> #include <stdlib.h>
typedef struct HashMapBucket typedef struct HashMapBucket {
{ uint32_t hash;
unsigned long hash;
char *key;
void *value; void *value;
} HashMapBucket; } HashMapBucket;
struct HashMap struct HashMap {
{
size_t count; size_t count;
size_t capacity; size_t capacity;
HashMapBucket **entries; HashMapBucket **entries;
unsigned long (*hashFunc) (const char *);
float maxLoad; float maxLoad;
size_t iterator;
}; };
static unsigned long static uint32_t
HashMapHashKey(const char *key) HashMapHashKey(const char *key)
{ {
unsigned long hash = 2166136261u; uint32_t hash = 2166136261u;
size_t i = 0; size_t i = 0;
while (key[i]) while (key[i])
{ {
hash ^= (unsigned char) key[i]; hash ^= (uint8_t) key[i];
hash *= 16777619; hash *= 16777619;
i++; i++;
@ -64,8 +34,9 @@ HashMapHashKey(const char *key)
return hash; return hash;
} }
static int static int
HashMapGrow(HashMap * map) HashMapGrow(HashMap *map)
{ {
size_t oldCapacity; size_t oldCapacity;
size_t i; size_t i;
@ -79,15 +50,12 @@ HashMapGrow(HashMap * map)
oldCapacity = map->capacity; oldCapacity = map->capacity;
map->capacity *= 2; map->capacity *= 2;
newEntries = Malloc(map->capacity * sizeof(HashMapBucket *)); newEntries = calloc(map->capacity, sizeof(HashMapBucket *));
if (!newEntries) if (!newEntries)
{ {
map->capacity /= 2;
return 0; return 0;
} }
memset(&newEntries, 0, map->capacity * sizeof(HashMapBucket *));
for (i = 0; i < oldCapacity; i++) for (i = 0; i < oldCapacity; i++)
{ {
/* If there is a value here, and it isn't a tombstone */ /* If there is a value here, and it isn't a tombstone */
@ -102,7 +70,7 @@ HashMapGrow(HashMap * map)
{ {
if (!newEntries[index]->hash) if (!newEntries[index]->hash)
{ {
Free(newEntries[index]); free(newEntries[index]);
newEntries[index] = map->entries[i]; newEntries[index] = map->entries[i];
break; break;
} }
@ -119,11 +87,11 @@ HashMapGrow(HashMap * map)
else else
{ {
/* Either NULL or a tombstone */ /* Either NULL or a tombstone */
Free(map->entries[i]); free(map->entries[i]);
} }
} }
Free(map->entries); free(map->entries);
map->entries = newEntries; map->entries = newEntries;
return 1; return 1;
} }
@ -131,8 +99,7 @@ HashMapGrow(HashMap * map)
HashMap * HashMap *
HashMapCreate(void) HashMapCreate(void)
{ {
HashMap *map = Malloc(sizeof(HashMap)); HashMap *map = malloc(sizeof(HashMap));
if (!map) if (!map)
{ {
return NULL; return NULL;
@ -141,25 +108,21 @@ HashMapCreate(void)
map->maxLoad = 0.75; map->maxLoad = 0.75;
map->count = 0; map->count = 0;
map->capacity = 16; 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) if (!map->entries)
{ {
Free(map); free(map);
return NULL; return NULL;
} }
memset(map->entries, 0, map->capacity * sizeof(HashMapBucket *));
return map; return map;
} }
void * void *
HashMapDelete(HashMap * map, const char *key) HashMapDelete(HashMap *map, const char *key)
{ {
unsigned long hash; uint32_t hash;
size_t index; size_t index;
if (!map || !key) if (!map || !key)
@ -167,7 +130,7 @@ HashMapDelete(HashMap * map, const char *key)
return NULL; return NULL;
} }
hash = map->hashFunc(key); hash = HashMapHashKey(key);
index = hash % map->capacity; index = hash % map->capacity;
for (;;) for (;;)
@ -192,7 +155,7 @@ HashMapDelete(HashMap * map, const char *key)
} }
void void
HashMapFree(HashMap * map) HashMapFree(HashMap *map)
{ {
if (map) if (map)
{ {
@ -202,18 +165,18 @@ HashMapFree(HashMap * map)
{ {
if (map->entries[i]) if (map->entries[i])
{ {
Free(map->entries[i]); free(map->entries[i]);
} }
} }
Free(map->entries);
Free(map);
} }
free(map);
} }
void * void *
HashMapGet(HashMap * map, const char *key) HashMapGet(HashMap *map, const char *key)
{ {
unsigned long hash; uint32_t hash;
size_t index; size_t index;
if (!map || !key) if (!map || !key)
@ -221,7 +184,7 @@ HashMapGet(HashMap * map, const char *key)
return NULL; return NULL;
} }
hash = map->hashFunc(key); hash = HashMapHashKey(key);
index = hash % map->capacity; index = hash % map->capacity;
for (;;) for (;;)
@ -244,44 +207,31 @@ HashMapGet(HashMap * map, const char *key)
return NULL; return NULL;
} }
int void
HashMapIterate(HashMap * map, char **key, void **value) HashMapIterate(HashMap *map, void (*iteratorFunc)(void *))
{ {
size_t i;
if (!map) if (!map)
{ {
return 0; return;
} }
if (map->iterator >= map->capacity) for (i = 0; i < map->capacity; i++)
{ {
map->iterator = 0; HashMapBucket *bucket = map->entries[i];
*key = NULL;
*value = NULL;
return 0;
}
while (map->iterator < map->capacity)
{
HashMapBucket *bucket = map->entries[map->iterator];
map->iterator++;
if (bucket) if (bucket)
{ {
*key = bucket->key; iteratorFunc(bucket->value);
*value = bucket->value;
return 1;
} }
} }
map->iterator = 0;
return 0;
} }
void void
HashMapMaxLoadSet(HashMap * map, float load) HashMapMaxLoadSet(HashMap *map, float load)
{ {
if (!map || (load > 1.0 || load <= 0)) if (!map)
{ {
return; return;
} }
@ -289,21 +239,11 @@ HashMapMaxLoadSet(HashMap * map, float load)
map->maxLoad = load; map->maxLoad = load;
} }
void
HashMapFunctionSet(HashMap * map, unsigned long (*hashFunc) (const char *))
{
if (!map || !hashFunc)
{
return;
}
map->hashFunc = hashFunc;
}
void * 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; size_t index;
if (!map || !key || !value) if (!map || !key || !value)
@ -316,7 +256,7 @@ HashMapSet(HashMap * map, char *key, void *value)
HashMapGrow(map); HashMapGrow(map);
} }
hash = map->hashFunc(key); hash = HashMapHashKey(key);
index = hash % map->capacity; index = hash % map->capacity;
for (;;) for (;;)
@ -325,14 +265,13 @@ HashMapSet(HashMap * map, char *key, void *value)
if (!bucket) if (!bucket)
{ {
bucket = Malloc(sizeof(HashMapBucket)); bucket = malloc(sizeof(HashMapBucket));
if (!bucket) if (!bucket)
{ {
break; break;
} }
bucket->hash = hash; bucket->hash = hash;
bucket->key = key;
bucket->value = value; bucket->value = value;
map->entries[index] = bucket; map->entries[index] = bucket;
map->count++; map->count++;
@ -342,7 +281,6 @@ HashMapSet(HashMap * map, char *key, void *value)
if (!bucket->hash) if (!bucket->hash)
{ {
bucket->hash = hash; bucket->hash = hash;
bucket->key = key;
bucket->value = value; bucket->value = value;
break; break;
} }
@ -350,7 +288,6 @@ HashMapSet(HashMap * map, char *key, void *value)
if (bucket->hash == hash) if (bucket->hash == hash)
{ {
void *oldValue = bucket->value; void *oldValue = bucket->value;
bucket->value = value; bucket->value = value;
return oldValue; return oldValue;
} }
@ -361,16 +298,3 @@ HashMapSet(HashMap * map, char *key, void *value)
return NULL; return NULL;
} }
void
HashMapIterateFree(char *key, void *value)
{
if (key)
{
Free(key);
}
if (value)
{
Free(value);
}
}

402
src/Log.c
View file

@ -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 <Log.h> #include <Log.h>
#include <Memory.h> #include <stdlib.h>
#include <string.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#include <ctype.h> #include <ctype.h>
#include <stdarg.h> #include <stdarg.h>
#include <pthread.h>
#define LOG_TSBUFFER 64 #define LOG_TSBUFFER 64
struct LogConfig struct LogConfig {
{ LogLevel level;
int level;
size_t indent; size_t indent;
FILE *out; FILE *out;
int flags; int flags;
char *tsFmt; 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 void
LogConfigFlagClear(LogConfig * config, int flags) Log(LogConfig *config, LogLevel level, const char *msg, ...)
{ {
if (!config) int i;
{
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 doColor; int doColor;
char indicator; char indicator;
va_list argp; va_list argp;
@ -226,17 +41,9 @@ Log(LogConfig * config, int level, const char *msg,...)
return; return;
} }
pthread_mutex_lock(&config->lock); for (i = 0; i < config->indent; i++)
if (LogConfigFlagGet(config, LOG_FLAG_SYSLOG))
{ {
/* No further print logic is needed; syslog will handle it all fputc(' ', config->out);
* for us. */
va_start(argp, msg);
vsyslog(level, msg, argp);
va_end(argp);
pthread_mutex_unlock(&config->lock);
return;
} }
doColor = LogConfigFlagGet(config, LOG_FLAG_COLOR) doColor = LogConfigFlagGet(config, LOG_FLAG_COLOR)
@ -245,13 +52,9 @@ Log(LogConfig * config, int level, const char *msg,...)
if (doColor) if (doColor)
{ {
char *ansi; char *ansi;
switch (level) switch (level)
{ {
case LOG_EMERG: case LOG_ERROR:
case LOG_ALERT:
case LOG_CRIT:
case LOG_ERR:
/* Bold Red */ /* Bold Red */
ansi = "\033[1;31m"; ansi = "\033[1;31m";
break; break;
@ -259,11 +62,11 @@ Log(LogConfig * config, int level, const char *msg,...)
/* Bold Yellow */ /* Bold Yellow */
ansi = "\033[1;33m"; ansi = "\033[1;33m";
break; break;
case LOG_NOTICE: case LOG_TASK:
/* Bold Magenta */ /* Bold Magenta */
ansi = "\033[1;35m"; ansi = "\033[1;35m";
break; break;
case LOG_INFO: case LOG_MESSAGE:
/* Bold Green */ /* Bold Green */
ansi = "\033[1;32m"; ansi = "\033[1;32m";
break; break;
@ -293,7 +96,7 @@ Log(LogConfig * config, int level, const char *msg,...)
if (tsLength) if (tsLength)
{ {
fputs(tsBuffer, config->out); fputs(tsBuffer, config->out);
if (!isspace((unsigned char) tsBuffer[tsLength - 1])) if (!isspace(tsBuffer[tsLength - 1]))
{ {
fputc(' ', config->out); fputc(' ', config->out);
} }
@ -302,15 +105,6 @@ Log(LogConfig * config, int level, const char *msg,...)
switch (level) switch (level)
{ {
case LOG_EMERG:
indicator = '#';
break;
case LOG_ALERT:
indicator = '@';
break;
case LOG_CRIT:
indicator = 'X';
break;
case LOG_ERROR: case LOG_ERROR:
indicator = 'x'; indicator = 'x';
break; break;
@ -327,7 +121,7 @@ Log(LogConfig * config, int level, const char *msg,...)
indicator = '*'; indicator = '*';
break; break;
default: default:
indicator = '?'; indicator = ' ';
break; break;
} }
@ -340,23 +134,177 @@ Log(LogConfig * config, int level, const char *msg,...)
} }
fputc(' ', config->out); fputc(' ', config->out);
for (i = 0; i < config->indent; i++)
{
fputc(' ', config->out);
}
va_start(argp, msg); va_start(argp, msg);
vfprintf(config->out, msg, argp); vfprintf(config->out, msg, argp);
fputc('\n', config->out); fputc('\n', config->out);
va_end(argp); va_end(argp);
/* If we are debugging, there might be something that's going to /* If we are debugging, there might be something that's
* segfault the program coming up, so flush the output stream * going to segfault the program coming up, so flush the
* immediately. */ * output stream immediately.
*/
if (config->level == LOG_DEBUG) if (config->level == LOG_DEBUG)
{ {
fflush(config->out); 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;
}
} }

View file

@ -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 <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <grp.h>
#include <pwd.h>
#include <Memory.h>
#include <TelodendriaConfig.h>
#include <Log.h> #include <Log.h>
#include <HashMap.h> #include <HashMap.h>
#include <Config.h> #include <Config.h>
#include <HttpServer.h> #include <Base64.h>
#include <Matrix.h>
#include <Db.h>
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);
}
typedef enum ArgFlag typedef enum ArgFlag
{ {
ARG_VERSION = (1 << 0), ARG_VERSION = (1 << 0),
ARG_CONFIGTEST = (1 << 1), ARG_USAGE = (1 << 1)
ARG_VERBOSE = (1 << 2)
} ArgFlag; } ArgFlag;
static void static void
TelodendriaPrintHeader(LogConfig * lc) TelodendriaPrintHeader(LogConfig *lc)
{ {
Log(lc, LOG_MESSAGE, Log(lc, LOG_MESSAGE,
" _____ _ _ _ _"); " _____ _ _ _ _");
@ -119,12 +32,20 @@ TelodendriaPrintHeader(LogConfig * lc)
Log(lc, LOG_MESSAGE, Log(lc, LOG_MESSAGE,
"Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>"); "Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>");
Log(lc, LOG_MESSAGE, Log(lc, LOG_MESSAGE,
"Documentation/Support: https://telodendria.io"); "Documentation/Support: https://bancino.net/pub/Telodendria");
Log(lc, LOG_MESSAGE, ""); Log(lc, LOG_MESSAGE, "");
} }
int static void
main(int argc, char **argv) TelodendriaPrintUsage(LogConfig *lc)
{
Log(lc, LOG_MESSAGE, "Usage:");
Log(lc, LOG_MESSAGE, " -c <file> 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; LogConfig *lc;
int exit = EXIT_SUCCESS; int exit = EXIT_SUCCESS;
@ -132,69 +53,38 @@ main(int argc, char **argv)
/* Arg parsing */ /* Arg parsing */
int opt; int opt;
int flags = 0; int flags = 0;
char *configArg = "/etc/telodendria.conf"; char *configArg = NULL;
/* Config file */ /* Config file */
FILE *configFile = NULL; FILE *configFile = NULL;
ConfigParseResult *configParseResult = NULL; ConfigParseResult *configParseResult = NULL;
HashMap *config = 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(); lc = LogConfigCreate();
/* TODO: Remove */
LogConfigLevelSet(lc, LOG_DEBUG);
if (!lc) if (!lc)
{ {
printf("Fatal error: unable to allocate memory for logger.\n"); printf("Fatal error: unable to allocate memory for logger.\n");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
MemoryHook(TelodendriaMemoryHook, lc);
TelodendriaPrintHeader(lc); TelodendriaPrintHeader(lc);
#ifdef __OpenBSD__ while ((opt = getopt(argc, argv, "c:Vh")) != -1)
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)
{ {
switch (opt) switch (opt)
{ {
case 'f': case 'c':
configArg = optarg; configArg = optarg;
break; break;
case 'V': case 'V':
flags |= ARG_VERSION; flags |= ARG_VERSION;
break; break;
case 'v': case 'h':
flags |= ARG_VERBOSE; flags |= ARG_USAGE;
break;
case 'n':
flags |= ARG_CONFIGTEST;
break;
case '?':
exit = EXIT_FAILURE;
goto finish;
default: default:
break; break;
} }
@ -205,21 +95,26 @@ main(int argc, char **argv)
goto finish; goto finish;
} }
if (strcmp(configArg, "-") == 0) if (flags & ARG_USAGE)
{ {
configFile = stdin; TelodendriaPrintUsage(lc);
goto finish;
} }
else
if (!configArg)
{ {
fclose(stdin); Log(lc, LOG_ERROR, "No configuration file specified.");
#ifdef __OpenBSD__ TelodendriaPrintUsage(lc);
if (unveil(configArg, "r") != 0)
{
Log(lc, LOG_ERROR, "Unable to unveil() configuration file '%s' for reading.", configArg);
exit = EXIT_FAILURE; exit = EXIT_FAILURE;
goto finish; goto finish;
} }
#endif
if (strcmp(configArg, "-") == 0)
{
configFile = stdout;
}
else
{
configFile = fopen(configArg, "r"); configFile = fopen(configArg, "r");
if (!configFile) 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); configParseResult = ConfigParse(configFile);
if (!ConfigParseResultOk(configParseResult)) if (!ConfigParseResultOk(configParseResult))
{ {
Log(lc, LOG_ERROR, "Syntax error on line %d.", Log(lc, LOG_ERROR, "Syntax error on line %d.",
@ -243,257 +140,18 @@ main(int argc, char **argv)
config = ConfigParseResultGet(configParseResult); config = ConfigParseResultGet(configParseResult);
ConfigParseResultFree(configParseResult); ConfigParseResultFree(configParseResult);
Log(lc, LOG_DEBUG, "Closing configuration file.");
fclose(configFile); fclose(configFile);
tConfig = TelodendriaConfigParse(config, lc); /* Configure log file */
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);
finish: finish:
Log(lc, LOG_TASK, "Shutting down..."); if (config)
if (httpServer)
{ {
HttpServerFree(httpServer); Log(lc, LOG_DEBUG, "Freeing configuration structure.");
Log(lc, LOG_DEBUG, "Freed HTTP Server."); ConfigFree(config);
} }
Log(lc, LOG_DEBUG, "Freeing log configuration and exiting with code '%d'.", exit);
/*
* 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);
LogConfigFree(lc); LogConfigFree(lc);
MemoryFreeAll();
fclose(stderr);
return exit; return exit;
} }

View file

@ -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 #ifndef TELODENDRIA_ARRAY_H
#define TELODENDRIA_ARRAY_H #define TELODENDRIA_ARRAY_H
@ -30,30 +6,27 @@
typedef struct Array Array; typedef struct Array Array;
extern Array * extern Array *
ArrayCreate(void); ArrayCreate(void);
extern size_t extern size_t
ArraySize(Array *); ArraySize(Array *array);
extern void * extern void *
ArrayGet(Array *, size_t); ArrayGet(Array *array, size_t index);
extern int extern int
ArrayInsert(Array *, void *, size_t); ArrayInsert(Array *, void *value, size_t index);
extern int extern int
ArrayAdd(Array *, void *); ArrayAdd(Array *array, void *value);
extern void * extern void *
ArrayDelete(Array *, size_t); ArrayDelete(Array *array, size_t index);
extern void extern void
ArraySort(Array *, int (*) (void *, void *)); ArrayFree(Array *array);
extern void
ArrayFree(Array *);
extern int extern int
ArrayTrim(Array *); ArrayTrim(Array *array);
#endif /* TELODENDRIA_ARRAY_H */ #endif

View file

@ -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 #ifndef TELODENDRIA_BASE64_H
#define TELODENDRIA_BASE64_H #define TELODENDRIA_BASE64_H
#include <stddef.h> #include <stddef.h>
extern size_t extern size_t
Base64EncodedSize(size_t); Base64EncodedSize(size_t inputSize);
extern size_t extern size_t
Base64DecodedSize(const char *, size_t); Base64DecodedSize(const char *base64, size_t len);
extern char * extern char *
Base64Encode(const char *, size_t); Base64Encode(const char *input, size_t len);
extern char * extern char *
Base64Decode(const char *, size_t); Base64Decode(const char *input, size_t len);
extern void extern void
Base64Unpad(char *, size_t); Base64Unpad(char *base64, size_t length);
extern int extern int
Base64Pad(char **, size_t); Base64Pad(char **base64Ptr, size_t length);
#endif
#endif /* TELODENDRIA_BASE64_H */

View file

@ -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 #ifndef TELODENDRIA_CONFIG_H
#define TELODENDRIA_CONFIG_H #define TELODENDRIA_CONFIG_H
@ -45,164 +6,32 @@
#include <HashMap.h> #include <HashMap.h>
#include <Array.h> #include <Array.h>
/*
* A configuration directive is a single key that may have at least one
* value, and any number of children.
*/
typedef struct ConfigDirective ConfigDirective; 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; 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 * 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 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 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 * 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 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 * 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 * 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 extern void
ConfigFree(HashMap *); ConfigFree(HashMap *conf);
#endif /* TELODENDRIA_CONFIG_H */ #endif /* TELODENDRIA_CONFIG_H */

View file

@ -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> * 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 #ifndef TELODENDRIA_HASHMAP_H
#define TELODENDRIA_HASHMAP_H #define TELODENDRIA_HASHMAP_H
#include <stddef.h>
/*
* 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; 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 * extern HashMap *
HashMapCreate(void); HashMapCreate(void);
extern 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 extern void
HashMapFunctionSet(HashMap *, unsigned long (*) (const char *)); HashMapIterate(HashMap *map, void (*iteratorFunc)(void *));
extern void *
HashMapSet(HashMap *, char *, void *);
extern void *
HashMapGet(HashMap *, const char *);
extern void *
HashMapDelete(HashMap *, const char *);
extern int
HashMapIterate(HashMap *, char **, 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 extern void
HashMapFree(HashMap *); HashMapFree(HashMap *map);
#endif /* TELODENDRIA_HASHMAP_H */ #endif /* TELODENDRIA_HASHMAP_H */

View file

@ -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 #ifndef TELODENDRIA_HTTP_H
#define TELODENDRIA_HTTP_H #define TELODENDRIA_HTTP_H
#include <stdio.h> typedef enum HttpRequestMethod {
#include <HashMap.h>
typedef enum HttpRequestMethod
{
HTTP_METHOD_UNKNOWN,
HTTP_GET, HTTP_GET,
HTTP_HEAD, HTTP_HEAD,
HTTP_POST, HTTP_POST,
@ -42,8 +13,7 @@ typedef enum HttpRequestMethod
HTTP_PATCH HTTP_PATCH
} HttpRequestMethod; } HttpRequestMethod;
typedef enum HttpStatus typedef enum HttpStatus {
{
/* Informational responses */ /* Informational responses */
HTTP_CONTINUE = 100, HTTP_CONTINUE = 100,
HTTP_SWITCHING_PROTOCOLS = 101, HTTP_SWITCHING_PROTOCOLS = 101,
@ -104,25 +74,23 @@ typedef enum HttpStatus
HTTP_NETWORK_AUTH_REQUIRED = 511 HTTP_NETWORK_AUTH_REQUIRED = 511
} HttpStatus; } HttpStatus;
extern const char * struct HttpRequest {
HttpStatusToString(const HttpStatus); HttpRequestMethod method;
};
struct HttpResponse {
HttpStatus status;
};
extern char *
HttpGetStatusString(const HttpStatus httpStatus);
extern HttpRequestMethod extern HttpRequestMethod
HttpRequestMethodFromString(const char *); HttpRequestMethodFromString(const char *requestMethod);
extern const char * typedef struct HttpRequest HttpRequest;
HttpRequestMethodToString(const HttpRequestMethod); typedef struct HttpResponse HttpResponse;
extern char * typedef void (*HttpHandler)(HttpRequest *, HttpResponse *);
HttpUrlEncode(char *);
extern char *
HttpUrlDecode(char *);
extern HashMap *
HttpParamDecode(char *);
extern char *
HttpParamEncode(HashMap *);
#endif #endif

View file

@ -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 #ifndef TELODENDRIA_JSON_H
#define TELODENDRIA_JSON_H #define TELODENDRIA_JSON_H
#include <HashMap.h> #include <HashMap.h>
#include <Array.h> #include <Array.h>
#include <stdio.h> typedef enum JsonType {
#include <stddef.h> JSON_OBJECT,
JSON_ARRAY,
/* JSON_STRING,
* All the possible JSON types. This enumeration is used to identify JSON_INTEGER,
* the type of the value stored in a JsonValue. JSON_FLOAT,
*/ JSON_BOOLEAN,
typedef enum JsonType JSON_NULL
{
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 */
} JsonType; } JsonType;
/* typedef struct JsonValue {
* A JsonValue encapsulates all the possible values that can be stored JsonType type;
* in a JSON object as a single type, so as to provide a consistent union as {
* API for accessing and setting them. It is an opaque structure that HashMap *object;
* can be managed entirely by the functions defined in this API. Array *array;
* char *string;
* Note that in the case of objects, arrays, and strings, this structure int64_t integer;
* only stores pointers to allocated data, it doesn't store the data double floating;
* itself. JsonValues only store integers, floats, booleans, and NULL int boolean : 1;
* in their memory. Anything else must be freed separately. };
*/ } JsonValue;
typedef struct JsonValue 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 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 * 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 * extern HashMap *
JsonValueAsObject(JsonValue *); JsonValueAsObject(JsonValue *value);
/*
* 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.
*/
extern JsonValue * extern JsonValue *
JsonValueArray(Array * array); JsonValueArray(Array *array);
extern Array * extern Array *
JsonValueAsArray(JsonValue * value); JsonValueAsArray(JsonValue *value);
extern JsonValue * extern JsonValue *
JsonValueString(char *string); JsonValueString(char *string);
extern JsonValue * extern JsonValue *
JsonValueInteger(long integer); JsonValueInteger(int64_t integer);
extern JsonValue * extern JsonValue *
JsonValueFloat(double floating); JsonValueFloat(double floating);
extern JsonValue * 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 * extern JsonValue *
JsonValueNull(void); JsonValueNull(void);
/* extern void *
* Free the memory being used by a JSON value. Note that this will JsonValueFree(JsonValue *value);
* 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 char *
* Recursively free a HashMap of JsonValues. This iterates over all JsonEncode(HashMap *object);
* 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 *);
/*
* 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 * extern HashMap *
JsonDecode(FILE *); JsonDecode(char *string);
#endif /* TELODENDRIA_JSON_H */ #endif

View file

@ -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 #ifndef TELODENDRIA_LOG_H
#define TELODENDRIA_LOG_H #define TELODENDRIA_LOG_H
#include <stdio.h> #include <stdio.h>
#include <stddef.h> #include <stddef.h>
#include <syslog.h>
/* typedef enum LogLevel {
* I used to define all my own constants, but now I use LOG_ERROR,
* those defined in syslog.h. Instead of replacing all the LOG_WARNING,
* references, I just map the old names to the new ones. If LOG_TASK,
* you're ever bored one day, you can remove these, and then LOG_MESSAGE,
* go fix all the compiler errors that arise. Should be pretty LOG_DEBUG
* easy, just mind numbing. } LogLevel;
*/
#define LOG_ERROR LOG_ERR
#define LOG_TASK LOG_NOTICE
#define LOG_MESSAGE LOG_INFO
/* typedef enum LogFlag {
* The possible flags that can be applied to alter the behavior of LOG_FLAG_COLOR = (1 << 0)
* 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 */
} LogFlag; } 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; 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 * extern LogConfig *
LogConfigCreate(void); 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);
extern void extern void
LogConfigOutputSet(LogConfig *, FILE *); LogConfigFree(LogConfig *config);
extern void extern void
LogConfigFlagSet(LogConfig *, int); LogConfigLevelSet(LogConfig *config, LogLevel level);
extern LogLevel
LogConfigLevelGet(LogConfig *config);
extern void extern void
LogConfigFlagClear(LogConfig *, int); LogConfigIndentSet(LogConfig *config, size_t indent);
extern size_t
LogConfigIndentGet(LogConfig *config);
extern void 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 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 #endif