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