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