forked from lda/telodendria
Initial revision
This commit is contained in:
commit
d102ba8676
|
@ -0,0 +1 @@
|
|||
build
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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
|
||||
}
|
|
@ -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";
|
|
@ -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"
|
|
@ -0,0 +1,174 @@
|
|||
#include <Array.h>
|
||||
|
||||
#ifndef ARRAY_BLOCK
|
||||
#define ARRAY_BLOCK 16
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct Array {
|
||||
void **entries; /* An array of void pointers, to store any data */
|
||||
size_t allocated; /* Elements allocated on the heap */
|
||||
size_t size; /* Elements actually filled */
|
||||
};
|
||||
|
||||
int
|
||||
ArrayAdd(Array *array, void *value)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ArrayInsert(array, value, array->size);
|
||||
}
|
||||
|
||||
Array *
|
||||
ArrayCreate(void)
|
||||
{
|
||||
Array *array = malloc(sizeof(Array));
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
array->size = 0;
|
||||
array->allocated = ARRAY_BLOCK;
|
||||
array->entries = malloc(sizeof(void *) * ARRAY_BLOCK);
|
||||
|
||||
if (!array->entries)
|
||||
{
|
||||
free(array);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
void *
|
||||
ArrayDelete(Array *array, size_t index)
|
||||
{
|
||||
size_t i;
|
||||
void *element;
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
element = array->entries[index];
|
||||
|
||||
for (i = index; i < array->size - 1; i++)
|
||||
{
|
||||
array->entries[i] = array->entries[i + 1];
|
||||
}
|
||||
|
||||
array->size--;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
void
|
||||
ArrayFree(Array *array)
|
||||
{
|
||||
if (array)
|
||||
{
|
||||
free(array->entries);
|
||||
free(array);
|
||||
}
|
||||
}
|
||||
|
||||
void *
|
||||
ArrayGet(Array *array, size_t index)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (index >= array->size)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return array->entries[index];
|
||||
}
|
||||
|
||||
|
||||
extern int
|
||||
ArrayInsert(Array *array, void *value, size_t index)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!array || !value || index > array->size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (array->size >= array->allocated)
|
||||
{
|
||||
void **tmp;
|
||||
size_t newSize = array->allocated + ARRAY_BLOCK;
|
||||
|
||||
tmp = array->entries;
|
||||
|
||||
array->entries = realloc(array->entries,
|
||||
sizeof(void *) * newSize);
|
||||
|
||||
if (!array->entries)
|
||||
{
|
||||
array->entries = tmp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
array->allocated = newSize;
|
||||
}
|
||||
|
||||
for (i = array->size; i > index; i--)
|
||||
{
|
||||
array->entries[i] = array->entries[i - 1];
|
||||
}
|
||||
|
||||
array->size++;
|
||||
|
||||
array->entries[index] = value;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t
|
||||
ArraySize(Array *array)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return array->size;
|
||||
}
|
||||
|
||||
int
|
||||
ArrayTrim(Array *array)
|
||||
{
|
||||
void **tmp;
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
tmp = array->entries;
|
||||
|
||||
array->entries = realloc(array->entries,
|
||||
sizeof(void *) * array->size);
|
||||
|
||||
if (!array->entries)
|
||||
{
|
||||
array->entries = tmp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
#include <Base64.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
static const char Base64EncodeMap[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
static const int Base64DecodeMap[] = {
|
||||
62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58,
|
||||
59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5,
|
||||
6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
||||
21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
|
||||
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
|
||||
43, 44, 45, 46, 47, 48, 49, 50, 51
|
||||
};
|
||||
|
||||
size_t
|
||||
Base64EncodedSize(size_t inputSize)
|
||||
{
|
||||
size_t size = inputSize;
|
||||
|
||||
if (inputSize % 3)
|
||||
{
|
||||
size += 3 - (inputSize % 3);
|
||||
}
|
||||
|
||||
size /= 3;
|
||||
size *= 4;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t
|
||||
Base64DecodedSize(const char *base64, size_t len)
|
||||
{
|
||||
size_t ret;
|
||||
size_t i;
|
||||
|
||||
if (!base64)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = len / 4 * 3;
|
||||
|
||||
for (i = len; i > 0; i--) {
|
||||
if (base64[i] == '=') {
|
||||
ret--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *
|
||||
Base64Encode(const char *input, size_t len)
|
||||
{
|
||||
char *out;
|
||||
size_t outLen;
|
||||
size_t i, j, v;
|
||||
|
||||
if (!input || !len)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
outLen = Base64EncodedSize(len);
|
||||
out = malloc(outLen + 1);
|
||||
if (!out)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
out[outLen] = '\0';
|
||||
|
||||
for (i = 0, j = 0; i < len; i += 3, j += 4)
|
||||
{
|
||||
v = input[i];
|
||||
v = i + 1 < len ? v << 8 | input[i + 1] : v << 8;
|
||||
v = i + 2 < len ? v << 8 | input[i + 2] : v << 8;
|
||||
|
||||
out[j] = Base64EncodeMap[(v >> 18) & 0x3F];
|
||||
out[j + 1] = Base64EncodeMap[(v >> 12) & 0x3F];
|
||||
|
||||
if (i + 1 < len)
|
||||
{
|
||||
out[j + 2] = Base64EncodeMap[(v >> 6) & 0x3F];
|
||||
} else {
|
||||
out[j + 2] = '=';
|
||||
}
|
||||
if (i + 2 < len) {
|
||||
out[j + 3] = Base64EncodeMap[v & 0x3F];
|
||||
} else {
|
||||
out[j + 3] = '=';
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static int
|
||||
Base64IsValidChar(char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
(c >= 'a' && c <= 'z') ||
|
||||
(c == '+') ||
|
||||
(c == '/') ||
|
||||
(c == '=');
|
||||
}
|
||||
|
||||
char *
|
||||
Base64Decode(const char *input, size_t len)
|
||||
{
|
||||
size_t i, j;
|
||||
int v;
|
||||
size_t outLen;
|
||||
char *out;
|
||||
|
||||
if (!input)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
outLen = Base64DecodedSize(input, len);
|
||||
if (len % 4)
|
||||
{
|
||||
/* Invalid length; must have incorrect padding */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Scan for invalid characters. */
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
if (!Base64IsValidChar(input[i]))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
out = malloc(outLen + 1);
|
||||
if (!out)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out[outLen] = '\0';
|
||||
|
||||
for (i = 0, j = 0; i < len; i += 4, j += 3) {
|
||||
v = Base64DecodeMap[input[i] - 43];
|
||||
v = (v << 6) | Base64DecodeMap[input[i + 1] - 43];
|
||||
v = input[i + 2] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 2] - 43];
|
||||
v = input[i + 3] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 3] - 43];
|
||||
|
||||
out[j] = (v >> 16) & 0xFF;
|
||||
if (input[i + 2] != '=')
|
||||
out[j + 1] = (v >> 8) & 0xFF;
|
||||
if (input[i + 3] != '=')
|
||||
out[j + 2] = v & 0xFF;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
extern void
|
||||
Base64Unpad(char *base64, size_t length)
|
||||
{
|
||||
if (!base64)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (base64[length - 1] == '=')
|
||||
{
|
||||
length--;
|
||||
}
|
||||
|
||||
base64[length] = '\0';
|
||||
}
|
||||
|
||||
extern int
|
||||
Base64Pad(char **base64Ptr, size_t length)
|
||||
{
|
||||
char *tmp;
|
||||
size_t newSize;
|
||||
size_t i;
|
||||
|
||||
if (length % 4 == 0)
|
||||
{
|
||||
return length; /* Success: no padding needed */
|
||||
}
|
||||
|
||||
newSize = length + (4 - (length % 4));
|
||||
|
||||
tmp = realloc(*base64Ptr, newSize + 100);;
|
||||
if (!tmp)
|
||||
{
|
||||
return 0; /* Memory error */
|
||||
}
|
||||
*base64Ptr = tmp;
|
||||
|
||||
for (i = length; i < newSize; i++)
|
||||
{
|
||||
(*base64Ptr)[i] = '=';
|
||||
}
|
||||
|
||||
(*base64Ptr)[newSize] = '\0';
|
||||
|
||||
return newSize;
|
||||
}
|
||||
|
|
@ -0,0 +1,452 @@
|
|||
#include <Config.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef CONFIG_BUFFER_BLOCK
|
||||
#define CONFIG_BUFFER_BLOCK 32
|
||||
#endif
|
||||
|
||||
struct ConfigDirective {
|
||||
Array *values;
|
||||
HashMap *children;
|
||||
};
|
||||
|
||||
struct ConfigParseResult {
|
||||
unsigned int ok : 1;
|
||||
union {
|
||||
size_t lineNumber;
|
||||
HashMap *confMap;
|
||||
} data;
|
||||
};
|
||||
|
||||
typedef enum ConfigToken {
|
||||
TOKEN_UNKNOWN,
|
||||
TOKEN_NAME,
|
||||
TOKEN_MACRO_ASSIGNMENT,
|
||||
TOKEN_VALUE,
|
||||
TOKEN_SEMICOLON,
|
||||
TOKEN_BLOCK_OPEN,
|
||||
TOKEN_BLOCK_CLOSE,
|
||||
TOKEN_MACRO,
|
||||
TOKEN_EOF
|
||||
} ConfigToken;
|
||||
|
||||
typedef struct ConfigParserState {
|
||||
FILE *stream;
|
||||
unsigned int line;
|
||||
|
||||
char *token;
|
||||
size_t tokenSize;
|
||||
size_t tokenLen;
|
||||
ConfigToken tokenType;
|
||||
|
||||
HashMap *macroMap;
|
||||
|
||||
} ConfigParserState;
|
||||
|
||||
unsigned int
|
||||
ConfigParseResultOk(ConfigParseResult *result)
|
||||
{
|
||||
return result ? result->ok : 0;
|
||||
}
|
||||
|
||||
size_t
|
||||
ConfigParseResultLineNumber(ConfigParseResult *result)
|
||||
{
|
||||
return result && !result->ok ? result->data.lineNumber : 0;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
ConfigParseResultGet(ConfigParseResult *result)
|
||||
{
|
||||
return result && result->ok ? result->data.confMap : NULL;
|
||||
}
|
||||
|
||||
void
|
||||
ConfigParseResultFree(ConfigParseResult *result)
|
||||
{
|
||||
/*
|
||||
* Note that if the parse was valid, the hash map
|
||||
* needs to be freed separately.
|
||||
*/
|
||||
free(result);
|
||||
}
|
||||
|
||||
Array *
|
||||
ConfigValuesGet(ConfigDirective *directive)
|
||||
{
|
||||
return directive ? directive->values : NULL;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
ConfigChildrenGet(ConfigDirective *directive)
|
||||
{
|
||||
return directive ? directive->children : NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Takes a void pointer because it is only used with
|
||||
* HashMapIterate(), which requires a pointer to a function
|
||||
* that takes a void pointer.
|
||||
*/
|
||||
static void
|
||||
ConfigDirectiveFree(void *ptr)
|
||||
{
|
||||
ConfigDirective *directive = ptr;
|
||||
size_t i;
|
||||
|
||||
if (!directive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < ArraySize(directive->values); i++)
|
||||
{
|
||||
free(ArrayGet(directive->values, i));
|
||||
}
|
||||
|
||||
ArrayFree(directive->values);
|
||||
ConfigFree(directive->children);
|
||||
|
||||
free(directive);
|
||||
}
|
||||
|
||||
void
|
||||
ConfigFree(HashMap *conf)
|
||||
{
|
||||
HashMapIterate(conf, ConfigDirectiveFree);
|
||||
HashMapFree(conf);
|
||||
}
|
||||
|
||||
static ConfigParserState *
|
||||
ConfigParserStateCreate(FILE * stream)
|
||||
{
|
||||
ConfigParserState *state = malloc(sizeof(ConfigParserState));
|
||||
if (!state)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
state->macroMap = HashMapCreate();
|
||||
|
||||
if (!state->macroMap)
|
||||
{
|
||||
free(state);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
state->stream = stream;
|
||||
state->line = 1;
|
||||
state->token = NULL;
|
||||
state->tokenSize = 0;
|
||||
state->tokenLen = 0;
|
||||
state->tokenType = TOKEN_UNKNOWN;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static void
|
||||
ConfigParserStateFree(ConfigParserState *state)
|
||||
{
|
||||
if (!state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
free(state->token);
|
||||
|
||||
HashMapIterate(state->macroMap, free);
|
||||
HashMapFree(state->macroMap);
|
||||
|
||||
free(state);
|
||||
}
|
||||
|
||||
static int
|
||||
ConfigIsNameChar(int c)
|
||||
{
|
||||
return isdigit(c) || isalpha(c) || (c == '-' || c == '_');
|
||||
}
|
||||
|
||||
static char
|
||||
ConfigConsumeWhitespace(ConfigParserState *state)
|
||||
{
|
||||
int c;
|
||||
while (isspace(c = fgetc(state->stream)))
|
||||
{
|
||||
if (c == '\n')
|
||||
{
|
||||
state->line++;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static void
|
||||
ConfigConsumeLine(ConfigParserState *state)
|
||||
{
|
||||
while (fgetc(state->stream) != '\n');
|
||||
state->line++;
|
||||
}
|
||||
|
||||
static void
|
||||
ConfigTokenSeek(ConfigParserState *state)
|
||||
{
|
||||
int c;
|
||||
|
||||
/* If we already hit EOF, don't do anything */
|
||||
if (state->tokenType == TOKEN_EOF) {
|
||||
return;
|
||||
}
|
||||
while ((c = ConfigConsumeWhitespace(state)) == '#') {
|
||||
ConfigConsumeLine(state);
|
||||
}
|
||||
|
||||
/*
|
||||
* After all whitespace and comments are consumed, identify the
|
||||
* token by looking at the next character
|
||||
*/
|
||||
|
||||
if (feof(state->stream)) {
|
||||
state->tokenType = TOKEN_EOF;
|
||||
return;
|
||||
}
|
||||
if (ConfigIsNameChar(c)) {
|
||||
state->tokenLen = 0;
|
||||
|
||||
/* Read the key/macro into state->token */
|
||||
if (!state->token) {
|
||||
state->tokenSize = CONFIG_BUFFER_BLOCK;
|
||||
state->token = malloc(CONFIG_BUFFER_BLOCK);
|
||||
}
|
||||
state->token[state->tokenLen] = c;
|
||||
state->tokenLen++;
|
||||
|
||||
while (ConfigIsNameChar((c = fgetc(state->stream)))) {
|
||||
state->token[state->tokenLen] = c;
|
||||
state->tokenLen++;
|
||||
|
||||
if (state->tokenLen >= state->tokenSize) {
|
||||
state->tokenSize += CONFIG_BUFFER_BLOCK;
|
||||
state->token = realloc(state->token,
|
||||
state->tokenSize);
|
||||
}
|
||||
}
|
||||
|
||||
state->token[state->tokenLen] = '\0';
|
||||
state->tokenLen++;
|
||||
|
||||
if (!isspace(c)) {
|
||||
state->tokenType = TOKEN_UNKNOWN;
|
||||
} else {
|
||||
state->tokenType = TOKEN_NAME;
|
||||
|
||||
if (c == '\n') {
|
||||
state->line++;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
switch (c) {
|
||||
case '=':
|
||||
state->tokenType = TOKEN_MACRO_ASSIGNMENT;
|
||||
break;
|
||||
case '"':
|
||||
state->tokenLen = 0;
|
||||
state->tokenType = TOKEN_VALUE;
|
||||
|
||||
/* read the value into state->curtok */
|
||||
while ((c = fgetc(state->stream)) != '"') {
|
||||
if (c == '\n') {
|
||||
state->line++;
|
||||
}
|
||||
/*
|
||||
* End of the stream reached without finding
|
||||
* a closing quote
|
||||
*/
|
||||
if (feof(state->stream)) {
|
||||
state->tokenType = TOKEN_EOF;
|
||||
break;
|
||||
}
|
||||
state->token[state->tokenLen] = c;
|
||||
state->tokenLen++;
|
||||
|
||||
if (state->tokenLen >= state->tokenSize) {
|
||||
state->tokenSize += CONFIG_BUFFER_BLOCK;
|
||||
state->token = realloc(state->token,
|
||||
state->tokenSize);
|
||||
}
|
||||
}
|
||||
state->token[state->tokenLen] = '\0';
|
||||
state->tokenLen++;
|
||||
break;
|
||||
case ';':
|
||||
state->tokenType = TOKEN_SEMICOLON;
|
||||
break;
|
||||
case '{':
|
||||
state->tokenType = TOKEN_BLOCK_OPEN;
|
||||
break;
|
||||
case '}':
|
||||
state->tokenType = TOKEN_BLOCK_CLOSE;
|
||||
break;
|
||||
case '$':
|
||||
state->tokenLen = 0;
|
||||
/* read the macro name into state->curtok */
|
||||
while (ConfigIsNameChar(c = fgetc(state->stream))) {
|
||||
state->token[state->tokenLen] = c;
|
||||
state->tokenLen++;
|
||||
|
||||
if (state->tokenLen >= state->tokenSize) {
|
||||
state->tokenSize += CONFIG_BUFFER_BLOCK;
|
||||
state->token = realloc(state->token,
|
||||
state->tokenSize);
|
||||
}
|
||||
}
|
||||
state->token[state->tokenLen] = '\0';
|
||||
state->tokenLen++;
|
||||
state->tokenType = TOKEN_MACRO;
|
||||
|
||||
ungetc(c, state->stream);
|
||||
break;
|
||||
default:
|
||||
state->tokenType = TOKEN_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Resize curtok to only use the bytes it needs */
|
||||
if (state->tokenLen) {
|
||||
state->tokenSize = state->tokenLen;
|
||||
state->token = realloc(state->token, state->tokenSize);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
ConfigExpect(ConfigParserState *state, ConfigToken tokenType)
|
||||
{
|
||||
return state->tokenType == tokenType;
|
||||
}
|
||||
|
||||
|
||||
static HashMap *
|
||||
ConfigParseBlock(ConfigParserState *state, int level)
|
||||
{
|
||||
HashMap *block = HashMapCreate();
|
||||
|
||||
ConfigTokenSeek(state);
|
||||
|
||||
while (ConfigExpect(state, TOKEN_NAME)) {
|
||||
char *name = malloc(state->tokenLen + 1);
|
||||
strcpy(name, state->token);
|
||||
|
||||
ConfigTokenSeek(state);
|
||||
if (ConfigExpect(state, TOKEN_VALUE) || ConfigExpect(state, TOKEN_MACRO)) {
|
||||
ConfigDirective *directive;
|
||||
|
||||
directive = malloc(sizeof(ConfigDirective));
|
||||
directive->children = NULL;
|
||||
directive->values = ArrayCreate();
|
||||
|
||||
while (ConfigExpect(state, TOKEN_VALUE) ||
|
||||
ConfigExpect(state, TOKEN_MACRO)) {
|
||||
|
||||
char *dval;
|
||||
char *dvalCpy;
|
||||
|
||||
if (ConfigExpect(state, TOKEN_VALUE)) {
|
||||
dval = state->token;
|
||||
} else if (ConfigExpect(state, TOKEN_MACRO)) {
|
||||
dval = HashMapGet(state->macroMap, state->token);
|
||||
if (!dval) {
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
dval = NULL; /* Should never happen */
|
||||
}
|
||||
|
||||
/* dval is a pointer which is overwritten with the next token. */
|
||||
dvalCpy = malloc(strlen(dval) + 1);
|
||||
strcpy(dvalCpy, dval);
|
||||
|
||||
ArrayAdd(directive->values, dvalCpy);
|
||||
ConfigTokenSeek(state);
|
||||
}
|
||||
|
||||
if (ConfigExpect(state, TOKEN_BLOCK_OPEN)) {
|
||||
/* token_seek(state); */
|
||||
directive->children = ConfigParseBlock(state, level + 1);
|
||||
if (!directive->children) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Append this directive to the current block,
|
||||
* overwriting a directive at this level with the same name.
|
||||
*
|
||||
* Note that if a value already exists with this name, it is
|
||||
* returned by HashMapSet() and then immediately passed to
|
||||
* ConfigDirectiveFree(). If the value does not exist, then
|
||||
* NULL is sent to ConfigDirectiveFree(), making it a no-op.
|
||||
*/
|
||||
ConfigDirectiveFree(HashMapSet(block, name, directive));
|
||||
|
||||
} else if (ConfigExpect(state, TOKEN_MACRO_ASSIGNMENT)) {
|
||||
ConfigTokenSeek(state);
|
||||
if (ConfigExpect(state, TOKEN_VALUE)) {
|
||||
char * valueCopy = malloc(strlen(state->token) + 1);
|
||||
strcpy(valueCopy, state->token);
|
||||
free(HashMapSet(state->macroMap, name, state->token));
|
||||
ConfigTokenSeek(state);
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!ConfigExpect(state, TOKEN_SEMICOLON)) {
|
||||
goto error;
|
||||
}
|
||||
ConfigTokenSeek(state);
|
||||
}
|
||||
|
||||
if (ConfigExpect(state, level ? TOKEN_BLOCK_CLOSE : TOKEN_EOF)) {
|
||||
ConfigTokenSeek(state);
|
||||
return block;
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
|
||||
error:
|
||||
/* Only free the very top level, because this will recurse */
|
||||
if (!level) {
|
||||
ConfigFree(block);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ConfigParseResult *
|
||||
ConfigParse(FILE * stream)
|
||||
{
|
||||
ConfigParseResult *result;
|
||||
HashMap *conf;
|
||||
ConfigParserState *state;
|
||||
|
||||
result = malloc(sizeof(ConfigParseResult));
|
||||
state = ConfigParserStateCreate(stream);
|
||||
conf = ConfigParseBlock(state, 0);
|
||||
|
||||
if (!conf) {
|
||||
result->ok = 0;
|
||||
result->data.lineNumber = state->line;
|
||||
} else {
|
||||
result->ok = 1;
|
||||
result->data.confMap = conf;
|
||||
}
|
||||
|
||||
ConfigParserStateFree(state);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -0,0 +1,300 @@
|
|||
#include <HashMap.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct HashMapBucket {
|
||||
uint32_t hash;
|
||||
void *value;
|
||||
} HashMapBucket;
|
||||
|
||||
struct HashMap {
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
HashMapBucket **entries;
|
||||
|
||||
float maxLoad;
|
||||
};
|
||||
|
||||
static uint32_t
|
||||
HashMapHashKey(const char *key)
|
||||
{
|
||||
uint32_t hash = 2166136261u;
|
||||
size_t i = 0;
|
||||
|
||||
while (key[i])
|
||||
{
|
||||
hash ^= (uint8_t) key[i];
|
||||
hash *= 16777619;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
HashMapGrow(HashMap *map)
|
||||
{
|
||||
size_t oldCapacity;
|
||||
size_t i;
|
||||
HashMapBucket **newEntries;
|
||||
|
||||
if (!map)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
oldCapacity = map->capacity;
|
||||
map->capacity *= 2;
|
||||
|
||||
newEntries = calloc(map->capacity, sizeof(HashMapBucket *));
|
||||
if (!newEntries)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < oldCapacity; i++)
|
||||
{
|
||||
/* If there is a value here, and it isn't a tombstone */
|
||||
if (map->entries[i] && map->entries[i]->hash)
|
||||
{
|
||||
/* Copy it to the new entries array */
|
||||
size_t index = map->entries[i]->hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (newEntries[index])
|
||||
{
|
||||
if (!newEntries[index]->hash)
|
||||
{
|
||||
free(newEntries[index]);
|
||||
newEntries[index] = map->entries[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newEntries[index] = map->entries[i];
|
||||
break;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Either NULL or a tombstone */
|
||||
free(map->entries[i]);
|
||||
}
|
||||
}
|
||||
|
||||
free(map->entries);
|
||||
map->entries = newEntries;
|
||||
return 1;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HashMapCreate(void)
|
||||
{
|
||||
HashMap *map = malloc(sizeof(HashMap));
|
||||
if (!map)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
map->maxLoad = 0.75;
|
||||
map->count = 0;
|
||||
map->capacity = 16;
|
||||
|
||||
map->entries = calloc(map->capacity, sizeof(HashMapBucket *));
|
||||
if (!map->entries)
|
||||
{
|
||||
free(map);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
void *
|
||||
HashMapDelete(HashMap *map, const char *key)
|
||||
{
|
||||
uint32_t hash;
|
||||
size_t index;
|
||||
|
||||
if (!map || !key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hash = HashMapHashKey(key);
|
||||
index = hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[index];
|
||||
|
||||
if (!bucket)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (bucket->hash == hash)
|
||||
{
|
||||
bucket->hash = 0;
|
||||
return bucket->value;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
HashMapFree(HashMap *map)
|
||||
{
|
||||
if (map)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < map->capacity; i++)
|
||||
{
|
||||
if (map->entries[i])
|
||||
{
|
||||
free(map->entries[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(map);
|
||||
}
|
||||
|
||||
void *
|
||||
HashMapGet(HashMap *map, const char *key)
|
||||
{
|
||||
uint32_t hash;
|
||||
size_t index;
|
||||
|
||||
if (!map || !key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hash = HashMapHashKey(key);
|
||||
index = hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[index];
|
||||
|
||||
if (!bucket)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (bucket->hash == hash)
|
||||
{
|
||||
return bucket->value;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
HashMapIterate(HashMap *map, void (*iteratorFunc)(void *))
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!map)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < map->capacity; i++)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[i];
|
||||
|
||||
if (bucket)
|
||||
{
|
||||
iteratorFunc(bucket->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HashMapMaxLoadSet(HashMap *map, float load)
|
||||
{
|
||||
if (!map)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
map->maxLoad = load;
|
||||
}
|
||||
|
||||
|
||||
void *
|
||||
HashMapSet(HashMap *map, const char *key, void *value)
|
||||
{
|
||||
uint32_t hash;
|
||||
size_t index;
|
||||
|
||||
if (!map || !key || !value)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (map->count + 1 > map->capacity * map->maxLoad)
|
||||
{
|
||||
HashMapGrow(map);
|
||||
}
|
||||
|
||||
hash = HashMapHashKey(key);
|
||||
index = hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[index];
|
||||
|
||||
if (!bucket)
|
||||
{
|
||||
bucket = malloc(sizeof(HashMapBucket));
|
||||
if (!bucket)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bucket->hash = hash;
|
||||
bucket->value = value;
|
||||
map->entries[index] = bucket;
|
||||
map->count++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!bucket->hash)
|
||||
{
|
||||
bucket->hash = hash;
|
||||
bucket->value = value;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bucket->hash == hash)
|
||||
{
|
||||
void *oldValue = bucket->value;
|
||||
bucket->value = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -0,0 +1,310 @@
|
|||
#include <Log.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#define LOG_TSBUFFER 64
|
||||
|
||||
struct LogConfig {
|
||||
LogLevel level;
|
||||
size_t indent;
|
||||
FILE *out;
|
||||
int flags;
|
||||
char *tsFmt;
|
||||
};
|
||||
|
||||
void
|
||||
Log(LogConfig *config, LogLevel level, const char *msg, ...)
|
||||
{
|
||||
int i;
|
||||
int doColor;
|
||||
char indicator;
|
||||
va_list argp;
|
||||
|
||||
/*
|
||||
* Only proceed if we have a config and its log level is set to a
|
||||
* value that permits us to log. This is as close as we can get
|
||||
* to a no-op function if we aren't logging anything, without doing
|
||||
* some crazy macro magic.
|
||||
*/
|
||||
if (!config || level > config->level)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Misconfiguration */
|
||||
if (!config->out)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < config->indent; i++)
|
||||
{
|
||||
fputc(' ', config->out);
|
||||
}
|
||||
|
||||
doColor = LogConfigFlagGet(config, LOG_FLAG_COLOR)
|
||||
&& isatty(fileno(config->out));
|
||||
|
||||
if (doColor)
|
||||
{
|
||||
char *ansi;
|
||||
switch (level)
|
||||
{
|
||||
case LOG_ERROR:
|
||||
/* Bold Red */
|
||||
ansi = "\033[1;31m";
|
||||
break;
|
||||
case LOG_WARNING:
|
||||
/* Bold Yellow */
|
||||
ansi = "\033[1;33m";
|
||||
break;
|
||||
case LOG_TASK:
|
||||
/* Bold Magenta */
|
||||
ansi = "\033[1;35m";
|
||||
break;
|
||||
case LOG_MESSAGE:
|
||||
/* Bold Green */
|
||||
ansi = "\033[1;32m";
|
||||
break;
|
||||
case LOG_DEBUG:
|
||||
/* Bold Blue */
|
||||
ansi = "\033[1;34m";
|
||||
break;
|
||||
default:
|
||||
ansi = "";
|
||||
break;
|
||||
}
|
||||
|
||||
fputs(ansi, config->out);
|
||||
}
|
||||
|
||||
fputc('[', config->out);
|
||||
|
||||
if (config->tsFmt)
|
||||
{
|
||||
time_t timer = time(NULL);
|
||||
struct tm *timeInfo = localtime(&timer);
|
||||
char tsBuffer[LOG_TSBUFFER];
|
||||
|
||||
int tsLength = strftime(tsBuffer, LOG_TSBUFFER, config->tsFmt,
|
||||
timeInfo);
|
||||
|
||||
if (tsLength)
|
||||
{
|
||||
fputs(tsBuffer, config->out);
|
||||
if (!isspace(tsBuffer[tsLength - 1]))
|
||||
{
|
||||
fputc(' ', config->out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case LOG_ERROR:
|
||||
indicator = 'x';
|
||||
break;
|
||||
case LOG_WARNING:
|
||||
indicator = '!';
|
||||
break;
|
||||
case LOG_TASK:
|
||||
indicator = '~';
|
||||
break;
|
||||
case LOG_MESSAGE:
|
||||
indicator = '>';
|
||||
break;
|
||||
case LOG_DEBUG:
|
||||
indicator = '*';
|
||||
break;
|
||||
default:
|
||||
indicator = ' ';
|
||||
break;
|
||||
}
|
||||
|
||||
fprintf(config->out, "%c]", indicator);
|
||||
|
||||
if (doColor)
|
||||
{
|
||||
/* ANSI Reset */
|
||||
fputs("\033[0m", config->out);
|
||||
}
|
||||
|
||||
fputc(' ', config->out);
|
||||
|
||||
va_start(argp, msg);
|
||||
vfprintf(config->out, msg, argp);
|
||||
fputc('\n', config->out);
|
||||
va_end(argp);
|
||||
|
||||
/* If we are debugging, there might be something that's
|
||||
* going to segfault the program coming up, so flush the
|
||||
* output stream immediately.
|
||||
*/
|
||||
if (config->level == LOG_DEBUG)
|
||||
{
|
||||
fflush(config->out);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <Log.h>
|
||||
#include <HashMap.h>
|
||||
#include <Config.h>
|
||||
#include <Base64.h>
|
||||
|
||||
typedef enum ArgFlag
|
||||
{
|
||||
ARG_VERSION = (1 << 0),
|
||||
ARG_USAGE = (1 << 1)
|
||||
} ArgFlag;
|
||||
|
||||
static void
|
||||
TelodendriaPrintHeader(LogConfig *lc)
|
||||
{
|
||||
Log(lc, LOG_MESSAGE,
|
||||
" _____ _ _ _ _");
|
||||
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, "");
|
||||
Log(lc, LOG_MESSAGE,
|
||||
"Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>");
|
||||
Log(lc, LOG_MESSAGE,
|
||||
"Documentation/Support: https://bancino.net/pub/Telodendria");
|
||||
Log(lc, LOG_MESSAGE, "");
|
||||
}
|
||||
|
||||
static void
|
||||
TelodendriaPrintUsage(LogConfig *lc)
|
||||
{
|
||||
Log(lc, LOG_MESSAGE, "Usage:");
|
||||
Log(lc, LOG_MESSAGE, " -c <file> Configuration file ('-' for stdin).");
|
||||
Log(lc, LOG_MESSAGE, " -V Print the header, then exit.");
|
||||
Log(lc, LOG_MESSAGE, " -h Print this usage, then exit.");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
LogConfig *lc;
|
||||
int exit = EXIT_SUCCESS;
|
||||
|
||||
/* Arg parsing */
|
||||
int opt;
|
||||
int flags = 0;
|
||||
char *configArg = NULL;
|
||||
|
||||
/* Config file */
|
||||
FILE *configFile = NULL;
|
||||
ConfigParseResult *configParseResult = NULL;
|
||||
HashMap *config = NULL;
|
||||
|
||||
lc = LogConfigCreate();
|
||||
|
||||
/* TODO: Remove */
|
||||
LogConfigLevelSet(lc, LOG_DEBUG);
|
||||
|
||||
if (!lc)
|
||||
{
|
||||
printf("Fatal error: unable to allocate memory for logger.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
TelodendriaPrintHeader(lc);
|
||||
|
||||
while ((opt = getopt(argc, argv, "c:Vh")) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 'c':
|
||||
configArg = optarg;
|
||||
break;
|
||||
case 'V':
|
||||
flags |= ARG_VERSION;
|
||||
break;
|
||||
case 'h':
|
||||
flags |= ARG_USAGE;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & ARG_VERSION)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (flags & ARG_USAGE)
|
||||
{
|
||||
TelodendriaPrintUsage(lc);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!configArg)
|
||||
{
|
||||
Log(lc, LOG_ERROR, "No configuration file specified.");
|
||||
TelodendriaPrintUsage(lc);
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (strcmp(configArg, "-") == 0)
|
||||
{
|
||||
configFile = stdout;
|
||||
}
|
||||
else
|
||||
{
|
||||
configFile = fopen(configArg, "r");
|
||||
if (!configFile)
|
||||
{
|
||||
Log(lc, LOG_ERROR, "Unable to open configuration file '%s' for reading.", configArg);
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
||||
Log(lc, LOG_MESSAGE, "Using configuration file '%s'.", configArg);
|
||||
|
||||
/* Read config here */
|
||||
configParseResult = ConfigParse(configFile);
|
||||
|
||||
if (!ConfigParseResultOk(configParseResult))
|
||||
{
|
||||
Log(lc, LOG_ERROR, "Syntax error on line %d.",
|
||||
ConfigParseResultLineNumber(configParseResult));
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
config = ConfigParseResultGet(configParseResult);
|
||||
ConfigParseResultFree(configParseResult);
|
||||
|
||||
Log(lc, LOG_DEBUG, "Closing configuration file.");
|
||||
fclose(configFile);
|
||||
|
||||
/* Configure log file */
|
||||
|
||||
finish:
|
||||
if (config)
|
||||
{
|
||||
Log(lc, LOG_DEBUG, "Freeing configuration structure.");
|
||||
ConfigFree(config);
|
||||
}
|
||||
Log(lc, LOG_DEBUG, "Freeing log configuration and exiting with code '%d'.", exit);
|
||||
LogConfigFree(lc);
|
||||
return exit;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef TELODENDRIA_ARRAY_H
|
||||
#define TELODENDRIA_ARRAY_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct Array Array;
|
||||
|
||||
extern Array *
|
||||
ArrayCreate(void);
|
||||
|
||||
extern size_t
|
||||
ArraySize(Array *array);
|
||||
|
||||
extern void *
|
||||
ArrayGet(Array *array, size_t index);
|
||||
|
||||
extern int
|
||||
ArrayInsert(Array *, void *value, size_t index);
|
||||
|
||||
extern int
|
||||
ArrayAdd(Array *array, void *value);
|
||||
|
||||
extern void *
|
||||
ArrayDelete(Array *array, size_t index);
|
||||
|
||||
extern void
|
||||
ArrayFree(Array *array);
|
||||
|
||||
extern int
|
||||
ArrayTrim(Array *array);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef TELODENDRIA_BASE64_H
|
||||
#define TELODENDRIA_BASE64_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
extern size_t
|
||||
Base64EncodedSize(size_t inputSize);
|
||||
|
||||
extern size_t
|
||||
Base64DecodedSize(const char *base64, size_t len);
|
||||
|
||||
extern char *
|
||||
Base64Encode(const char *input, size_t len);
|
||||
|
||||
extern char *
|
||||
Base64Decode(const char *input, size_t len);
|
||||
|
||||
extern void
|
||||
Base64Unpad(char *base64, size_t length);
|
||||
|
||||
extern int
|
||||
Base64Pad(char **base64Ptr, size_t length);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef TELODENDRIA_CONFIG_H
|
||||
#define TELODENDRIA_CONFIG_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <HashMap.h>
|
||||
#include <Array.h>
|
||||
|
||||
typedef struct ConfigDirective ConfigDirective;
|
||||
|
||||
typedef struct ConfigParseResult ConfigParseResult;
|
||||
|
||||
extern ConfigParseResult *
|
||||
ConfigParse(FILE *stream);
|
||||
|
||||
extern unsigned int
|
||||
ConfigParseResultOk(ConfigParseResult *result);
|
||||
|
||||
extern size_t
|
||||
ConfigParseResultLineNumber(ConfigParseResult *result);
|
||||
|
||||
extern HashMap *
|
||||
ConfigParseResultGet(ConfigParseResult *result);
|
||||
|
||||
extern void
|
||||
ConfigParseResultFree(ConfigParseResult *result);
|
||||
|
||||
extern Array *
|
||||
ConfigValuesGet(ConfigDirective *directive);
|
||||
|
||||
extern HashMap *
|
||||
ConfigChildrenGet(ConfigDirective *directive);
|
||||
|
||||
extern void
|
||||
ConfigFree(HashMap *conf);
|
||||
|
||||
#endif /* TELODENDRIA_CONFIG_H */
|
|
@ -0,0 +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>
|
||||
*/
|
||||
#ifndef TELODENDRIA_HASHMAP_H
|
||||
#define TELODENDRIA_HASHMAP_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/*
|
||||
* HashMap: The primary data structure used by this hash map algorithm.
|
||||
* All data necessary for the algorithm to function is stored inside
|
||||
* it. This is an opaque structure; use the methods defined by this
|
||||
* interface to manipulate it.
|
||||
*/
|
||||
typedef struct HashMap HashMap;
|
||||
|
||||
/*
|
||||
* HashMapCreate: Create a new HashMap object.
|
||||
*
|
||||
* Returns: A HashMap object that is ready to be used by the rest of
|
||||
* the HashMap functions, or NULL if memory could not be allocated on
|
||||
* the heap.
|
||||
*/
|
||||
extern HashMap *
|
||||
HashMapCreate(void);
|
||||
|
||||
extern void
|
||||
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
|
||||
HashMapIterate(HashMap *map, void (*iteratorFunc)(void *));
|
||||
|
||||
/*
|
||||
* HashMapFree: Free the hash map, returning its memory to the operating
|
||||
* system. Note that this function does not free the values stored in
|
||||
* the map since this hash map implementation has no way of knowing
|
||||
* what actually is stored in it. You should use HashMapIterate to
|
||||
* free the values using your own algorithm.
|
||||
*/
|
||||
extern void
|
||||
HashMapFree(HashMap *map);
|
||||
|
||||
#endif /* TELODENDRIA_HASHMAP_H */
|
|
@ -0,0 +1,96 @@
|
|||
#ifndef TELODENDRIA_HTTP_H
|
||||
#define TELODENDRIA_HTTP_H
|
||||
|
||||
typedef enum HttpRequestMethod {
|
||||
HTTP_GET,
|
||||
HTTP_HEAD,
|
||||
HTTP_POST,
|
||||
HTTP_PUT,
|
||||
HTTP_DELETE,
|
||||
HTTP_CONNECT,
|
||||
HTTP_OPTIONS,
|
||||
HTTP_TRACE,
|
||||
HTTP_PATCH
|
||||
} HttpRequestMethod;
|
||||
|
||||
typedef enum HttpStatus {
|
||||
/* Informational responses */
|
||||
HTTP_CONTINUE = 100,
|
||||
HTTP_SWITCHING_PROTOCOLS = 101,
|
||||
HTTP_EARLY_HINTS = 103,
|
||||
|
||||
/* Successful responses */
|
||||
HTTP_OK = 200,
|
||||
HTTP_CREATED = 201,
|
||||
HTTP_ACCEPTED = 202,
|
||||
HTTP_NON_AUTHORITATIVE_INFORMATION = 203,
|
||||
HTTP_NO_CONTENT = 204,
|
||||
HTTP_RESET_CONTENT = 205,
|
||||
HTTP_PARTIAL_CONTENT = 206,
|
||||
|
||||
/* Redirection messages */
|
||||
HTTP_MULTIPLE_CHOICES = 300,
|
||||
HTTP_MOVED_PERMANENTLY = 301,
|
||||
HTTP_FOUND = 302,
|
||||
HTTP_SEE_OTHER = 303,
|
||||
HTTP_NOT_MODIFIED = 304,
|
||||
HTTP_TEMPORARY_REDIRECT = 307,
|
||||
HTTP_PERMANENT_REDIRECT = 308,
|
||||
|
||||
/* Client error messages */
|
||||
HTTP_BAD_REQUEST = 400,
|
||||
HTTP_UNAUTHORIZED = 401,
|
||||
HTTP_FORBIDDEN = 403,
|
||||
HTTP_NOT_FOUND = 404,
|
||||
HTTP_METHOD_NOT_ALLOWED = 405,
|
||||
HTTP_NOT_ACCEPTABLE = 406,
|
||||
HTTP_PROXY_AUTH_REQUIRED = 407,
|
||||
HTTP_REQUEST_TIMEOUT = 408,
|
||||
HTTP_CONFLICT = 409,
|
||||
HTTP_GONE = 410,
|
||||
HTTP_LENGTH_REQUIRED = 411,
|
||||
HTTP_PRECONDITION_FAILED = 412,
|
||||
HTTP_PAYLOAD_TOO_LARGE = 413,
|
||||
HTTP_URI_TOO_LONG = 414,
|
||||
HTTP_UNSUPPORTED_MEDIA_TYPE = 415,
|
||||
HTTP_RANGE_NOT_SATISFIABLE = 416,
|
||||
HTTP_EXPECTATION_FAILED = 417,
|
||||
HTTP_TEAPOT = 418,
|
||||
HTTP_UPGRADE_REQUIRED = 426,
|
||||
HTTP_PRECONDITION_REQUIRED = 428,
|
||||
HTTP_TOO_MANY_REQUESTS = 429,
|
||||
HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
|
||||
HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451,
|
||||
|
||||
/* Server error responses */
|
||||
HTTP_INTERNAL_SERVER_ERROR = 500,
|
||||
HTTP_NOT_IMPLEMENTED = 501,
|
||||
HTTP_BAD_GATEWAY = 502,
|
||||
HTTP_SERVICE_UNAVAILABLE = 503,
|
||||
HTTP_GATEWAY_TIMEOUT = 504,
|
||||
HTTP_VERSION_NOT_SUPPORTED = 505,
|
||||
HTTP_VARIANT_ALSO_NEGOTIATES = 506,
|
||||
HTTP_NOT_EXTENDED = 510,
|
||||
HTTP_NETWORK_AUTH_REQUIRED = 511
|
||||
} HttpStatus;
|
||||
|
||||
struct HttpRequest {
|
||||
HttpRequestMethod method;
|
||||
};
|
||||
|
||||
struct HttpResponse {
|
||||
HttpStatus status;
|
||||
};
|
||||
|
||||
extern char *
|
||||
HttpGetStatusString(const HttpStatus httpStatus);
|
||||
|
||||
extern HttpRequestMethod
|
||||
HttpRequestMethodFromString(const char *requestMethod);
|
||||
|
||||
typedef struct HttpRequest HttpRequest;
|
||||
typedef struct HttpResponse HttpResponse;
|
||||
|
||||
typedef void (*HttpHandler)(HttpRequest *, HttpResponse *);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef TELODENDRIA_JSON_H
|
||||
#define TELODENDRIA_JSON_H
|
||||
|
||||
#include <HashMap.h>
|
||||
#include <Array.h>
|
||||
|
||||
typedef enum JsonType {
|
||||
JSON_OBJECT,
|
||||
JSON_ARRAY,
|
||||
JSON_STRING,
|
||||
JSON_INTEGER,
|
||||
JSON_FLOAT,
|
||||
JSON_BOOLEAN,
|
||||
JSON_NULL
|
||||
} JsonType;
|
||||
|
||||
typedef struct JsonValue {
|
||||
JsonType type;
|
||||
union as {
|
||||
HashMap *object;
|
||||
Array *array;
|
||||
char *string;
|
||||
int64_t integer;
|
||||
double floating;
|
||||
int boolean : 1;
|
||||
};
|
||||
} JsonValue;
|
||||
|
||||
|
||||
extern JsonType
|
||||
JsonValueType(JsonValue *value);
|
||||
|
||||
extern JsonValue *
|
||||
JsonValueObject(HashMap *object);
|
||||
|
||||
extern HashMap *
|
||||
JsonValueAsObject(JsonValue *value);
|
||||
|
||||
extern JsonValue *
|
||||
JsonValueArray(Array *array);
|
||||
|
||||
extern Array *
|
||||
JsonValueAsArray(JsonValue *value);
|
||||
|
||||
extern JsonValue *
|
||||
JsonValueString(char *string);
|
||||
|
||||
extern JsonValue *
|
||||
JsonValueInteger(int64_t integer);
|
||||
|
||||
extern JsonValue *
|
||||
JsonValueFloat(double floating);
|
||||
|
||||
extern JsonValue *
|
||||
JsonValueBoolean(int boolean);
|
||||
|
||||
extern JsonValue *
|
||||
JsonValueNull(void);
|
||||
|
||||
extern void *
|
||||
JsonValueFree(JsonValue *value);
|
||||
|
||||
extern char *
|
||||
JsonEncode(HashMap *object);
|
||||
|
||||
extern HashMap *
|
||||
JsonDecode(char *string);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,63 @@
|
|||
#ifndef TELODENDRIA_LOG_H
|
||||
#define TELODENDRIA_LOG_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef enum LogLevel {
|
||||
LOG_ERROR,
|
||||
LOG_WARNING,
|
||||
LOG_TASK,
|
||||
LOG_MESSAGE,
|
||||
LOG_DEBUG
|
||||
} LogLevel;
|
||||
|
||||
typedef enum LogFlag {
|
||||
LOG_FLAG_COLOR = (1 << 0)
|
||||
} LogFlag;
|
||||
|
||||
typedef struct LogConfig LogConfig;
|
||||
|
||||
extern LogConfig *
|
||||
LogConfigCreate(void);
|
||||
|
||||
extern void
|
||||
LogConfigFree(LogConfig *config);
|
||||
|
||||
extern void
|
||||
LogConfigLevelSet(LogConfig *config, LogLevel level);
|
||||
|
||||
extern LogLevel
|
||||
LogConfigLevelGet(LogConfig *config);
|
||||
|
||||
extern void
|
||||
LogConfigIndentSet(LogConfig *config, size_t indent);
|
||||
|
||||
extern size_t
|
||||
LogConfigIndentGet(LogConfig *config);
|
||||
|
||||
extern void
|
||||
LogConfigIndent(LogConfig *config);
|
||||
|
||||
extern void
|
||||
LogConfigUnindent(LogConfig *config);
|
||||
|
||||
extern void
|
||||
LogConfigOutputSet(LogConfig *config, FILE *out);
|
||||
|
||||
extern void
|
||||
LogConfigFlagSet(LogConfig *config, int flags);
|
||||
|
||||
extern void
|
||||
LogConfigFlagClear(LogConfig *config, int flags);
|
||||
|
||||
extern int
|
||||
LogConfigFlagGet(LogConfig *config, int flags);
|
||||
|
||||
extern void
|
||||
LogConfigTimeStampFormatSet(LogConfig *config, char *tsFmt);
|
||||
|
||||
extern void
|
||||
Log(LogConfig *config, LogLevel level, const char *msg, ...);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue