Compare commits
48 Commits
64ae8365b6
...
master
Author | SHA1 | Date | |
---|---|---|---|
e88dc0426a | |||
5ee7e02f7a | |||
e1f0d04409 | |||
48f4a73b0a | |||
2e70abd0de | |||
b3a5860beb | |||
486952dcbf | |||
2988a5b3d6 | |||
2badc915d6 | |||
447362c74d | |||
7a9d5aa371 | |||
d9071a4947 | |||
632d731118 | |||
bbd23cc8ba | |||
037b0a123a | |||
ab37785d2a | |||
4254c66aa3 | |||
06574d86c9 | |||
7d911d73fa | |||
cb6ec2caa0 | |||
2f5465a0a0 | |||
e230bb01c0 | |||
68dcc80d1c | |||
5ee77b4628 | |||
a7f8ebea09 | |||
e04e6c135e | |||
90d5fa37c6 | |||
27581183fd | |||
0fab7bcffe | |||
d32dd721e1 | |||
7a653f082d | |||
804fec1c7d | |||
edc039db6e | |||
540b3a1567 | |||
53b097bf92 | |||
7659d9ae54 | |||
45b69de132 | |||
4d2c12d2b1 | |||
3c1c0e412e | |||
5c125f7ca8 | |||
6cfeae7a0d | |||
4973f0c622 | |||
bcd1255965 | |||
ed1d61d976 | |||
e9a95c2c54 | |||
a90517c4da | |||
5db22711be | |||
9bb248ee03 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,3 +3,6 @@ aboba
|
||||
build
|
||||
gpp1
|
||||
watcher
|
||||
commit.h
|
||||
compile_flags.txt
|
||||
bundle.zip
|
||||
|
5
.gitmodules
vendored
5
.gitmodules
vendored
@ -3,7 +3,7 @@
|
||||
url = https://github.com/cesanta/mongoose.git
|
||||
[submodule "gebs"]
|
||||
path = gebs
|
||||
url = https://gitlab.com/kamkow1/gebs.git
|
||||
url = http://git.kamkow1lair.pl/kamkow1/gebs.git
|
||||
[submodule "incbin"]
|
||||
path = incbin
|
||||
url = https://github.com/graphitemaster/incbin.git
|
||||
@ -16,3 +16,6 @@
|
||||
[submodule "md5-c"]
|
||||
path = md5-c
|
||||
url = https://github.com/Zunawe/md5-c.git
|
||||
[submodule "zip"]
|
||||
path = zip
|
||||
url = https://github.com/kuba--/zip.git
|
||||
|
@ -3,3 +3,6 @@
|
||||
./gpp1
|
||||
./watcher
|
||||
./mongoose.o
|
||||
./bundle.zip
|
||||
./compile_flags.txt
|
||||
./commit.h
|
||||
|
12
CONFIG.h
Normal file
12
CONFIG.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef CONFIG_H_
|
||||
#define CONFIG_H_
|
||||
|
||||
#if MY_DEBUG
|
||||
# define CONFIG_LISTEN_URL "http://localhost:8080"
|
||||
#else
|
||||
# define CONFIG_LISTEN_URL "http://localhost:5000"
|
||||
#endif
|
||||
|
||||
#define BUNDLE_ZIP_COMPRESSION 10
|
||||
|
||||
#endif // CONFIG_H_
|
68
baked.c
68
baked.c
@ -3,19 +3,15 @@
|
||||
#include "gebs/gebs.h"
|
||||
#include "incbin/incbin.h"
|
||||
#include "stb/stb_ds.h"
|
||||
#include "zip.h"
|
||||
|
||||
#include "baked.h"
|
||||
#include "locked.h"
|
||||
#include "CONFIG.h"
|
||||
|
||||
INCBIN(gpp1, "./gpp1");
|
||||
INCBIN(bundle_zip, "./bundle.zip");
|
||||
|
||||
INCBIN(home_t, "./tmpls/home.t");
|
||||
INCBIN(page_missing_t, "./tmpls/page-missing.t");
|
||||
|
||||
INCBIN(simple_min_css, "./etc/simple.min.css");
|
||||
INCBIN(favicon_ico, "./etc/favicon.ico");
|
||||
INCBIN(hotreload_js, "./etc/hotreload.js");
|
||||
|
||||
Baked_Resource *baked_resources = NULL;
|
||||
static locked(Baked_Resource *) baked_resources = locked_init(nil);
|
||||
|
||||
void add_baked_resource(char *key, const uchar *data, size_t size)
|
||||
{
|
||||
@ -25,34 +21,62 @@ void add_baked_resource(char *key, const uchar *data, size_t size)
|
||||
abort();
|
||||
}
|
||||
write(fd, data, size);
|
||||
shput(baked_resources, key, fd);
|
||||
shput(baked_resources.value, key, ((Baked_Resource_Value){ .memfd = fd, .bufptr = (void *)data }));
|
||||
LOGI("Added baked resource: %s\n", key);
|
||||
}
|
||||
|
||||
void init_baked_resources(void)
|
||||
{
|
||||
add_baked_resource("home.t", home_t_data, home_t_size);
|
||||
add_baked_resource("page-missing.t", page_missing_t_data, page_missing_t_size);
|
||||
add_baked_resource("gpp1", gpp1_data, gpp1_size);
|
||||
add_baked_resource("simple.min.css", simple_min_css_data, simple_min_css_size);
|
||||
add_baked_resource("favicon.ico", favicon_ico_data, favicon_ico_size);
|
||||
add_baked_resource("hotreload.js", hotreload_js_data, hotreload_js_size);
|
||||
LOGI("Initializing baked resources...\n");
|
||||
struct zip_t *zip = zip_stream_open(bundle_zip_data, bundle_zip_size, BUNDLE_ZIP_COMPRESSION, 'r');
|
||||
size_t n = zip_entries_total(zip);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
zip_entry_openbyindex(zip, i);
|
||||
|
||||
const char *name = strdup(zip_entry_name(zip));
|
||||
size_t size = zip_entry_size(zip);
|
||||
char *buf = malloc(size);
|
||||
zip_entry_noallocread(zip, buf, size);
|
||||
|
||||
add_baked_resource((char *)name, buf, size);
|
||||
|
||||
zip_entry_close(zip);
|
||||
}
|
||||
zip_stream_close(zip);
|
||||
LOGI("baked resources done\n");
|
||||
}
|
||||
|
||||
void free_baked_resources(void)
|
||||
{
|
||||
for (size_t i = 0; i < shlen(baked_resources); i++) {
|
||||
close(baked_resources[i].value);
|
||||
LOGI("freeing baked resouces\n");
|
||||
for (size_t i = 0; i < shlen(baked_resources.value); i++) {
|
||||
close(baked_resources.value[i].value.memfd);
|
||||
free(baked_resources.value[i].key);
|
||||
free(baked_resources.value[i].value.bufptr);
|
||||
}
|
||||
shfree(baked_resources);
|
||||
shfree(baked_resources.value);
|
||||
LOGI("baked resources done\n");
|
||||
}
|
||||
|
||||
bool get_baked_resource_path(char *key, char *buf, size_t size)
|
||||
{
|
||||
if (shgeti(baked_resources, key) != -1) {
|
||||
int fd = shget(baked_resources, key);
|
||||
snprintf(buf, size, "/proc/%d/fd/%d", getpid(), fd);
|
||||
LOGI("Request for baked resource path: %s\n", key);
|
||||
lockx(&baked_resources);
|
||||
if (shgeti(baked_resources.value, key) != -1) {
|
||||
Baked_Resource_Value brv = shget(baked_resources.value, key);
|
||||
snprintf(buf, size, "/proc/%d/fd/%d", getpid(), brv.memfd);
|
||||
LOGI("Memfd path %s\n", buf);
|
||||
unlockx(&baked_resources);
|
||||
return true;
|
||||
}
|
||||
unlockx(&baked_resources);
|
||||
return false;
|
||||
}
|
||||
|
||||
void baked_resource_each(void (*f)(Baked_Resource *resource, void *udata), void *udata)
|
||||
{
|
||||
for (size_t i = 0; i < shlen(baked_resources.value); i++) {
|
||||
f(&baked_resources.value[i], udata);
|
||||
}
|
||||
}
|
||||
|
||||
|
17
baked.h
17
baked.h
@ -1,24 +1,19 @@
|
||||
#ifndef BAKED_H_
|
||||
#define BAKED_H_
|
||||
|
||||
#include "incbin/incbin.h"
|
||||
|
||||
INCBIN_EXTERN(gpp1);
|
||||
|
||||
INCBIN_EXTERN(home_t);
|
||||
INCBIN_EXTERN(page_missing_t);
|
||||
|
||||
INCBIN_EXTERN(simple_min_css);
|
||||
INCBIN_EXTERN(favicon_ico);
|
||||
INCBIN_EXTERN(hotreload_js);
|
||||
typedef struct {
|
||||
int memfd;
|
||||
void *bufptr;
|
||||
} Baked_Resource_Value;
|
||||
|
||||
typedef struct {
|
||||
char *key; // path
|
||||
int value; // memfd
|
||||
Baked_Resource_Value value;
|
||||
} Baked_Resource;
|
||||
|
||||
void init_baked_resources(void);
|
||||
void free_baked_resources(void);
|
||||
bool get_baked_resource_path(char *key, char *buf, size_t size);
|
||||
void baked_resource_each(void (*f)(Baked_Resource *resource, void *udata), void *udata);
|
||||
|
||||
#endif // BAKED_H_
|
||||
|
273
blog/blog-asset-packing-with-zip.c.md
Normal file
273
blog/blog-asset-packing-with-zip.c.md
Normal file
@ -0,0 +1,273 @@
|
||||
# Asset packing with [zip.c](https://raw.githubusercontent.com/kuba--/zip/refs/heads/master/src/zip.c)
|
||||
|
||||
One of the technical decisions that I had to make when writing this website is the way that I'm going to
|
||||
distribute the assets (images, GPP, page templates, etc.). I've decided to go with packing or baking-in
|
||||
the assets into the main binary, so that I don't have to worry about updating an asset folder alongside
|
||||
the application.
|
||||
|
||||
One flaw of this approach is that as the website gets bigger, the binary will too. Fortunately I don't
|
||||
have to worry about this too much right now, since I've reimplemented asset packing with the excellent
|
||||
zip library by \`kuba--\` (Polska GUROM!!!111!!). In this article I'd like to demonstrate how I've
|
||||
minimised the size of our assets using \`zip\` and \`incbin\`.
|
||||
|
||||

|
||||
|
||||
## References
|
||||
|
||||
- zip: https://raw.githubusercontent.com/kuba--/zip/refs/heads/master/src/zip.c
|
||||
- incbin: https://github.com/graphitemaster/incbin
|
||||
- zip format: https://en.wikipedia.org/wiki/ZIP_(file_format)
|
||||
- the source code: https://git.kamkow1lair.pl/kamkow1/aboba
|
||||
|
||||
## Generating the bundle - \`bundle.zip\`
|
||||
|
||||
To compress our assets, first we need to generate a bundle file. I've decided to call it bundle.zip - simple
|
||||
and descriptive.
|
||||
|
||||
Generating the bundle requires some changes to our build program. See https://git.kamkow1lair.pl/kamkow1/aboba/src/branch/master/build.c for reference.
|
||||
|
||||
\`\`\`
|
||||
#define BUNDLED_FILES \\
|
||||
"./gpp1", \\
|
||||
"./tmpls/home.html", \\
|
||||
"./tmpls/page-missing.html", \\
|
||||
"./tmpls/template-blog.html", \\
|
||||
"./tmpls/blog.html", \\
|
||||
"./etc/hotreload.js", \\
|
||||
"./etc/theme.js", \\
|
||||
"./etc/simple.css", \\
|
||||
"./etc/highlight.js", \\
|
||||
"./etc/hljs-rainbow.css", \\
|
||||
"./etc/marked.js", \\
|
||||
"./etc/favicon.ico", \\
|
||||
"./etc/me.jpg", \\
|
||||
"./etc/tmoa-engine.jpg", \\
|
||||
"./etc/tmoa-garbage.jpg", \\
|
||||
"./blog/blog-welcome.md", \\
|
||||
"./blog/blog-weird-page.md", \\
|
||||
"./blog/blog-curious-case-of-gebs.md", \\
|
||||
"./blog/blog-the-making-of-aboba.md", \\
|
||||
"./blog/blog-asset-packing-with-zip.c.md"
|
||||
|
||||
const char *bundle_zip_deps[] = { BUNDLED_FILES };
|
||||
|
||||
RULE_ARRAY("./bundle.zip", bundle_zip_deps) {
|
||||
RULE("./gpp1", "./gpp/gpp.c") {
|
||||
CMD("cc", "-DHAVE_STRDUP", "-DHAVE_FNMATCH_H", "-o", "gpp1", "gpp/gpp.c");
|
||||
}
|
||||
|
||||
struct zip_t *zip = zip_open("./bundle.zip", BUNDLE_ZIP_COMPRESSION, 'w');
|
||||
defer { zip_close(zip); }
|
||||
|
||||
for (size_t i = 0; i < sizeof(bundle_zip_deps)/sizeof(bundle_zip_deps[0]); i++) {
|
||||
char *copy = strdup(bundle_zip_deps[i]);
|
||||
defer { free(copy); }
|
||||
char *name = basename(copy);
|
||||
|
||||
String_Builder sb = {0};
|
||||
defer { sb_free(&sb); }
|
||||
sb_read_file(&sb, bundle_zip_deps[i]);
|
||||
|
||||
zip_entry_open(zip, name);
|
||||
zip_entry_write(zip, sb.items, sb.count);
|
||||
zip_entry_close(zip);
|
||||
}
|
||||
LOGI("Generated bundle.zip\\n");
|
||||
}
|
||||
|
||||
RULE("./aboba",
|
||||
"./main.c",
|
||||
"./routes.c",
|
||||
"./routes.h",
|
||||
"./baked.c",
|
||||
"./baked.h",
|
||||
"./commit.h",
|
||||
"./timer.c",
|
||||
"./timer.h",
|
||||
"./CONFIG.h",
|
||||
"./locked.h",
|
||||
|
||||
"./mongoose.o",
|
||||
|
||||
"./bundle.zip",
|
||||
BUNDLED_FILES
|
||||
) {
|
||||
// build mongoose.o - skipped
|
||||
|
||||
// Generate commit.h - skipped
|
||||
|
||||
#define CC "cc"
|
||||
#define TARGET "-o", "aboba"
|
||||
#if MY_DEBUG
|
||||
#define CFLAGS "-fsanitize=address", "-fPIC", "-ggdb"
|
||||
#define DEFINES "-DMY_DEBUG=1", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", \\
|
||||
"-DGEBS_ENABLE_PTHREAD_FEATURES"
|
||||
#define EXTRA_SOURCES "./cJSON/cJSON.c", "./zip/src/zip.c", "./md5-c/md5.c"
|
||||
#else
|
||||
#define CFLAGS "-fPIC"
|
||||
#define DEFINES "-DMY_DEBUG=0", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", \\
|
||||
"-DGEBS_ENABLE_PTHREAD_FEATURES"
|
||||
#define EXTRA_SOURCES "./cJSON/cJSON.c", "./zip/src/zip.c"
|
||||
#endif
|
||||
#define SOURCES "./main.c", "./routes.c", "./baked.c", "./timer.c"
|
||||
#define OBJECTS "./mongoose.o"
|
||||
#define LINK_FLAGS "-Wl,-z,execstack", "-lpthread"
|
||||
#define INC_FLAGS "-I.", "-I./zip/src"
|
||||
|
||||
CMD(CC, TARGET, CFLAGS, DEFINES, INC_FLAGS, SOURCES, OBJECTS, EXTRA_SOURCES, LINK_FLAGS);
|
||||
|
||||
// generate compile_flags.txt - skipped
|
||||
|
||||
// #undef macros - skipped
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
If you go through the commit history, you'll see that apart from just generating the bundle file, I've also cleaned up the
|
||||
build commands a bit with \`#define\`s. Let's take a closer look at the bundle generation code.
|
||||
|
||||
\`\`\`
|
||||
const char *bundle_zip_deps[] = { BUNDLED_FILES };
|
||||
|
||||
RULE_ARRAY("./bundle.zip", bundle_zip_deps) {
|
||||
RULE("./gpp1", "./gpp/gpp.c") {
|
||||
CMD("cc", "-DHAVE_STRDUP", "-DHAVE_FNMATCH_H", "-o", "gpp1", "gpp/gpp.c");
|
||||
}
|
||||
|
||||
struct zip_t *zip = zip_open("./bundle.zip", BUNDLE_ZIP_COMPRESSION, 'w');
|
||||
defer { zip_close(zip); }
|
||||
|
||||
for (size_t i = 0; i < sizeof(bundle_zip_deps)/sizeof(bundle_zip_deps[0]); i++) {
|
||||
char *copy = strdup(bundle_zip_deps[i]);
|
||||
defer { free(copy); }
|
||||
char *name = basename(copy);
|
||||
|
||||
String_Builder sb = {0};
|
||||
defer { sb_free(&sb); }
|
||||
sb_read_file(&sb, bundle_zip_deps[i]);
|
||||
|
||||
zip_entry_open(zip, name);
|
||||
zip_entry_write(zip, sb.items, sb.count);
|
||||
zip_entry_close(zip);
|
||||
}
|
||||
LOGI("Generated bundle.zip\\n");
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
We declare a dependency (using \`RULE\*()\` macro), which says that \`./bundle.zip\` depends on files defined by \`BUNDLED_FILES\`.
|
||||
To generate the bundle we use \`zip_open()\` \`zip_close()\`. To call \`zip_open()\`, we have to provide a so called "compression level".
|
||||
The zip library provides us only with \`ZIP_DEFAULT_COMPRESSION_LEVEL\`, which is a macro that evaluates to 6. I wasn't satisfied with
|
||||
it, so after looking at \`miniz.h\` (a backing library that zip uses), I've found that zip uses \`MZ_DEFAULT_COMPRESSION_LEVEL\`, which
|
||||
is 6, but we can use the value of \`MZ_UBER_COMPRESSION\`, which is 10. This way we can achieve the most size-efficient compression.
|
||||
|
||||
I've decided to \`#define\` the compression level to avoid using arbitrary magic numbers and we can also use that definition both in
|
||||
the application and in the build program. Here's how \`CONFIG.h\` looks like now:
|
||||
|
||||
\`\`\`
|
||||
#ifndef CONFIG_H_
|
||||
#define CONFIG_H_
|
||||
|
||||
#if MY_DEBUG
|
||||
# define CONFIG_LISTEN_URL "http://localhost:8080"
|
||||
#else
|
||||
# define CONFIG_LISTEN_URL "http://localhost:5000"
|
||||
#endif
|
||||
|
||||
#define BUNDLE_ZIP_COMPRESSION 10
|
||||
|
||||
#endif // CONFIG_H_
|
||||
\`\`\`
|
||||
|
||||
The only "downside" here is that, since we're compressing so hard, it's going to take more time to generate the bundle. I've put "downside"
|
||||
in quotes for purpose, because this does not apply in our case. The files that we're packing are quite small already and there aren't
|
||||
many of them. We're just doing this to sqeeze out *extra* spacial performance.
|
||||
|
||||
Previously we were baking-in the assets like so:
|
||||
|
||||
from https://git.kamkow1lair.pl/kamkow1/aboba/src/commit/447362c74dda85838d37d6ee3cb218697abf6106/baked.c
|
||||
|
||||
\`\`\`
|
||||
INCBIN(gpp1, "./gpp1");
|
||||
|
||||
INCBIN(home_html, "./tmpls/home.html");
|
||||
INCBIN(page_missing_html, "./tmpls/page-missing.html");
|
||||
INCBIN(template_blog_html, "./tmpls/template-blog.html");
|
||||
INCBIN(blog_html, "./tmpls/blog.html");
|
||||
|
||||
INCBIN(simple_css, "./etc/simple.css");
|
||||
INCBIN(favicon_ico, "./etc/favicon.ico");
|
||||
#if MY_DEBUG
|
||||
INCBIN(hotreload_js, "./etc/hotreload.js");
|
||||
#endif
|
||||
INCBIN(theme_js, "./etc/theme.js");
|
||||
INCBIN(highlight_js, "./etc/highlight.js");
|
||||
INCBIN(hljs_rainbow_css, "./etc/hljs-rainbow.css");
|
||||
INCBIN(marked_js, "./etc/marked.js");
|
||||
INCBIN(me_jpg, "./etc/me.jpg");
|
||||
INCBIN(tmoa_engine_jpg, "./etc/tmoa-engine.jpg");
|
||||
INCBIN(tmoa_garbage_jpg, "./etc/tmoa-garbage.jpg");
|
||||
|
||||
INCBIN(blog_welcome_md, "./blog/welcome.md");
|
||||
INCBIN(blog_weird_page_md, "./blog/weird-page.md");
|
||||
INCBIN(blog_curious_case_of_gebs_md, "./blog/curious-case-of-gebs.md");
|
||||
INCBIN(blog_the_making_of_aboba_md, "./blog/the-making-of-aboba.md");
|
||||
\`\`\`
|
||||
|
||||
Now that we have our \`bundle.zip\`, we do it like this:
|
||||
|
||||
\`\`\`
|
||||
INCBIN(bundle_zip, "./bundle.zip");
|
||||
\`\`\`
|
||||
|
||||
And there we go, we have our bundle!
|
||||
|
||||
I've also had to slightly change the way we add the assets to the resource hash table:
|
||||
|
||||
\`\`\`
|
||||
void add_baked_resource(char *key, const uchar *data, size_t size)
|
||||
{
|
||||
int fd = memfd_create(key, 0);
|
||||
if (fd < 0) {
|
||||
LOGE("Could not create resource %s. Aborting...\n", key);
|
||||
abort();
|
||||
}
|
||||
write(fd, data, size);
|
||||
shput(baked_resources.value, key, ((Baked_Resource_Value){ .memfd = fd, .bufptr = (void *)data }));
|
||||
}
|
||||
|
||||
void init_baked_resources(void)
|
||||
{
|
||||
struct zip_t *zip = zip_stream_open(bundle_zip_data, bundle_zip_size, BUNDLE_ZIP_COMPRESSION, 'r');
|
||||
size_t n = zip_entries_total(zip);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
zip_entry_openbyindex(zip, i);
|
||||
|
||||
const char *name = strdup(zip_entry_name(zip));
|
||||
size_t size = zip_entry_size(zip);
|
||||
char *buf = malloc(size);
|
||||
zip_entry_noallocread(zip, buf, size);
|
||||
|
||||
add_baked_resource((char *)name, buf, size);
|
||||
|
||||
zip_entry_close(zip);
|
||||
}
|
||||
zip_stream_close(zip);
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
\`add_baked_resource()\` hasn't changed here, but \`init_baked_resources()\` has. Here we use one of zip's abilities,
|
||||
which is unpacking an in-memory .zip file. We iterate each entry in the bundle, get the name and size, preallocate
|
||||
a buffer and read said entry into the buffer. We can then add the resource to the hash table as we did previously.
|
||||
|
||||
## Conclusion
|
||||
|
||||

|
||||
|
||||
One question you may ask is, how much space are we saving? I don't remeber the exact sizes of the binary, but
|
||||
I remember that before compression it was \`~1.2M\` and now after compression is implemented, it's \`~1.6M\`. How is
|
||||
that an improvement if the binary gained weight? That \`~.4M\` is likely due to zip format overhead - metadata, file and
|
||||
directory headers and so on. What we gain here is that the binary hasn't changed in size much despite adding more files,
|
||||
like for eg. this article that I'm writing right now. It has stayed at \`~1.6M\` so far and the size doesn't go up.
|
||||
*(So far)* We aren't even making byte-sized gains, which can be checked with \`stat --printf "%s" ./aboba\`. What
|
||||
we're gaining here is slowed down size increase. Previously if I wanted to add a 30K jpeg, the binary would literally
|
||||
go up by 30K.
|
||||
|
142
blog/blog-curious-case-of-gebs.md
Normal file
142
blog/blog-curious-case-of-gebs.md
Normal file
@ -0,0 +1,142 @@
|
||||
# GEBS - the Good Enough Build System
|
||||
|
||||
Source code: http://git.kamkow1lair.pl/kamkow1/gebs
|
||||
|
||||
GEBS is a reiteration of my previous build system "MIBS" (or MIni Build System). It takes some inspiration from
|
||||
[Tsoding's](https://twitch.tv/tsoding) [nobuild](https://github.com/tsoding/nobuild) and later on [nob.h](https://github.com/tsoding/nob.h).
|
||||
The key difference is the way GEBS is implemented on the inside, which makes it more powerful and extensible than nob.h.
|
||||
GEBS also includes a bunch of extra helper macros, which turn C into a language more akin to Go or Zig, but more on that later.
|
||||
|
||||
## So what makes GEBS different?
|
||||
|
||||
### Allocators
|
||||
|
||||
So one thing I've noticed is that nob.h is used alongside of [arena](https://github.com/tsoding/arena). If you look into the implementation
|
||||
you can see some things, which are somewhat redundant like \`arena_sprintf()\` or \`arena_da_append()\`, \`arena_sb_append_cstr()\` and so on...
|
||||
First of all, why is an arena library managing string builders and dynamic arrays? In my opinion it should be the other way around.
|
||||
A string builder should rather accept a generic allocator interface, which it can then utilize to get it's memory.
|
||||
Basically we supplement a dynamic structure with an allocator of choice.
|
||||
In GEBS this is done via a \`Gebs_Allocator\` interface.
|
||||
|
||||
\`\`\`c
|
||||
typedef struct {
|
||||
void *(*malloc)(void *self, size_t size);
|
||||
void (*free)(void *self, void *memory);
|
||||
void *(*realloc)(void *self, void *memory, size_t prev_size, size_t new_size);
|
||||
} Gebs_Allocator;
|
||||
|
||||
// Wrapper macros
|
||||
#define gebs_malloc(alloc, size) ((alloc)->malloc((void *)(alloc), (size)))
|
||||
#define gebs_free(alloc, memory) ((alloc)->free((void *)(alloc), (memory)))
|
||||
#define gebs_realloc(alloc, memory, prev_size, new_size) \
|
||||
((alloc)->realloc((void *)(alloc), (memory), (prev_size), (new_size)))
|
||||
\`\`\`
|
||||
|
||||
We then can implement an allocator that conforms to this interface and it will work with any dynamic structure.
|
||||
This is my version of the \`XXX_da_append()\` macro:
|
||||
|
||||
\`\`\`c
|
||||
#define gebs_list_append_alloc(alloc, list, item) \\
|
||||
do { \\
|
||||
if ((list)->items == nil) { \\
|
||||
(list)->capacity = 1; \\
|
||||
(list)->items = gebs_malloc((alloc), \\
|
||||
sizeof(*(list)->items) * (list)->capacity); \\
|
||||
} else { \\
|
||||
if ((list)->count == (list)->capacity) { \\
|
||||
size_t __prev_capacity = (list)->capacity; \\
|
||||
(list)->capacity *= 2; \\
|
||||
(list)->items = gebs_realloc((alloc), (list)->items, \\
|
||||
sizeof(*(list)->items) * __prev_capacity, \\
|
||||
sizeof(*(list)->items) * (list)->capacity); \\
|
||||
} \\
|
||||
} \\
|
||||
(list)->items[(list)->count++] = (item); \\
|
||||
} while(0)
|
||||
|
||||
#define gebs_list_append(list, item) \\
|
||||
gebs_list_append_alloc(&gebs_default_allocator, (list), (item))
|
||||
\`\`\`
|
||||
|
||||
This way a dynamic list can work with any kind of allocator - the default libc allocator, an arena or literally anything else.
|
||||
We're not tied to the libc allocator and then have to implement the same macro of all other allocators.
|
||||
|
||||
### Defer macro
|
||||
|
||||
Ever forgot to place a \`free()\` call on function exit or an \`fclose()\`? The defer macro comes to the rescue. Here's a short snippet:
|
||||
(Taken straight form the source code of this website btw.)
|
||||
|
||||
\`\`\`c
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
defer { cJSON_Delete(root); }
|
||||
|
||||
char *time = __TIME__;
|
||||
uchar md5_buf[16];
|
||||
md5String(time, md5_buf);
|
||||
String_Builder sb = {0};
|
||||
defer { sb_free(&sb); }
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
sb_append_nstr(&sb, fmt("%02x", md5_buf[i]));
|
||||
}
|
||||
sb_finish(&sb);
|
||||
|
||||
cJSON_AddItemToObject(root, "build_id", cJSON_CreateString(sb.items));
|
||||
|
||||
make_application_json(result, 200, root);
|
||||
\`\`\`
|
||||
|
||||
If not for the \`defer { ... }\` macro, remebering when to free memory would have been quite hellish.
|
||||
Another example:
|
||||
|
||||
\`\`\`c
|
||||
NString_List env = {0};
|
||||
defer { list_free(&env); }
|
||||
|
||||
String_Builder out = {0};
|
||||
defer { sb_free(&out); }
|
||||
|
||||
char path[PATH_MAX] = {0};
|
||||
if (!get_baked_resource_path("home.html", path, sizeof(path))) {
|
||||
make_internal_server_error(result);
|
||||
return;
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
On \`return\` we'd have to **NOT FORGET** to add \`list_free()\` and \`sb_free()\`, but now that we have our defer,
|
||||
we can kind of shut the brain off and not concern ourselves with freeing the memory. We can be 100% sure it's going to
|
||||
be freed if we step into the return statement.
|
||||
|
||||
The implementation is quite simple, actually
|
||||
|
||||
\`\`\`
|
||||
#define defer defer__2(__COUNTER__)
|
||||
#define defer__2(X) defer__3(X)
|
||||
#define defer__3(X) defer__4(defer__id##X)
|
||||
#define defer__4(ID) auto void ID##func(char (*)[]); __attribute__((cleanup(ID##func))) char ID##var[0]; void ID##func(char (*ID##param)[])
|
||||
\`\`\`
|
||||
|
||||
Source article: https://gustedt.wordpress.com/2025/01/06/simple-defer-ready-to-use/
|
||||
|
||||
### compile_flags.txt
|
||||
|
||||
Clang/LLVM docs: https://clang.llvm.org/docs/JSONCompilationDatabase.html
|
||||
|
||||
I use clangd inside of my vim. Clangd can be configured via a json database compile_commands.json. It's quite complicated for GEBS in a sense
|
||||
that it uses the \`XX.c -> XX.o\` building pattern, while GEBS is focused more on unity builds (it's on the programmer to implement caching).
|
||||
Luckily, clangd can be configured via a simple and minimalistic config file - \`compile_flags.txt\`, which holds only compiler flags that
|
||||
are used to compile our C files. We can for eg. put some include paths in there and clangd will pick them up.
|
||||
|
||||
In GEBS we can generate a \`compile_flags.txt\` file using a built-in macro:
|
||||
|
||||
\`\`\`c
|
||||
#define CFLAGS \\
|
||||
"-I.", \\
|
||||
"-I./some-lib", \\
|
||||
"-Wall", \\
|
||||
"-Wextra" \\
|
||||
|
||||
// #define other stuff like CC, LDFLAGS, SOURCES
|
||||
|
||||
make_compile_flags(CFLAGS); // Will output the file
|
||||
\`\`\`
|
||||
|
407
blog/blog-the-making-of-aboba.md
Normal file
407
blog/blog-the-making-of-aboba.md
Normal file
@ -0,0 +1,407 @@
|
||||
# The making of aboba (this website)
|
||||
|
||||
In this article I'd like to present to you the internals of this website,
|
||||
how the code is architectured and some cool tricks that are used throughout the project.
|
||||
|
||||
## Our "engine"
|
||||
|
||||

|
||||
|
||||
This image is a joke, obviously.
|
||||
|
||||
The "engine" here is a web server that we're going to be using. I've decided to pick mongoose (-> [their page](https://mongoose.ws)),
|
||||
partly because I didn't know about/couldn't find other solutions, but I'm really happy with my pick. The only "downside" here is that
|
||||
mongoose is not http-specific, but also has websockets, MQTT, SNTP and even RPC. While that's really cool, I only need http and not
|
||||
much else. I haven't dove deeper into mongoose, but I'd be cool if they provided some \`#ifdef\`s to just disable these protocols
|
||||
(ie. strip down the code that implements them). That way I could make mongoose even more lightweight and only use the features that I need.
|
||||
|
||||
Here's roughly how we work with mongoose. Refer to their documentation for more context.
|
||||
|
||||
Let's start with \`main()\`
|
||||
\`\`\`
|
||||
volatile bool alive = true;
|
||||
|
||||
void graceful_shutdown(int no) { alive = false; }
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
signal(SIGINT, &graceful_shutdown);
|
||||
|
||||
// skip BS
|
||||
|
||||
mg_log_set(MG_LL_DEBUG);
|
||||
struct mg_mgr mgr;
|
||||
mg_mgr_init(&mgr);
|
||||
|
||||
// skip BS
|
||||
|
||||
mg_wakeup_init(&mgr); // We need this for multithreading
|
||||
mg_http_listen(&mgr, CONFIG_LISTEN_URL, &event_handler, NULL);
|
||||
|
||||
while (alive) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
// skip BS
|
||||
}
|
||||
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
// skip BS
|
||||
|
||||
return 0;
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
As you can see it's quite simple to set up mongoose. Here's what the used functions do:
|
||||
- \`mg_log_set()\` - set the log level. \`MG_LL_DEBUG\` is very verbose, but it's good for
|
||||
when the application breaks and we have no clue why.
|
||||
- \`struct mg_mgr\` & \`mg_mgr_init()\` - this is the mongoose "manager". The detailed explaination
|
||||
can be found [here](https://mongoose.ws/documentation/#2-minute-integration-guide), but it can be
|
||||
essentially boiled down to "overall state of the web server".
|
||||
- \`mg_wakeup_init()\` - this is needed to make our application multithreaded. In the docs it says
|
||||
that it's used to "initialize the *wakeup scheme*". This basically means that we can now talk between
|
||||
multiple threads using \`mg_wakeup()\`, which is the only thread-safe function provided by mongoose.
|
||||
- \`mg_mgr_poll()\` - handle the next conection if there's any incoming data to work with. We can
|
||||
also specify the timeout for a connection. Here we provide 1 second (1000 ms).
|
||||
|
||||
That's all you really need to know to get started with mongoose. Let's get to the \`event_handler()\` now.
|
||||
|
||||
\`\`\`
|
||||
void event_handler(struct mg_connection *conn, int ev, void *ev_data)
|
||||
{
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
// Run handler in a new thread
|
||||
} else if (ev == MG_EV_WAKEUP) {
|
||||
// We've woken up from a handler by mg_wakeup(). Send the reply back to the client
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
I've removed a lot of code here, because it's irrelevant at the current point. This allowes us to look at
|
||||
the simplified image of the \`event_handler()\` function.
|
||||
|
||||
Let's stop to talk about the parameters for a second.
|
||||
|
||||
- \`struct mg_connection *conn\` - the structure that describes the incoming connection. We will also
|
||||
use it to send back our reply.
|
||||
- \`int ev\` - this is the event enumeration. Basically tells us what event we're currently handling
|
||||
inside of mongoose's event loop.
|
||||
- \`void *ev_data\` - additional event data. The value of this parameter differs based on the value of \`int ev\`.
|
||||
More on that a little bit later.
|
||||
|
||||
What goes on inside the \`MG_EV_HTTP_MSG\` branch?
|
||||
|
||||
\`\`\`
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *msg = (struct mg_http_message *)ev_data;
|
||||
|
||||
Route_Thread_Data *data = calloc(1, sizeof(*data));
|
||||
data->message = mg_strdup(msg->message);
|
||||
data->conn_id = conn->id;
|
||||
data->mgr = conn->mgr;
|
||||
run_in_thread(&route_thread_function, data);
|
||||
\`\`\`
|
||||
|
||||
If we have an "HTTP Message" event incoming, ev_data is a pointer to \`struct mg_http_message\`.
|
||||
This structure contains things like the message body, query parameters, the uri, the method and so on. Here we
|
||||
duplicate the \`message\` field, which encompasses the entire HTTP message. We also save the reference to the
|
||||
mongoose manager for later use when we will want to wake up from a thread.
|
||||
|
||||
Here's how the thread is spawned. This code is taken from the mongoose tutorial: https://github.com/cesanta/mongoose/blob/master/tutorials/core/multi-threaded/main.c#L11
|
||||
|
||||
\`\`\`
|
||||
void run_in_thread(void *(*f)(void *), void *p)
|
||||
{
|
||||
pthread_t tid = 0;
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
pthread_create(&tid, &attr, f, p);
|
||||
pthread_attr_destroy(&attr);
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
And then here's what goes on inside a thread:
|
||||
|
||||
\`\`\`
|
||||
void *route_thread_function(void *param)
|
||||
{
|
||||
Route_Thread_Data *data = (Route_Thread_Data *)param;
|
||||
|
||||
struct mg_http_message http_msg = {0};
|
||||
int r = mg_http_parse(data->message.buf, data->message.len, &http_msg);
|
||||
if (r <= 0) {
|
||||
// Unparsable HTTP request
|
||||
}
|
||||
|
||||
if (mg_match(http_msg.uri, mg_str("/bakedres/*"), nil)) {
|
||||
// Request for static resource
|
||||
}
|
||||
|
||||
// Request for a dynamic page
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
This is quite simple, so there's not much to explain here. One cool thing I'd like to mention is the
|
||||
\`mg_match()\` function. Normally I'd have to implement uri string matching myself, but I'm glad that
|
||||
this functionality comes with mongoose built-in. God bless you mongoose!
|
||||
|
||||
## Dynamic pages and static assets.
|
||||
|
||||

|
||||
|
||||
Let's stop here now and talk about something different entirely (dont' worry, we'll come back later).
|
||||
I'd like to show you how assets/pages are implemented in aboba, so we get the whole picture.
|
||||
|
||||
In most applications you typically have a distribution model that goes like this: you have your application's
|
||||
binary ("aboba" or "aboba.exe" or "aboba.bin"...) and then you have an "assets"/"resources" directory, placed
|
||||
somewhere within the filesystem. There are now 2 ways to go about this. You can have a fixed path to the assets
|
||||
directory or let the user configure it based on how they've installed the application. In the first case the
|
||||
app could require that it's assets must be located in \`/usr/share/my-app/assets\` and otherwise it will bail
|
||||
out with an error "Err: Could not find assets directory blah blah" or something. In the other case the application
|
||||
would require that the user configures the path to the assets directory themselves, which is not a bad solution
|
||||
(it may sound like it at first). This really depends what audience are we targetting. "Are our users tech-savvy
|
||||
enough to do it themselves?" is the question we'd have to ask ourselves.
|
||||
|
||||
**Enter embedded assets**
|
||||
|
||||
The downsides of both solutions is that we still have to distribute our application with an assets directory. We
|
||||
have to manage 1 executable file + an entire directory. The *obvious* solution would be to just create a
|
||||
windows-style setup wizard or a \`make install\` or some other install script thingy. This problem is easily
|
||||
solvable by literally compiling the bytes of the assets into our program. That way we can distribute only the
|
||||
executable since it's self contained and doesn't need to reach out to the filesystem at runtime to get it's resources.
|
||||
This technique is fairly old and the most notable uses I can think of are: [XPM Format](https://en.wikipedia.org/wiki/X_PixMap),
|
||||
[Windows resources](https://en.wikipedia.org/wiki/Resource_(Windows)) and raylib's [rres](https://github.com/raysan5/rres).
|
||||
Even C23 has acknowledged the significance of embedded assets (-> [article](https://thephd.dev/finally-embed-in-c23)).
|
||||
|
||||
Of course there are some pitfalls of this approach. What if the files we're trying to embed are too *thicc*? We obviously
|
||||
don't want to end up with a 5GB executable. Imagine the OS trying to load that program into the ram. That would just eat up 5
|
||||
gigs before anything meaningful ever happens. This pattern of asset distribution is the most suitable for embedded applications
|
||||
where there's no underlying filesystem to work with, games which are not too large, GUI apps that need some icons and fonts.
|
||||
|
||||
So how does aboba go about embedding it's assets? [incbin](https://github.com/graphitemaster/incbin) comes to the rescue!
|
||||
|
||||
Here are external declarations for our resources
|
||||
\`\`\`
|
||||
#include "incbin/incbin.h"
|
||||
|
||||
INCBIN_EXTERN(gpp1);
|
||||
|
||||
INCBIN_EXTERN(home_html);
|
||||
INCBIN_EXTERN(page_missing_html);
|
||||
INCBIN_EXTERN(template_blog_html);
|
||||
INCBIN_EXTERN(blog_html);
|
||||
|
||||
INCBIN_EXTERN(simple_css);
|
||||
INCBIN_EXTERN(favicon_ico);
|
||||
#if MY_DEBUG
|
||||
INCBIN_EXTERN(hotreload_js);
|
||||
#endif
|
||||
INCBIN_EXTERN(me_jpg);
|
||||
INCBIN_EXTERN(tmoa_engine_jpg);
|
||||
|
||||
INCBIN_EXTERN(blog_welcome_md);
|
||||
INCBIN_EXTERN(blog_weird_page_md);
|
||||
INCBIN_EXTERN(blog_curious_case_of_gebs_md);
|
||||
INCBIN_EXTERN(blog_the_making_of_aboba_md);
|
||||
\`\`\`
|
||||
|
||||
And here's where the actual inclusion happens:
|
||||
|
||||
\`\`\`
|
||||
INCBIN(gpp1, "./gpp1");
|
||||
|
||||
INCBIN(home_html, "./tmpls/home.html");
|
||||
INCBIN(page_missing_html, "./tmpls/page-missing.html");
|
||||
INCBIN(template_blog_html, "./tmpls/template-blog.html");
|
||||
INCBIN(blog_html, "./tmpls/blog.html");
|
||||
|
||||
INCBIN(simple_css, "./etc/simple.css");
|
||||
INCBIN(favicon_ico, "./etc/favicon.ico");
|
||||
#if MY_DEBUG
|
||||
INCBIN(hotreload_js, "./etc/hotreload.js");
|
||||
#endif
|
||||
INCBIN(me_jpg, "./etc/me.jpg");
|
||||
INCBIN(tmoa_engine_jpg, "./etc/tmoa-engine.jpg");
|
||||
INCBIN(tmoa_garbage.jpg, "./etc/tmoa-garbage.jpg");
|
||||
|
||||
INCBIN(blog_welcome_md, "./blog/welcome.md");
|
||||
INCBIN(blog_weird_page_md, "./blog/weird-page.md");
|
||||
INCBIN(blog_curious_case_of_gebs_md, "./blog/curious-case-of-gebs.md");
|
||||
INCBIN(blog_the_making_of_aboba_md, "./blog/the-making-of-aboba.md");
|
||||
\`\`\`
|
||||
|
||||
As you can see, with incbin embedding assets is extremely easy. In the past I've worked with my
|
||||
own asset packer utility \`x2h.c\`, but it was kind of sloppy and I don't have the will to
|
||||
rewrite it (maybe one day ;) ).
|
||||
|
||||
Incbin works (on GCC, which I'm using here) by calling into the assembler using \`__asm__()\` and then uses \`.incbin\`
|
||||
directive to include the binary file. More on that can be found in the docs: https://sourceware.org/binutils/docs/as/Incbin.html
|
||||
|
||||
Now let the **trickery** begin...
|
||||
|
||||
## Tangent about templating
|
||||
|
||||
Don't worry, in this section I'll briefly explain how templating is implemented, so that we can circle back to our embedded assets system to better understand it.
|
||||
|
||||
How are templating engines implemented in general? You usually have a pseudo-HTML page (for eg. \`my-page.tmpl\`), which
|
||||
contains plain old HTML, but also has some escaped blocks that can embed dynamic variables, loop over lists, include other pages
|
||||
and whatnot. You then take that pseudo-page and run it through some kind of a (keyword) *preprocessor*, which given an environment,
|
||||
can expand variable *macros*, unfold loops and other tricks.
|
||||
|
||||
Do you already see where I'm going with my wording?
|
||||
|
||||
I want to keep this a *mostly* a pure C project, where every bit of it that can be written in C, is written in C. Yes, even the templates
|
||||
(*in a way*). In order to expand the page templates I've decided to use GPP or the General PreProcessor written by Denis Auroux and maintained
|
||||
by Tristan Miller. Here's a link to GPP's website (-> [click](https://logological.org/gpp)). Why not use regular CPP that comes with the GCC
|
||||
suite, you may ask. This is because GPP has a special mode - the HTML mode, which makes it so that it's better suited for working with HTML.
|
||||
For example \`\#define\` vs. \`<\#define>\` or \`\#ifdef\` and \`\#endif\` vs. \`<\#ifdef>\` and \`<\#endif>\`.
|
||||
|
||||
How do we interact with GPP then? To preprocess our templates, we can call GPP via it's command line interface (CLI) - set HTML mode, give path
|
||||
to the template file and collect the output, which we then can send back to the client.
|
||||
|
||||
But wait, if we call out to an external executable, doesn't that mean that we'll have to ship GPP alongside aboba? The answer is yes! Since
|
||||
we follow the *principle of a single-executable-deployment*, why don't we pack GPP into our binary, just like any other asset? Another question
|
||||
arises then - how do we call a packed program and how to we work with it's CLI? I'll answer this in the next part...
|
||||
|
||||
## Back to the assets
|
||||
|
||||
So now that we've established why and how we embed assets into an executable and the way we work with templates, we can now discuss the
|
||||
**embedded assets system**.
|
||||
|
||||
Here's the core API of the system:
|
||||
|
||||
\`\`\`
|
||||
typedef struct {
|
||||
char *key; // path
|
||||
int value; // memfd
|
||||
} Baked_Resource;
|
||||
|
||||
void init_baked_resources(void);
|
||||
void free_baked_resources(void);
|
||||
// skip BS
|
||||
bool get_baked_resource_path(char *key, char *buf, size_t size);
|
||||
// skip BS
|
||||
\`\`\`
|
||||
|
||||
The \`Baked_Resource\` struct is defined in such a way that works with \`stb_ds.h\`'s string hashmap. stb_ds.h can be found here: https://nothings.org/stb_ds/.
|
||||
Let's take a closer look at the fields:
|
||||
- \`key\` - a key to a file within our hashmap of baked resources
|
||||
- \`value\` - a memfd associated with the baked resource
|
||||
|
||||
Here's the initialization and deinitialization of baked resources:
|
||||
|
||||
\`\`\`
|
||||
|
||||
void add_baked_resource(char *key, const uchar *data, size_t size)
|
||||
{
|
||||
int fd = memfd_create(key, 0);
|
||||
if (fd < 0) {
|
||||
LOGE("Could not create resource %s. Aborting...", key);
|
||||
abort();
|
||||
}
|
||||
write(fd, data, size);
|
||||
shput(baked_resources.value, key, fd);
|
||||
}
|
||||
|
||||
void init_baked_resources(void)
|
||||
{
|
||||
lockx(&baked_resources);
|
||||
add_baked_resource("home.html", home_html_data, home_html_size);
|
||||
add_baked_resource("page-missing.html", page_missing_html_data, page_missing_html_size);
|
||||
add_baked_resource("template-blog.html", template_blog_html_data, template_blog_html_size);
|
||||
add_baked_resource("blog.html", blog_html_data, blog_html_size);
|
||||
add_baked_resource("gpp1", gpp1_data, gpp1_size);
|
||||
add_baked_resource("simple.css", simple_css_data, simple_css_size);
|
||||
add_baked_resource("favicon.ico", favicon_ico_data, favicon_ico_size);
|
||||
#if MY_DEBUG
|
||||
add_baked_resource("hotreload.js", hotreload_js_data, hotreload_js_size);
|
||||
#endif
|
||||
add_baked_resource("me.jpg", me_jpg_data, me_jpg_size);
|
||||
add_baked_resource("tmoa-engine.jpg", tmoa_engine_jpg_data, tmoa_engine_jpg_size);
|
||||
add_baked_resource("tmoa-garbage.jpg", tmoa_garbage_jpg_data, tmoa_garbage_jpg_size);
|
||||
add_baked_resource("blog-welcome.md", blog_welcome_md_data, blog_welcome_md_size);
|
||||
add_baked_resource("blog-weird-page.md", blog_weird_page_md_data, blog_weird_page_md_size);
|
||||
add_baked_resource("blog-curious-case-of-gebs.md", blog_curious_case_of_gebs_md_data, blog_curious_case_of_gebs_md_size);
|
||||
add_baked_resource("blog-the-making-of-aboba.md", blog_the_making_of_aboba_md_data, blog_the_making_of_aboba_md_size);
|
||||
unlockx(&baked_resources);
|
||||
}
|
||||
|
||||
void free_baked_resources(void)
|
||||
{
|
||||
lockx(&baked_resources);
|
||||
for (size_t i = 0; i < shlen(baked_resources.value); i++) {
|
||||
close(baked_resources.value[i].value);
|
||||
}
|
||||
shfree(baked_resources.value);
|
||||
unlockx(&baked_resources);
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
Here we use memfd API to convert a baked-in file into a file that has a file descriptor associated with it. Why? We do this, because
|
||||
we have no way of passing files down to GPP's CLI. We only have the raw bytes of a file, which we can't really work with. memfds
|
||||
allow us to create a virtual memory-mapped file and get it's file descriptor. Using said file descriptor we can then write our file's
|
||||
bytes into the virtual file, making it accessible via Linux's VFS. The virtual file can be accessed via a path like \`/proc/<PID>/fd/<THE FILE>\`.
|
||||
Now that we've successfully converted a baked-in file into a "*pathed*" file, we can then pass the path down to GPP. Heck, we can even run
|
||||
GPP itself from a memory-mapped file!
|
||||
|
||||
Here's how we get the memory-mapped file's path in aboba:
|
||||
|
||||
\`\`\`
|
||||
bool get_baked_resource_path(char *key, char *buf, size_t size)
|
||||
{
|
||||
if (shgeti(baked_resources.value, key) != -1) {
|
||||
int fd = shget(baked_resources.value, key);
|
||||
snprintf(buf, size, "/proc/%d/fd/%d", getpid(), fd);
|
||||
unlockx(&baked_resources);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
And then we run GPP like so:
|
||||
|
||||
\`\`\`
|
||||
bool gpp_run(char *path, NString_List *env, String_Builder *out)
|
||||
{
|
||||
Cmd cmd = {0};
|
||||
defer { cmd_free(&cmd); }
|
||||
|
||||
char gpp1[PATH_MAX];
|
||||
if (!get_baked_resource_path("gpp1", gpp1, sizeof(gpp1))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cmd_append(&cmd, gpp1);
|
||||
cmd_append(&cmd, "-H");
|
||||
cmd_append(&cmd, "-x");
|
||||
cmd_append(&cmd, "--nostdinc");
|
||||
cmd_append(&cmd, path);
|
||||
|
||||
for (size_t i = 0; i < env->count; i++) {
|
||||
cmd_append(&cmd, env->items[i]);
|
||||
}
|
||||
|
||||
return cmd_run_collect(&cmd, out) == 0;
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
In the logger we can now see commands like this: \`Info: cmd /proc/1210675/fd/8 -H -x --nostdinc /proc/1210675/fd/6 ...\`, where
|
||||
/proc/1210675/fd/8 is memory-mapped file for GPP and /proc/1210675/fd/6 is a memory-mapped file for the template. Pretty cool, eh?
|
||||
|
||||
## Left out topics
|
||||
|
||||
I think this is out of scope of this article, so I'm not going to talk about it here, but a big part of this project was making
|
||||
live hotreloading. I can basically edit the website inside of my editor and it auto-refreshes in the browser, kinda like a vite js
|
||||
project. A video of this can be found here: https://www.reddit.com/r/C_Programming/comments/1lbzjvi/webdev_in_c_pt2_true_live_hotreloading_no_more/
|
||||
|
||||
## Summary
|
||||
|
||||
During this project I've learned a lot about Linux, the memfd API and webdev in general. Normally I wouldn't pick up a website project
|
||||
simply because I'm tired of webdev. I've been through a webdev phase and it sucked. Using JavaScript, 300MB of node_modules for a bare
|
||||
react hello world project, npm installing countless slop libraries and so on.
|
||||
Then on the backend you have the same amount of C# or Java or TypeScript slop, but now you can call yourself a *backend engineer* or
|
||||
whatever the hell. Writing this website in C put me on a different view of webdev and made it actually fun to write.
|
||||
|
||||
You can go check out the code for aboba @ http://git.kamkow1lair.pl/kamkow1/aboba.git.
|
||||
|
2
blog/blog-weird-page.md
Normal file
2
blog/blog-weird-page.md
Normal file
@ -0,0 +1,2 @@
|
||||
# Hello??? Who's here??
|
||||
|
11
blog/blog-welcome.md
Normal file
11
blog/blog-welcome.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Welcome to my blog!
|
||||
|
||||
## What is it about?
|
||||
|
||||
This blog is about me and my hobbies - recreational coding, lifting weights and other miscellaneous things.
|
||||
This is pretty much a dumping ground for my thoughts, ideas, opinions and whatever I have on my mind. Don't expect
|
||||
clean, structured, nice and neutral/unbiased writing. Here I can say what I want.
|
||||
|
||||
## Who am I?
|
||||
|
||||
// TODO: uhhhhh write something
|
179
build.c
179
build.c
@ -1,61 +1,172 @@
|
||||
#include <libgen.h>
|
||||
#define GEBS_NO_PREFIX
|
||||
#define GEBS_IMPLEMENTATION
|
||||
#include "gebs/gebs.h"
|
||||
#include "./zip/src/zip.c"
|
||||
#include "CONFIG.h"
|
||||
|
||||
char *prog = NULL;
|
||||
|
||||
#define DEBUG 1
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
rebuild_self(argc, argv, "cc", "-o", "build", "build.c");
|
||||
#if MY_DEBUG
|
||||
rebuild_self(argc, argv, "cc", "-DMY_DEBUG=1", "-o", "build", "build.c");
|
||||
#else
|
||||
rebuild_self(argc, argv, "cc", "-DMY_DEBUG=0", "-o", "build", "build.c");
|
||||
#endif
|
||||
|
||||
prog = SHIFT(&argc, &argv);
|
||||
char *cmd = SHIFT(&argc, &argv);
|
||||
if (strcmp(cmd, "make") == 0) {
|
||||
RULE("./aboba",
|
||||
"./main.c",
|
||||
"./routes.c",
|
||||
"./baked.c",
|
||||
"./mongoose.o",
|
||||
"./gpp1",
|
||||
"./tmpls/home.t",
|
||||
"./tmpls/page-missing.t",
|
||||
"./etc/hotreload.js"
|
||||
) {
|
||||
#define BUNDLED_FILES \
|
||||
"./gpp1", \
|
||||
"./tmpls/home.html", \
|
||||
"./tmpls/page-missing.html", \
|
||||
"./tmpls/template-blog.html", \
|
||||
"./tmpls/blog.html", \
|
||||
"./etc/hotreload.js", \
|
||||
"./etc/theme.js", \
|
||||
"./etc/simple.css", \
|
||||
"./etc/highlight.js", \
|
||||
"./etc/hljs-rainbow.css", \
|
||||
"./etc/marked.js", \
|
||||
"./etc/favicon.ico", \
|
||||
"./etc/me.jpg", \
|
||||
"./etc/tmoa-engine.jpg", \
|
||||
"./etc/tmoa-garbage.jpg", \
|
||||
"./etc/apwz-zip-icon.png", \
|
||||
"./etc/apwz-xkcd1.png", \
|
||||
"./blog/blog-welcome.md", \
|
||||
"./blog/blog-weird-page.md", \
|
||||
"./blog/blog-curious-case-of-gebs.md", \
|
||||
"./blog/blog-the-making-of-aboba.md", \
|
||||
"./blog/blog-asset-packing-with-zip.c.md"
|
||||
|
||||
RULE("./mongoose.o", "./mongoose/mongoose.c") {
|
||||
#if DEBUG
|
||||
CMD("cc", "-ggdb", "-c", "-fPIC", "-D_GNU_SOURCE", "-o", "./mongoose.o", "./mongoose/mongoose.c");
|
||||
#else
|
||||
CMD("cc", "-c", "-fPIC", "-D_GNU_SOURCE", "-o", "./mongoose.o", "./mongoose/mongoose.c");
|
||||
#endif
|
||||
}
|
||||
const char *bundle_zip_deps[] = { BUNDLED_FILES };
|
||||
|
||||
RULE_ARRAY("./bundle.zip", bundle_zip_deps) {
|
||||
RULE("./gpp1", "./gpp/gpp.c") {
|
||||
CMD("cc", "-DHAVE_STRDUP", "-DHAVE_FNMATCH_H", "-o", "gpp1", "gpp/gpp.c");
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
CMD("cc", "-fPIC", "-ggdb", "-I.", "-DDEBUG=1", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX",
|
||||
"-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", "-o", "./aboba",
|
||||
"./main.c", "./routes.c", "./baked.c", "./mongoose.o", "./cJSON/cJSON.c",
|
||||
"./md5-c/md5.c",
|
||||
"-lpthread");
|
||||
struct zip_t *zip = zip_open("./bundle.zip", BUNDLE_ZIP_COMPRESSION, 'w');
|
||||
defer { zip_close(zip); }
|
||||
|
||||
for (size_t i = 0; i < sizeof(bundle_zip_deps)/sizeof(bundle_zip_deps[0]); i++) {
|
||||
char *copy = strdup(bundle_zip_deps[i]);
|
||||
defer { free(copy); }
|
||||
char *name = basename(copy);
|
||||
|
||||
String_Builder sb = {0};
|
||||
defer { sb_free(&sb); }
|
||||
sb_read_file(&sb, bundle_zip_deps[i]);
|
||||
|
||||
zip_entry_open(zip, name);
|
||||
zip_entry_write(zip, sb.items, sb.count);
|
||||
zip_entry_close(zip);
|
||||
}
|
||||
LOGI("Generated bundle.zip\n");
|
||||
}
|
||||
|
||||
RULE("./aboba",
|
||||
"./main.c",
|
||||
"./routes.c",
|
||||
"./routes.h",
|
||||
"./baked.c",
|
||||
"./baked.h",
|
||||
"./commit.h",
|
||||
"./timer.c",
|
||||
"./timer.h",
|
||||
"./CONFIG.h",
|
||||
"./locked.h",
|
||||
|
||||
"./mongoose.o",
|
||||
|
||||
"./bundle.zip",
|
||||
BUNDLED_FILES
|
||||
) {
|
||||
|
||||
RULE("./mongoose.o", "./mongoose/mongoose.c") {
|
||||
#if MY_DEBUG
|
||||
CMD("cc", "-ggdb", "-c", "-fPIC", "-Wl,-z,execstack", "-D_GNU_SOURCE",
|
||||
"-o", "./mongoose.o", "./mongoose/mongoose.c");
|
||||
#else
|
||||
CMD("cc", "-c", "-fPIC", "-Wl,-z,exectack", "-D_GNU_SOURCE", "-o",
|
||||
"./mongoose.o", "./mongoose/mongoose.c");
|
||||
#endif
|
||||
}
|
||||
|
||||
RULE("./commit.h") {
|
||||
String_Builder commit = {0};
|
||||
defer { sb_free(&commit); }
|
||||
Cmd commit_get_cmd = {0};
|
||||
defer { cmd_free(&commit_get_cmd); }
|
||||
cmd_append(&commit_get_cmd, "git");
|
||||
cmd_append(&commit_get_cmd, "rev-parse");
|
||||
cmd_append(&commit_get_cmd, "HEAD");
|
||||
cmd_run_collect(&commit_get_cmd, &commit);
|
||||
commit.items[commit.count - 2] = '\0'; // \n -> \0
|
||||
|
||||
LOGI("Commit %s\n", commit.items);
|
||||
|
||||
String_Builder header = {0};
|
||||
defer { sb_free(&header); }
|
||||
|
||||
sb_append_nstr(&header, "#ifndef COMMIT_H_\n");
|
||||
sb_append_nstr(&header, "#define COMMIT_H_\n");
|
||||
sb_append_nstr(&header, fmt("#define COMMIT_STRING \"%s\"\n", commit.items));
|
||||
sb_append_nstr(&header, "#endif // COMMIT_H_\n");
|
||||
|
||||
FILE *out = fopen("./commit.h", "w");
|
||||
if (out) {
|
||||
fwrite(header.items, header.count, 1, out);
|
||||
fclose(out);
|
||||
}
|
||||
}
|
||||
|
||||
#define CC "cc"
|
||||
#define TARGET "-o", "aboba"
|
||||
#if MY_DEBUG
|
||||
#define CFLAGS "-fsanitize=address", "-fPIC", "-ggdb"
|
||||
#define DEFINES "-DMY_DEBUG=1", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", \
|
||||
"-DGEBS_ENABLE_PTHREAD_FEATURES"
|
||||
#define EXTRA_SOURCES "./cJSON/cJSON.c", "./zip/src/zip.c", "./md5-c/md5.c"
|
||||
#else
|
||||
CMD("cc", "-fPIC", "-I.", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX",
|
||||
"-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", "-o", "./aboba",
|
||||
"./main.c", "./routes.c", "./baked.c", "./mongoose.o", "./cJSON/cJSON.c",
|
||||
"./md5-c/md5.c",
|
||||
"-lpthread");
|
||||
#define CFLAGS "-fPIC"
|
||||
#define DEFINES "-DMY_DEBUG=0", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", \
|
||||
"-DGEBS_ENABLE_PTHREAD_FEATURES"
|
||||
#define EXTRA_SOURCES "./cJSON/cJSON.c", "./zip/src/zip.c"
|
||||
#endif
|
||||
#define SOURCES "./main.c", "./routes.c", "./baked.c", "./timer.c"
|
||||
#define OBJECTS "./mongoose.o"
|
||||
#define LINK_FLAGS "-Wl,-z,execstack", "-lpthread"
|
||||
#define INC_FLAGS "-I.", "-I./zip/src"
|
||||
|
||||
CMD(CC, TARGET, CFLAGS, DEFINES, INC_FLAGS, SOURCES, OBJECTS, EXTRA_SOURCES, LINK_FLAGS);
|
||||
|
||||
RULE("./compile_flags.txt") {
|
||||
make_compile_flags(TARGET, CFLAGS, DEFINES, INC_FLAGS, SOURCES, OBJECTS, EXTRA_SOURCES, LINK_FLAGS);
|
||||
}
|
||||
|
||||
#undef CC
|
||||
#undef TARGET
|
||||
#undef CFLAGS
|
||||
#undef DEFINES
|
||||
#undef EXTRA_SOURCES
|
||||
#undef SOURCES
|
||||
#undef OBJECTS
|
||||
#undef LINK_FLAGS
|
||||
#undef INC_FLAGS
|
||||
}
|
||||
} else if (strcmp(cmd, "clean") == 0) {
|
||||
remove1("./build");
|
||||
remove1("./aboba");
|
||||
remove("./mongoose.o");
|
||||
remove("./gpp1");
|
||||
remove("./watcher");
|
||||
remove1("./mongoose.o");
|
||||
remove1("./gpp1");
|
||||
remove1("./watcher");
|
||||
remove1("./compile_flags.txt");
|
||||
remove1("./bundle.zip");
|
||||
remove1("./commit.h");
|
||||
} else if (strcmp(cmd, "make-watcher") == 0) {
|
||||
CMD("cc", "-o", "watcher", "watcher.c");
|
||||
} else if (strcmp(cmd, "watch") == 0) {
|
||||
|
@ -1,11 +1,5 @@
|
||||
#ifndef CLONESTR_H_
|
||||
#define CLONESTR_H_
|
||||
|
||||
#define clonestr(str) \
|
||||
({ \
|
||||
char *__p = malloc(strlen((str))+1); \
|
||||
strcpy(__p, (str)); \
|
||||
(__p); \
|
||||
})
|
||||
|
||||
#endif // CLONESTR_H_
|
||||
|
BIN
etc/apwz-xkcd1.png
Normal file
BIN
etc/apwz-xkcd1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
BIN
etc/apwz-zip-icon.png
Normal file
BIN
etc/apwz-zip-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
1244
etc/highlight.js
Normal file
1244
etc/highlight.js
Normal file
File diff suppressed because one or more lines are too long
1
etc/hljs-rainbow.css
Normal file
1
etc/hljs-rainbow.css
Normal file
@ -0,0 +1 @@
|
||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#474949;color:#d1d9e1}.hljs-comment,.hljs-quote{color:#969896;font-style:italic}.hljs-addition,.hljs-keyword,.hljs-literal,.hljs-selector-tag,.hljs-type{color:#c9c}.hljs-number,.hljs-selector-attr,.hljs-selector-pseudo{color:#f99157}.hljs-doctag,.hljs-regexp,.hljs-string{color:#8abeb7}.hljs-built_in,.hljs-name,.hljs-section,.hljs-title{color:#b5bd68}.hljs-class .hljs-title,.hljs-selector-id,.hljs-template-variable,.hljs-title.class_,.hljs-variable{color:#fc6}.hljs-name,.hljs-section,.hljs-strong{font-weight:700}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-subst,.hljs-symbol{color:#f99157}.hljs-deletion{color:#dc322f}.hljs-formula{background:#eee8d5}.hljs-attr,.hljs-attribute{color:#81a2be}.hljs-emphasis{font-style:italic}
|
@ -18,41 +18,24 @@ async function fetch_build_id()
|
||||
return json.build_id;
|
||||
}
|
||||
|
||||
(async function() {
|
||||
build_id = await fetch_build_id();
|
||||
})();
|
||||
function refresh_page()
|
||||
{
|
||||
let scroll = localStorage.setItem("scroll", window.scrollY);
|
||||
location.reload();
|
||||
}
|
||||
|
||||
|
||||
const asyncIntervals = [];
|
||||
|
||||
const runAsyncInterval = async (cb, interval, intervalIndex) => {
|
||||
await cb();
|
||||
if (asyncIntervals[intervalIndex]) {
|
||||
setTimeout(() => runAsyncInterval(cb, interval, intervalIndex), interval);
|
||||
}
|
||||
};
|
||||
|
||||
const setAsyncInterval = (cb, interval) => {
|
||||
if (cb && typeof cb === "function") {
|
||||
const intervalIndex = asyncIntervals.length;
|
||||
asyncIntervals.push(true);
|
||||
runAsyncInterval(cb, interval, intervalIndex);
|
||||
return intervalIndex;
|
||||
} else {
|
||||
throw new Error('Callback must be a function');
|
||||
}
|
||||
};
|
||||
|
||||
const clearAsyncInterval = (intervalIndex) => {
|
||||
if (asyncIntervals[intervalIndex]) {
|
||||
asyncIntervals[intervalIndex] = false;
|
||||
}
|
||||
};
|
||||
|
||||
setAsyncInterval(async function () {
|
||||
let new_build_id = await fetch_build_id();
|
||||
if (build_id !== new_build_id) {
|
||||
location.reload(true);
|
||||
window.onload = function()
|
||||
{
|
||||
setInterval(async function() {
|
||||
let new_build_id = await fetch_build_id();
|
||||
if (build_id !== new_build_id) {
|
||||
refresh_page();
|
||||
}
|
||||
}, 1000);
|
||||
let scroll = localStorage.getItem("scroll");
|
||||
if (scroll) {
|
||||
window.scrollTo(0, scroll);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
|
||||
|
69
etc/marked.js
Normal file
69
etc/marked.js
Normal file
File diff suppressed because one or more lines are too long
BIN
etc/me.jpg
Normal file
BIN
etc/me.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
1
etc/simple.css
Normal file
1
etc/simple.css
Normal file
File diff suppressed because one or more lines are too long
1
etc/simple.min.css
vendored
1
etc/simple.min.css
vendored
File diff suppressed because one or more lines are too long
131
etc/theme.js
Normal file
131
etc/theme.js
Normal file
@ -0,0 +1,131 @@
|
||||
if (!localStorage.getItem("theme")) {
|
||||
localStorage.setItem("theme", "light-breeze");
|
||||
}
|
||||
|
||||
function plan9_acme()
|
||||
{
|
||||
document.documentElement.style.setProperty("--bg", "#FFFFEC");
|
||||
document.documentElement.style.setProperty("--accent-bg", "#EEEEA7");
|
||||
document.documentElement.style.setProperty("--text", "#424242");
|
||||
document.documentElement.style.setProperty("--text-light", "#999957");
|
||||
document.documentElement.style.setProperty("--border", "#B7B19C");
|
||||
document.documentElement.style.setProperty("--accent", "#030093");
|
||||
document.documentElement.style.setProperty("--accent-hover", "#2A8DC5");
|
||||
document.documentElement.style.setProperty("--accent-text", "var(--bg)");
|
||||
document.documentElement.style.setProperty("--code", "#57864E");
|
||||
document.documentElement.style.setProperty("--preformatted", "#424242");
|
||||
document.documentElement.style.setProperty("--marked", "#B85C57");
|
||||
document.documentElement.style.setProperty("--disabled", "#EAEBDB");
|
||||
}
|
||||
|
||||
function light_breeze()
|
||||
{
|
||||
document.documentElement.style.setProperty("--bg", "#F0F2F5");
|
||||
document.documentElement.style.setProperty("--accent-bg", "#E3E6EA");
|
||||
document.documentElement.style.setProperty("--text", "#2C2C2C");
|
||||
document.documentElement.style.setProperty("--text-light", "#9A9A9A");
|
||||
document.documentElement.style.setProperty("--border", "#D1D7DC");
|
||||
document.documentElement.style.setProperty("--accent", "#3454D1");
|
||||
document.documentElement.style.setProperty("--accent-hover", "#6A8ED8");
|
||||
document.documentElement.style.setProperty("--accent-text", "var(--bg)");
|
||||
document.documentElement.style.setProperty("--code", "#3C9E47");
|
||||
document.documentElement.style.setProperty("--preformatted", "#2C2C2C");
|
||||
document.documentElement.style.setProperty("--marked", "#D6674B");
|
||||
document.documentElement.style.setProperty("--disabled", "#F1F3F6");
|
||||
}
|
||||
|
||||
function bold_navy()
|
||||
{
|
||||
document.documentElement.style.setProperty("--bg", "#F0F4FF");
|
||||
document.documentElement.style.setProperty("--accent-bg", "#E0E6F1");
|
||||
document.documentElement.style.setProperty("--text", "#2D3A58");
|
||||
document.documentElement.style.setProperty("--text-light", "#7A8C9E");
|
||||
document.documentElement.style.setProperty("--border", "#BCC6D8");
|
||||
document.documentElement.style.setProperty("--accent", "#1E2A5B");
|
||||
document.documentElement.style.setProperty("--accent-hover", "#2A3F80");
|
||||
document.documentElement.style.setProperty("--accent-text", "var(--bg)");
|
||||
document.documentElement.style.setProperty("--code", "#4A7C2F");
|
||||
document.documentElement.style.setProperty("--preformatted", "#2D3A58");
|
||||
document.documentElement.style.setProperty("--marked", "#F18F5D");
|
||||
document.documentElement.style.setProperty("--disabled", "#E8ECF5");
|
||||
}
|
||||
|
||||
function soft_lavander()
|
||||
{
|
||||
document.documentElement.style.setProperty("--bg", "#F5F2FF");
|
||||
document.documentElement.style.setProperty("--accent-bg", "#E1D1FF");
|
||||
document.documentElement.style.setProperty("--text", "#3D2B6F");
|
||||
document.documentElement.style.setProperty("--text-light", "#8D8B9B");
|
||||
document.documentElement.style.setProperty("--border", "#C1B5D6");
|
||||
document.documentElement.style.setProperty("--accent", "#7A4FE3");
|
||||
document.documentElement.style.setProperty("--accent-hover", "#8D6DE8");
|
||||
document.documentElement.style.setProperty("--accent-text", "var(--bg)");
|
||||
document.documentElement.style.setProperty("--code", "#5D7C42");
|
||||
document.documentElement.style.setProperty("--preformatted", "#3D2B6F");
|
||||
document.documentElement.style.setProperty("--marked", "#F77A63");
|
||||
document.documentElement.style.setProperty("--disabled", "#E9E4FF");
|
||||
}
|
||||
|
||||
function neon_cyber()
|
||||
{
|
||||
document.documentElement.style.setProperty("--bg", "#121212");
|
||||
document.documentElement.style.setProperty("--accent-bg", "#1C1C1C");
|
||||
document.documentElement.style.setProperty("--text", "#00FF00");
|
||||
document.documentElement.style.setProperty("--text-light", "#A1FF3D");
|
||||
document.documentElement.style.setProperty("--border", "#006400");
|
||||
document.documentElement.style.setProperty("--accent", "#FF007F");
|
||||
document.documentElement.style.setProperty("--accent-hover", "#FF3399");
|
||||
document.documentElement.style.setProperty("--accent-text", "var(--bg)");
|
||||
document.documentElement.style.setProperty("--code", "#2AFF32");
|
||||
document.documentElement.style.setProperty("--preformatted", "#00FF00");
|
||||
document.documentElement.style.setProperty("--marked", "#FF6F00");
|
||||
document.documentElement.style.setProperty("--disabled", "#333333");
|
||||
}
|
||||
|
||||
function dark_easy()
|
||||
{
|
||||
document.documentElement.style.setProperty("--bg", "#1A1A1A");
|
||||
document.documentElement.style.setProperty("--accent-bg", "#2E2E2E");
|
||||
document.documentElement.style.setProperty("--text", "#DCDCDC");
|
||||
document.documentElement.style.setProperty("--text-light", "#A3A3A3");
|
||||
document.documentElement.style.setProperty("--border", "#585858");
|
||||
document.documentElement.style.setProperty("--accent", "#FF5722");
|
||||
document.documentElement.style.setProperty("--accent-hover", "#F4511E");
|
||||
document.documentElement.style.setProperty("--accent-text", "#1A1A1A");
|
||||
document.documentElement.style.setProperty("--code", "#8BC34A");
|
||||
document.documentElement.style.setProperty("--preformatted", "#A3A3A3");
|
||||
document.documentElement.style.setProperty("--marked", "#FF7043");
|
||||
document.documentElement.style.setProperty("--disabled", "#383838");
|
||||
}
|
||||
|
||||
const themes = {
|
||||
"light-breeze": light_breeze,
|
||||
"plan9-acme": plan9_acme,
|
||||
"bold-navy": bold_navy,
|
||||
"soft-lavander": soft_lavander,
|
||||
"neon-cyber": neon_cyber,
|
||||
"dark-easy": dark_easy,
|
||||
};
|
||||
|
||||
|
||||
const footer = document.getElementsByTagName("footer")[0];
|
||||
const select = document.createElement("select");
|
||||
select.id = "theme-picker";
|
||||
footer.children[0].appendChild(select);
|
||||
for (const prop in themes) {
|
||||
const option = document.createElement("option");
|
||||
option.value = prop;
|
||||
option.innerHTML = prop;
|
||||
if (prop == localStorage.getItem("theme")) {
|
||||
option.selected = true;
|
||||
}
|
||||
select.appendChild(option);
|
||||
}
|
||||
|
||||
const theme_picker = document.getElementById("theme-picker");
|
||||
theme_picker.addEventListener("change", function (e) {
|
||||
const theme_name = e.target.value;
|
||||
localStorage.setItem("theme", theme_name);
|
||||
themes[localStorage.getItem("theme")]()
|
||||
});
|
||||
themes[localStorage.getItem("theme")]()
|
BIN
etc/tmoa-engine.jpg
Normal file
BIN
etc/tmoa-engine.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 90 KiB |
BIN
etc/tmoa-garbage.jpg
Normal file
BIN
etc/tmoa-garbage.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 230 KiB |
2
gebs
2
gebs
Submodule gebs updated: d8e1d54f3d...db8389f7d5
12
locked.h
Normal file
12
locked.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef LOCKED_H_
|
||||
#define LOCKED_H_
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#define locked_init(x) { .value = x, .lock = PTHREAD_MUTEX_INITIALIZER }
|
||||
#define locked(T) struct { T value; pthread_mutex_t lock; }
|
||||
|
||||
#define lockx(x) pthread_mutex_lock(&(x)->lock)
|
||||
#define unlockx(x) pthread_mutex_unlock(&(x)->lock)
|
||||
|
||||
#endif // LOCKED_H_
|
268
main.c
268
main.c
@ -1,23 +1,47 @@
|
||||
#include <libgen.h>
|
||||
#include <pthread.h>
|
||||
#include <ftw.h>
|
||||
#include <signal.h>
|
||||
#include <sys/sendfile.h>
|
||||
|
||||
#define GEBS_IMPLEMENTATION
|
||||
#include "gebs/gebs.h"
|
||||
#include "mongoose/mongoose.h"
|
||||
#define STB_DS_IMPLEMENTATION
|
||||
#include "stb/stb_ds.h"
|
||||
#include "md5-c/md5.h"
|
||||
|
||||
#include "routes.h"
|
||||
#include "baked.h"
|
||||
#include "timer.h"
|
||||
#include "CONFIG.h"
|
||||
#include "locked.h"
|
||||
|
||||
Route *route_hashtable = NULL;
|
||||
static locked(Route *) route_hashtable = locked_init(nil);
|
||||
static locked(char *) baked_dump_path = locked_init(nil);
|
||||
|
||||
char *clonestr_alloc(Allocator *alloc, char *s)
|
||||
{
|
||||
char *p = gebs_malloc(alloc, strlen(s)+1);
|
||||
strcpy(p, s);
|
||||
return p;
|
||||
}
|
||||
|
||||
void *zmalloc_alloc(Allocator *alloc, size_t size)
|
||||
{
|
||||
void *p = gebs_malloc(alloc, size);
|
||||
memset(p, 0, size);
|
||||
return p;
|
||||
}
|
||||
|
||||
void run_in_thread(void *(*f)(void *), void *p)
|
||||
{
|
||||
pthread_t tid = 0;
|
||||
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
|
||||
pthread_create(&tid, &attr, f, p);
|
||||
pthread_attr_destroy(&attr);
|
||||
}
|
||||
@ -25,94 +49,260 @@ void run_in_thread(void *(*f)(void *), void *p)
|
||||
void *route_thread_function(void *param)
|
||||
{
|
||||
Route_Thread_Data *data = (Route_Thread_Data *)param;
|
||||
Arena *arena = data->arena;
|
||||
|
||||
struct mg_http_message http_msg = {0};
|
||||
int r = mg_http_parse(data->message.buf, data->message.len, &http_msg);
|
||||
if (r <= 0) {
|
||||
Route_Result result = {0};
|
||||
result.status_code = 400;
|
||||
Route_Result *result = zmalloc_alloc((Allocator *)arena, sizeof(*result));
|
||||
result->arena = arena;
|
||||
result->status_code = 400;
|
||||
result->type = ROUTE_RESULT_DYNAMIC;
|
||||
sb_append_nstr_alloc(arena, &result->body, "Could not parse HTTP request");
|
||||
sb_finish_alloc(arena, &result->body);
|
||||
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
|
||||
return nil;
|
||||
free(data->message.buf);
|
||||
free(data);
|
||||
pthread_exit(nil);
|
||||
} else {
|
||||
if (mg_match(http_msg.uri, mg_str("/bakedres/*"), nil)) {
|
||||
Route_Result *result = zmalloc_alloc((Allocator *)arena, sizeof(*result));
|
||||
result->arena = arena;
|
||||
result->type = ROUTE_RESULT_STATIC;
|
||||
|
||||
char *p = gebs_malloc(arena, http_msg.uri.len+1);
|
||||
strncpy(p, http_msg.uri.buf, http_msg.uri.len);
|
||||
p[http_msg.uri.len] = 0;
|
||||
|
||||
char *file = basename(p);
|
||||
char *full_path = gebs_malloc(arena, PATH_MAX);
|
||||
|
||||
lockx(&baked_dump_path);
|
||||
snprintf(full_path, PATH_MAX, "%s/%s", baked_dump_path.value, file);
|
||||
unlockx(&baked_dump_path);
|
||||
|
||||
sb_append_nstr_alloc(arena, &result->body, full_path);
|
||||
sb_finish_alloc(arena, &result->body);
|
||||
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
|
||||
free(data->message.buf);
|
||||
free(data);
|
||||
pthread_exit(nil);
|
||||
} else {
|
||||
char key[MG_PATH_MAX] = {0};
|
||||
strncpy(key, http_msg.uri.buf, http_msg.uri.len);
|
||||
lockx(&route_hashtable);
|
||||
ssize_t idx = shgeti(route_hashtable.value, key);
|
||||
|
||||
Route_Result *result = zmalloc_alloc((Allocator *)arena, sizeof(*result));
|
||||
result->arena = arena;
|
||||
route_hashtable.value[idx].value((Allocator *)arena, &http_msg, result, route_hashtable.value[idx].context_data);
|
||||
unlockx(&route_hashtable);
|
||||
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
|
||||
|
||||
free(data->message.buf);
|
||||
free(data);
|
||||
pthread_exit(nil);
|
||||
}
|
||||
}
|
||||
|
||||
char key[MG_PATH_MAX] = {0};
|
||||
strncpy(key, http_msg.uri.buf, http_msg.uri.len);
|
||||
Route_Handler handler = shget(route_hashtable, key);
|
||||
|
||||
Route_Result result = {0};
|
||||
handler(&http_msg, &result);
|
||||
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
|
||||
|
||||
free(data->message.buf);
|
||||
free(data);
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
void event_handler(struct mg_connection *conn, int ev, void *ev_data)
|
||||
{
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
LOGI("HTTP EVENT\n");
|
||||
struct mg_http_message *msg = (struct mg_http_message *)ev_data;
|
||||
LOGI("Route: %.*s\n", msg->uri.len, msg->uri.buf);
|
||||
|
||||
Route_Thread_Data *data = calloc(1, sizeof(*data));
|
||||
data->message = mg_strdup(msg->message);
|
||||
data->conn_id = conn->id;
|
||||
data->mgr = conn->mgr;
|
||||
data->arena = malloc(sizeof(*data->arena));
|
||||
*data->arena = arena_get();
|
||||
LOGI("starting handler thread...\n");
|
||||
run_in_thread(&route_thread_function, data);
|
||||
} else if (ev == MG_EV_WAKEUP) {
|
||||
LOGI("WAKEUP EVENT\n");
|
||||
struct mg_str *data = (struct mg_str *)ev_data;
|
||||
Route_Result *result = (Route_Result *)data->buf;
|
||||
defer {
|
||||
for (size_t i = 0; i < result->headers.count; i++) {
|
||||
free(result->headers.items[i]);
|
||||
}
|
||||
list_free(&result->headers);
|
||||
sb_free(&result->body);
|
||||
}
|
||||
Route_Result *result = *(Route_Result **)data->buf;
|
||||
Arena *arena = result->arena;
|
||||
|
||||
Gebs_String_Builder sb = {0};
|
||||
defer { sb_free(&sb); }
|
||||
nsl_join(&result->headers, &sb, "\r\n");
|
||||
nsl_join_alloc(arena, &result->headers, &sb, "\r\n");
|
||||
if (result->headers.count > 0) {
|
||||
sb_append_nstr(&sb, "\r\n");
|
||||
sb_append_nstr_alloc(arena, &sb, "\r\n");
|
||||
}
|
||||
sb_finish(&sb);
|
||||
sb_finish_alloc(arena, &sb);
|
||||
|
||||
mg_http_reply(conn, result->status_code, sb.items, "%s", result->body.items);
|
||||
if (result->type == ROUTE_RESULT_DYNAMIC) {
|
||||
mg_http_reply(conn, result->status_code, sb.items, "%s", result->body.items);
|
||||
} else if (result->type == ROUTE_RESULT_STATIC) {
|
||||
char *path = result->body.items;
|
||||
mg_http_serve_file(conn, ev_data, path, &(struct mg_http_serve_opts){0});
|
||||
}
|
||||
|
||||
arena_destroy(arena);
|
||||
free(arena);
|
||||
|
||||
LOGI("WAKEUP done, arena cleaned\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void route_hashtable_put_blogs(Baked_Resource *resource, void *udata)
|
||||
{
|
||||
if ((strlen(resource->key) >= strlen("blog-"))
|
||||
&& strncmp(resource->key, "blog-", strlen("blog-")) == 0) {
|
||||
char *path = malloc(MG_PATH_MAX);
|
||||
snprintf(path, MG_PATH_MAX, "/%s", resource->key);
|
||||
shput(route_hashtable.value, path, &route_generic_blog);
|
||||
route_hashtable.value[shgeti(route_hashtable.value, path)].context_data = (void *)resource;
|
||||
}
|
||||
}
|
||||
|
||||
void init_route_hashtable(void)
|
||||
{
|
||||
shdefault(route_hashtable, &route_page_not_found);
|
||||
shput(route_hashtable, "/page-missing", &route_page_not_found);
|
||||
shput(route_hashtable, "/simple.min.css", &route_simple_css);
|
||||
shput(route_hashtable, "/favicon.ico", &route_favicon);
|
||||
shput(route_hashtable, "/hotreload.js", &route_hotreload_js);
|
||||
shput(route_hashtable, "/", &route_home);
|
||||
shput(route_hashtable, "/build-id", &route_build_id);
|
||||
shdefault(route_hashtable.value, &route_page_not_found);
|
||||
shput(route_hashtable.value, clonestr_alloc(&default_allocator, "/"), &route_home);
|
||||
shput(route_hashtable.value, clonestr_alloc(&default_allocator, "/page-missing"), &route_page_not_found);
|
||||
shput(route_hashtable.value, clonestr_alloc(&default_allocator, "/blog"), &route_blog);
|
||||
#if MY_DEBUG
|
||||
shput(route_hashtable.value, clonestr_alloc(&default_allocator, "/build-id"), &route_build_id);
|
||||
#endif
|
||||
|
||||
baked_resource_each(&route_hashtable_put_blogs, nil);
|
||||
}
|
||||
|
||||
void free_route_hashtable(void)
|
||||
{
|
||||
for (size_t i = 0; i < shlen(route_hashtable.value); i++) {
|
||||
free(route_hashtable.value[i].key);
|
||||
}
|
||||
shfree(route_hashtable.value);
|
||||
}
|
||||
|
||||
char *init_baked_dump(void)
|
||||
{
|
||||
char template[] = "/tmp/aboba-bakedres.XXXXXX";
|
||||
char *baked_dump1 = mkdtemp(template);
|
||||
char *baked_dump = malloc(strlen(baked_dump1)+1);
|
||||
strcpy(baked_dump, baked_dump1);
|
||||
|
||||
if (baked_dump == nil) {
|
||||
LOGE("Could not create bakedres dump\n");
|
||||
return nil;
|
||||
}
|
||||
LOGI("bakedres dump dir is %s\n", baked_dump);
|
||||
|
||||
return baked_dump;
|
||||
}
|
||||
|
||||
int baked_dump_rm_cb(const char *path,
|
||||
discard const struct stat *st,
|
||||
discard int type,
|
||||
discard struct FTW *ftw)
|
||||
{
|
||||
return remove(path);
|
||||
}
|
||||
|
||||
bool free_baked_dump(char *baked_dump)
|
||||
{
|
||||
LOGI("Removing bakedres dump %s\n", baked_dump);
|
||||
bool ok = nftw(baked_dump, baked_dump_rm_cb, FOPEN_MAX, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) != -1;
|
||||
if (!ok) {
|
||||
LOGE("Could not remove %s\n", baked_dump);
|
||||
}
|
||||
free(baked_dump);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/2180079/how-can-i-copy-a-file-on-unix-using-c
|
||||
int cp(const char* source, const char* destination)
|
||||
{
|
||||
int result = 0;
|
||||
int input, output;
|
||||
if ((input = open(source, O_RDONLY)) == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if ((output = creat(destination, 0660)) == -1)
|
||||
{
|
||||
close(input);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// sendfile will work with non-socket output (i.e. regular file) under
|
||||
// Linux 2.6.33+ and some other unixy systems.
|
||||
struct stat file_stat = {0};
|
||||
result = fstat(input, &file_stat);
|
||||
off_t copied = 0;
|
||||
while (result == 0 && copied < file_stat.st_size) {
|
||||
ssize_t written = sendfile(output, input, &copied, SSIZE_MAX);
|
||||
copied += written;
|
||||
if (written == -1) {
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
|
||||
close(input);
|
||||
close(output);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void copy_baked_resources_to_baked_dump(Baked_Resource *resource, void *udata)
|
||||
{
|
||||
char *baked_dump = (char *)udata;
|
||||
char path[PATH_MAX];
|
||||
get_baked_resource_path(resource->key, path, sizeof(path));
|
||||
char dest[PATH_MAX];
|
||||
snprintf(dest, sizeof(dest), "%s/%s", baked_dump, resource->key);
|
||||
cp(path, dest);
|
||||
}
|
||||
|
||||
void populate_baked_dump(char *baked_dump)
|
||||
{
|
||||
baked_resource_each(©_baked_resources_to_baked_dump, baked_dump);
|
||||
}
|
||||
|
||||
volatile sig_atomic_t alive = true;
|
||||
|
||||
void graceful_shutdown(int no) { alive = false; }
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = &graceful_shutdown;
|
||||
sa.sa_flags = 0;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sigaction(SIGINT, &sa, nil);
|
||||
|
||||
start_timer();
|
||||
|
||||
init_baked_resources();
|
||||
if ((baked_dump_path.value = init_baked_dump()) == nil) {
|
||||
return 1;
|
||||
}
|
||||
populate_baked_dump(baked_dump_path.value);
|
||||
|
||||
mg_log_set(MG_LL_DEBUG);
|
||||
struct mg_mgr mgr;
|
||||
mg_mgr_init(&mgr);
|
||||
init_route_hashtable();
|
||||
defer { free_route_hashtable(); }
|
||||
|
||||
mg_wakeup_init(&mgr);
|
||||
mg_http_listen(&mgr, "http://localhost:8080", &event_handler, NULL);
|
||||
mg_http_listen(&mgr, CONFIG_LISTEN_URL, &event_handler, NULL);
|
||||
|
||||
for (;;) {
|
||||
while (alive) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
scratch_arena_reset();
|
||||
}
|
||||
|
||||
mg_mgr_free(&mgr);
|
||||
shfree(route_hashtable);
|
||||
|
||||
free_baked_dump(baked_dump_path.value);
|
||||
free_baked_resources();
|
||||
|
||||
return 0;
|
||||
|
340
routes.c
340
routes.c
@ -1,77 +1,84 @@
|
||||
#include "gebs/gebs.h"
|
||||
#include "mongoose/mongoose.h"
|
||||
#include "cJSON/cJSON.h"
|
||||
#if MY_DEBUG
|
||||
#include "md5-c/md5.h"
|
||||
#endif
|
||||
|
||||
#include "routes.h"
|
||||
#include "baked.h"
|
||||
#include "clonestr.h"
|
||||
#include "commit.h"
|
||||
#include "timer.h"
|
||||
|
||||
extern char *clonestr_alloc(Allocator *alloc, char *s);
|
||||
|
||||
#define INTERNAL_SERVER_ERROR_MSG "Internal server error ;(. Try again later..."
|
||||
|
||||
void make_internal_server_error(Route_Result *result)
|
||||
{
|
||||
result->status_code = 500;
|
||||
list_append(&result->headers, "Content-Type: text/plain");
|
||||
sb_append_nstr(&result->body, INTERNAL_SERVER_ERROR_MSG);
|
||||
sb_finish(&result->body);
|
||||
}
|
||||
#define META_TAGS \
|
||||
"<meta name=\"description\" content=\"kamkow1's personal website and blog. Focused on C programming, weight lifting and personal thoughts.\" />" \
|
||||
"<meta name=\"keywords\" content=\"C, C Programming, Blog, kamkow1, Linux\" />" \
|
||||
"<meta name=\"author\" content=\"kamkow1\">" \
|
||||
"<meta name=\"application-name\" content=\"aboba\">"
|
||||
|
||||
void make_application_json(Route_Result *result, int code, cJSON *root)
|
||||
void make_application_json(Allocator *alloc, Route_Result *result, int code, cJSON *root)
|
||||
{
|
||||
char *root_text = cJSON_PrintUnformatted(root);
|
||||
defer { free(root_text); }
|
||||
size_t buf_size = 512;
|
||||
char *buf = gebs_malloc(alloc, buf_size);
|
||||
|
||||
cJSON_PrintPreallocated(root, buf, buf_size, false);
|
||||
|
||||
result->status_code = code;
|
||||
list_append(&result->headers, clonestr("Content-Type: application/json"));
|
||||
sb_append_nstr(&result->body, root_text);
|
||||
sb_finish(&result->body);
|
||||
result->type = ROUTE_RESULT_DYNAMIC;
|
||||
list_append_alloc(alloc, &result->headers, clonestr_alloc(alloc, "Content-Type: application/json"));
|
||||
sb_append_nstr_alloc(alloc, &result->body, buf);
|
||||
sb_finish_alloc(alloc, &result->body);
|
||||
}
|
||||
|
||||
void make_image_xicon(Route_Result *result, int code, char *icon)
|
||||
void make_text(Allocator *alloc, Route_Result *result, char *subtype, int code, char *in)
|
||||
{
|
||||
result->status_code = code;
|
||||
list_append(&result->headers, clonestr("Content-Type: image/x-icon"));
|
||||
sb_append_nstr(&result->body, icon);
|
||||
result->type = ROUTE_RESULT_DYNAMIC;
|
||||
list_append_alloc(alloc, &result->headers,
|
||||
clonestr_alloc(alloc, fmt("Content-Type: text/%s", subtype)));
|
||||
sb_append_nstr_alloc(alloc, &result->body, in);
|
||||
sb_finish(&result->body);
|
||||
}
|
||||
|
||||
void make_text(Route_Result *result, char *subtype, int code, char *in)
|
||||
void make_internal_server_error(Allocator *alloc, Route_Result *result)
|
||||
{
|
||||
char type[100];
|
||||
snprintf(type, sizeof(type), "Content-Type: text/%s", subtype);
|
||||
|
||||
result->status_code = code;
|
||||
list_append(&result->headers, clonestr(type));
|
||||
sb_append_nstr(&result->body, in);
|
||||
sb_finish(&result->body);
|
||||
make_text(alloc, result, "plain", 500, INTERNAL_SERVER_ERROR_MSG);
|
||||
}
|
||||
|
||||
bool gpp_run(char *path, NString_List *env, String_Builder *out)
|
||||
bool gpp_run(Allocator *alloc, char *path, NString_List *env, String_Builder *out)
|
||||
{
|
||||
Cmd cmd = {0};
|
||||
defer { cmd_free(&cmd); }
|
||||
|
||||
char gpp1[PATH_MAX];
|
||||
if (!get_baked_resource_path("gpp1", gpp1, sizeof(gpp1))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cmd_append(&cmd, gpp1);
|
||||
cmd_append(&cmd, "-H");
|
||||
cmd_append(&cmd, "-x");
|
||||
cmd_append(&cmd, "--nostdinc");
|
||||
cmd_append(&cmd, path);
|
||||
cmd_append_alloc(alloc, &cmd, gpp1);
|
||||
cmd_append_alloc(alloc, &cmd, "-H");
|
||||
cmd_append_alloc(alloc, &cmd, "-x");
|
||||
cmd_append_alloc(alloc, &cmd, "--nostdinc");
|
||||
cmd_append_alloc(alloc, &cmd, path);
|
||||
|
||||
for (size_t i = 0; i < env->count; i++) {
|
||||
cmd_append(&cmd, env->items[i]);
|
||||
cmd_append_alloc(alloc, &cmd, env->items[i]);
|
||||
}
|
||||
|
||||
return cmd_run_collect(&cmd, out) == 0;
|
||||
bool ok = cmd_run_collect_alloc(alloc, &cmd, out) == 0;
|
||||
size_t len = out->count > 100 ? 100 : out->count;
|
||||
LOGI("Output (first 100): \n-------------------------------\n%.*s\n-------------------------------\n", len, out->items);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void route_build_id(struct mg_http_message *msg, Route_Result *result)
|
||||
#if MY_DEBUG
|
||||
void route_build_id(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
|
||||
{
|
||||
LOGI("handler build_id (%.*s)\n", msg->uri.len, msg->uri.buf);
|
||||
// TODO: Somehow use our arena in here. This will require a slight rework of the way cJSON works.
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
defer { cJSON_Delete(root); }
|
||||
|
||||
@ -79,134 +86,217 @@ void route_build_id(struct mg_http_message *msg, Route_Result *result)
|
||||
uchar md5_buf[16];
|
||||
md5String(time, md5_buf);
|
||||
String_Builder sb = {0};
|
||||
defer { sb_free(&sb); }
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
char tmp[3];
|
||||
snprintf(tmp, sizeof(tmp), "%02x", md5_buf[i]);
|
||||
sb_append_nstr(&sb, tmp);
|
||||
sb_append_nstr_alloc(alloc, &sb, clonestr_alloc(alloc, fmt("%02x", md5_buf[i])));
|
||||
}
|
||||
sb_finish(&sb);
|
||||
sb_finish_alloc(alloc, &sb);
|
||||
|
||||
cJSON_AddItemToObject(root, "build_id", cJSON_CreateString(sb.items));
|
||||
|
||||
make_application_json(result, 200, root);
|
||||
make_application_json(alloc, result, 200, root);
|
||||
LOGI("handler build_id done\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
void route_page_not_found(struct mg_http_message *msg, Route_Result *result)
|
||||
void route_page_not_found(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
|
||||
{
|
||||
LOGI("handler page not found (%.*s)\n", msg->uri.len, msg->uri.buf);
|
||||
NString_List env = {0};
|
||||
defer { list_free(&env); }
|
||||
|
||||
list_append(&env, fmt("-DURL=%.*s", msg->uri.len, msg->uri.buf));
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DURL=%.*s", msg->uri.len, msg->uri.buf)));
|
||||
|
||||
String_Builder out = {0};
|
||||
defer { sb_free(&out); }
|
||||
|
||||
char path[PATH_MAX] = {0};
|
||||
if (!get_baked_resource_path("page-missing.t", path, sizeof(path))) {
|
||||
make_internal_server_error(result);
|
||||
if (!get_baked_resource_path("page-missing.html", path, sizeof(path))) {
|
||||
LOGE("Failed to get baked resource page-missing.html\n");
|
||||
make_internal_server_error(alloc, result);
|
||||
return;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
list_append(&env, "-DHOTRELOAD=1");
|
||||
#else
|
||||
list_append(&env, "-DHOTRELOAD=0");
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DCOMMIT=%s", COMMIT_STRING)));
|
||||
char timer[100];
|
||||
get_timer_string(timer, sizeof(timer));
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DRUNNING_SINCE=%s", timer)));
|
||||
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DMETA_TAGS=%s", META_TAGS)));
|
||||
|
||||
#if MY_DEBUG
|
||||
list_append_alloc(alloc, &env, "-DHOTRELOAD");
|
||||
#endif
|
||||
|
||||
bool ok = gpp_run(path, &env, &out);
|
||||
sb_finish(&out);
|
||||
bool ok = gpp_run(alloc, path, &env, &out);
|
||||
sb_finish_alloc(alloc, &out);
|
||||
|
||||
if (!ok) {
|
||||
make_internal_server_error(result);
|
||||
make_internal_server_error(alloc, result);
|
||||
} else {
|
||||
make_text(result, "html", 200, out.items);
|
||||
make_text(alloc, result, "html", 200, out.items);
|
||||
}
|
||||
LOGI("handler page not found done\n");
|
||||
}
|
||||
|
||||
void route_simple_css(struct mg_http_message *msg, Route_Result *result)
|
||||
{
|
||||
char path[PATH_MAX] = {0};
|
||||
if (!get_baked_resource_path("simple.min.css", path, sizeof(path))) {
|
||||
make_internal_server_error(result);
|
||||
return;
|
||||
}
|
||||
|
||||
String_Builder sb = {0};
|
||||
defer { sb_free(&sb); }
|
||||
if (!sb_read_file(&sb, path)) {
|
||||
make_internal_server_error(result);
|
||||
return;
|
||||
}
|
||||
sb_finish(&sb);
|
||||
|
||||
make_text(result, "css", 200, sb.items);
|
||||
}
|
||||
|
||||
void route_favicon(struct mg_http_message *msg, Route_Result *result)
|
||||
{
|
||||
char path[PATH_MAX] = {0};
|
||||
if (!get_baked_resource_path("favicon.ico", path, sizeof(path))) {
|
||||
make_internal_server_error(result);
|
||||
return;
|
||||
}
|
||||
|
||||
String_Builder sb = {0};
|
||||
defer { sb_free(&sb); }
|
||||
if (!sb_read_file(&sb, path)) {
|
||||
make_internal_server_error(result);
|
||||
return;
|
||||
}
|
||||
sb_finish(&sb);
|
||||
|
||||
make_image_xicon(result, 200, sb.items);
|
||||
}
|
||||
|
||||
void route_hotreload_js(struct mg_http_message *msg, Route_Result *result)
|
||||
{
|
||||
char path[PATH_MAX] = {0};
|
||||
if (!get_baked_resource_path("hotreload.js", path, sizeof(path))) {
|
||||
make_internal_server_error(result);
|
||||
return;
|
||||
}
|
||||
|
||||
String_Builder sb = {0};
|
||||
defer { sb_free(&sb); }
|
||||
if (!sb_read_file(&sb, path)) {
|
||||
make_internal_server_error(result);
|
||||
return;
|
||||
}
|
||||
sb_finish(&sb);
|
||||
|
||||
make_text(result, "javascript", 200, sb.items);
|
||||
}
|
||||
|
||||
void route_home(struct mg_http_message *msg, Route_Result *result)
|
||||
void route_home(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
|
||||
{
|
||||
LOGI("handler home (%.*s)\n", msg->uri.len, msg->uri.buf);
|
||||
NString_List env = {0};
|
||||
defer { list_free(&env); }
|
||||
|
||||
String_Builder out = {0};
|
||||
defer { sb_free(&out); }
|
||||
|
||||
char path[PATH_MAX] = {0};
|
||||
if (!get_baked_resource_path("home.t", path, sizeof(path))) {
|
||||
make_internal_server_error(result);
|
||||
if (!get_baked_resource_path("home.html", path, sizeof(path))) {
|
||||
LOGE("Failed to get baked resource home.html\n");
|
||||
make_internal_server_error(alloc, result);
|
||||
return;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
list_append(&env, "-DHOTRELOAD=1");
|
||||
#else
|
||||
list_append(&env, "-DHOTRELOAD=0");
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DCOMMIT=%s", COMMIT_STRING)));
|
||||
char timer[100];
|
||||
get_timer_string(timer, sizeof(timer));
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DRUNNING_SINCE=%s", timer)));
|
||||
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DMETA_TAGS=%s", META_TAGS)));
|
||||
|
||||
#if MY_DEBUG
|
||||
list_append_alloc(alloc, &env, "-DHOTRELOAD");
|
||||
#endif
|
||||
|
||||
bool ok = gpp_run(path, &env, &out);
|
||||
sb_finish(&out);
|
||||
bool ok = gpp_run(alloc, path, &env, &out);
|
||||
sb_finish_alloc(alloc, &out);
|
||||
|
||||
if (!ok) {
|
||||
make_internal_server_error(result);
|
||||
make_internal_server_error(alloc, result);
|
||||
} else {
|
||||
make_text(result, "html", 200, out.items);
|
||||
make_text(alloc, result, "html", 200, out.items);
|
||||
}
|
||||
LOGI("handler home done\n");
|
||||
}
|
||||
|
||||
void route_generic_blog(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
|
||||
{
|
||||
LOGI("handler generic blog (%.*s)\n", msg->uri.len, msg->uri.buf);
|
||||
NString_List env = {0};
|
||||
|
||||
String_Builder out = {0};
|
||||
|
||||
Baked_Resource *resource = (Baked_Resource *)context_data;
|
||||
|
||||
char md_path[PATH_MAX] = {0};
|
||||
if (!get_baked_resource_path(resource->key, md_path, sizeof(md_path))) {
|
||||
LOGE("Failed to get baked resource %s\n", resource->key);
|
||||
make_internal_server_error(alloc, result);
|
||||
return;
|
||||
}
|
||||
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DCOMMIT=%s", COMMIT_STRING)));
|
||||
char timer[100];
|
||||
get_timer_string(timer, sizeof(timer));
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DRUNNING_SINCE=%s", timer)));
|
||||
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DMETA_TAGS=%s", META_TAGS)));
|
||||
|
||||
#if MY_DEBUG
|
||||
list_append_alloc(alloc, &env, "-DHOTRELOAD");
|
||||
#endif
|
||||
|
||||
String_Builder md = {0};
|
||||
if (!sb_read_file_alloc(alloc, &md, md_path)) {
|
||||
make_internal_server_error(alloc, result);
|
||||
return;
|
||||
}
|
||||
|
||||
String_Builder md_prepared = {0};
|
||||
for (size_t i = 0; i < md.count; i++) {
|
||||
char c = md.items[i];
|
||||
switch (c) {
|
||||
case '\"': sb_append_nstr_alloc(alloc, &md_prepared, "\\\""); break;
|
||||
case '\'': sb_append_nstr_alloc(alloc, &md_prepared, "\\\'"); break;
|
||||
case '\\': sb_append_nstr_alloc(alloc, &md_prepared, "\\\\"); break;
|
||||
case '\a': sb_append_nstr_alloc(alloc, &md_prepared, "\\a"); break;
|
||||
case '\b': sb_append_nstr_alloc(alloc, &md_prepared, "\\b"); break;
|
||||
case '\r': sb_append_nstr_alloc(alloc, &md_prepared, "\\r"); break;
|
||||
case '\n': sb_append_nstr_alloc(alloc, &md_prepared, "\\n"); break;
|
||||
case '\t': sb_append_nstr_alloc(alloc, &md_prepared, "\\t"); break;
|
||||
default:
|
||||
if (iscntrl(md.items[i])) {
|
||||
sb_append_nstr_alloc(alloc, &md_prepared, clonestr_alloc(alloc, fmt("\\%03o", c)));
|
||||
} else {
|
||||
sb_append_char_alloc(alloc, &md_prepared, c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
sb_finish_alloc(alloc, &md_prepared);
|
||||
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DBLOG_POST_TITLE=%s", resource->key)));
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DBLOG_POST_MARKDOWN=%s", md_prepared.items)));
|
||||
|
||||
char tmpl_file[PATH_MAX] = {0};
|
||||
if (!get_baked_resource_path("template-blog.html", tmpl_file, sizeof(tmpl_file))) {
|
||||
make_internal_server_error(alloc, result);
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok = gpp_run(alloc, tmpl_file, &env, &out);
|
||||
sb_finish_alloc(alloc, &out);
|
||||
|
||||
if (!ok) {
|
||||
make_internal_server_error(alloc, result);
|
||||
} else {
|
||||
make_text(alloc, result, "html", 200, out.items);
|
||||
}
|
||||
LOGI("handler generic blog done\n");
|
||||
}
|
||||
|
||||
void collect_blogs(Baked_Resource *resource, void *udata)
|
||||
{
|
||||
struct { Allocator *alloc; String_Builder *sb; } *ud = udata;
|
||||
if ((strlen(resource->key) >= strlen("blog-"))
|
||||
&& strncmp(resource->key, "blog-", strlen("blog-")) == 0) {
|
||||
sb_append_nstr_alloc(ud->alloc, ud->sb,
|
||||
clonestr_alloc(ud->alloc, fmt("<li><a href=\"/%s\">%s</a></li>", resource->key, resource->key)));
|
||||
}
|
||||
}
|
||||
|
||||
void route_blog(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
|
||||
{
|
||||
NString_List env = {0};
|
||||
|
||||
String_Builder out = {0};
|
||||
|
||||
String_Builder sb = {0};
|
||||
struct { Allocator *alloc; String_Builder *sb; } collect_blogs_data = {
|
||||
.alloc = alloc,
|
||||
.sb = &sb,
|
||||
};
|
||||
baked_resource_each(&collect_blogs, &collect_blogs_data);
|
||||
sb_finish_alloc(alloc, &sb);
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DBLOG_POSTS=%s", sb.items)));
|
||||
|
||||
char path[PATH_MAX] = {0};
|
||||
if (!get_baked_resource_path("blog.html", path, sizeof(path))) {
|
||||
make_internal_server_error(alloc, result);
|
||||
return;
|
||||
}
|
||||
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DCOMMIT=%s", COMMIT_STRING)));
|
||||
char timer[100];
|
||||
get_timer_string(timer, sizeof(timer));
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DRUNNING_SINCE=%s", timer)));
|
||||
|
||||
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DMETA_TAGS=%s", META_TAGS)));
|
||||
|
||||
#if MY_DEBUG
|
||||
list_append_alloc(alloc, &env, "-DHOTRELOAD");
|
||||
#endif
|
||||
|
||||
bool ok = gpp_run(alloc, path, &env, &out);
|
||||
sb_finish_alloc(alloc, &out);
|
||||
|
||||
if (!ok) {
|
||||
make_internal_server_error(alloc, result);
|
||||
} else {
|
||||
make_text(alloc, result, "html", 200, out.items);
|
||||
}
|
||||
}
|
||||
|
||||
|
22
routes.h
22
routes.h
@ -5,29 +5,37 @@
|
||||
#include "mongoose/mongoose.h"
|
||||
|
||||
typedef struct {
|
||||
enum {
|
||||
ROUTE_RESULT_DYNAMIC,
|
||||
ROUTE_RESULT_STATIC,
|
||||
} type;
|
||||
int status_code;
|
||||
NString_List headers;
|
||||
String_Builder body;
|
||||
Arena *arena;
|
||||
} Route_Result;
|
||||
|
||||
typedef struct {
|
||||
struct mg_mgr *mgr;
|
||||
ulong conn_id;
|
||||
struct mg_str message;
|
||||
Arena *arena;
|
||||
} Route_Thread_Data;
|
||||
|
||||
typedef void (*Route_Handler)(struct mg_http_message *msg, Route_Result *result);
|
||||
typedef void (*Route_Handler)(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
|
||||
|
||||
typedef struct {
|
||||
char *key; // path
|
||||
Route_Handler value;
|
||||
void *context_data;
|
||||
} Route;
|
||||
|
||||
void route_page_not_found(struct mg_http_message *msg, Route_Result *result);
|
||||
void route_simple_css(struct mg_http_message *msg, Route_Result *result);
|
||||
void route_favicon(struct mg_http_message *msg, Route_Result *result);
|
||||
void route_hotreload_js(struct mg_http_message *msg, Route_Result *result);
|
||||
void route_home(struct mg_http_message *msg, Route_Result *result);
|
||||
void route_build_id(struct mg_http_message *msg, Route_Result *result);
|
||||
#if MY_DEBUG
|
||||
void route_build_id(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
|
||||
#endif
|
||||
void route_page_not_found(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
|
||||
void route_home(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
|
||||
void route_generic_blog(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
|
||||
void route_blog(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
|
||||
|
||||
#endif // ROUTES_H_
|
||||
|
10
scripts/update-release
Executable file
10
scripts/update-release
Executable file
@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
systemctl stop aboba.service
|
||||
./build clean
|
||||
cc -DMY_DEBUG=0 -o build build.c
|
||||
./build make
|
||||
cp ./aboba /usr/local/bin/aboba
|
||||
systemctl start aboba.service
|
||||
systemctl status aboba.service
|
||||
|
13
systemd/aboba.service
Normal file
13
systemd/aboba.service
Normal file
@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Aboba website server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
User=root
|
||||
WorkingDirectory=/var/lib/aboba
|
||||
ExecStart=/usr/local/bin/aboba
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
29
timer.c
Normal file
29
timer.c
Normal file
@ -0,0 +1,29 @@
|
||||
#include <time.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "timer.h"
|
||||
#include "locked.h"
|
||||
|
||||
static locked(time_t) rawtime;
|
||||
|
||||
void start_timer(void)
|
||||
{
|
||||
lockx(&rawtime);
|
||||
time(&rawtime.value);
|
||||
unlockx(&rawtime);
|
||||
}
|
||||
|
||||
void get_timer_string(char *output, size_t size)
|
||||
{
|
||||
lockx(&rawtime);
|
||||
struct tm * timeinfo;
|
||||
|
||||
timeinfo = localtime(&rawtime.value);
|
||||
|
||||
snprintf(output, size, "[%02d %02d %d %02d:%02d:%02d]", timeinfo->tm_mday,
|
||||
timeinfo->tm_mon + 1, timeinfo->tm_year + 1900,
|
||||
timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
|
||||
unlockx(&rawtime);
|
||||
}
|
||||
|
7
timer.h
Normal file
7
timer.h
Normal file
@ -0,0 +1,7 @@
|
||||
#ifndef TIMER_H_
|
||||
#define TIMER_H_
|
||||
|
||||
void start_timer(void);
|
||||
void get_timer_string(char *output, size_t size);
|
||||
|
||||
#endif // TIMER_H_
|
31
tmpls/blog.html
Normal file
31
tmpls/blog.html
Normal file
@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Blog</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/bakedres/simple.css" />
|
||||
<#META_TAGS>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Blog posts</h1>
|
||||
<p>
|
||||
This page aggregates all blog posts.
|
||||
<a href="/blog-welcome.md">Welcome (or not) post</a>
|
||||
</p>
|
||||
<ul>
|
||||
<#BLOG_POSTS>
|
||||
</ul>
|
||||
<footer>
|
||||
<div style="float: left;">
|
||||
<a href="/">HOME</a>
|
||||
<span>COMMIT: <#COMMIT></span>
|
||||
<span>Running since: <#RUNNING_SINCE></span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<#ifdef HOTRELOAD>
|
||||
<script src="/bakedres/hotreload.js"></script>
|
||||
<#endif>
|
||||
<script src="/bakedres/theme.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -3,19 +3,25 @@
|
||||
<head>
|
||||
<title>Kamil's personal website</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/simple.min.css" />
|
||||
<link rel="stylesheet" href="/bakedres/simple.css" />
|
||||
<#META_TAGS>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Kamil's personal website</h1>
|
||||
<div style="float: left;">
|
||||
<a class="button" href="/">home</a>
|
||||
<a class="button" href="/#projects">projects</a>
|
||||
<a class="button" href="/blog">blog</a>
|
||||
</div>
|
||||
<p>Written in C™. <a href="https://gitlab.com/kamkow1/aboba" target="_blank">Check out the source code here!</a></p>
|
||||
<p>Written in C™. <a href="http://git.kamkow1lair.pl/kamkow1/aboba" target="_blank">Check out the source code here!</a></p>
|
||||
<section id="projects">
|
||||
<h2>Personal projects</h2>
|
||||
<a href="https://gitlab.com/kamkow1">Gitlab</a><br />
|
||||
<a href="https://github.com/kamkow1">Github (mostly old unused stuff)</a><br />
|
||||
<h3>Hosts</h3>
|
||||
<ul>
|
||||
<li><a href="http://git.kamkow1lair.pl">The Lair</a></li>
|
||||
<li><a href="https://gitlab.com/kamkow1">Gitlab</a></li>
|
||||
<li><a href="https://github.com/kamkow1">Github (mostly old unused stuff)</a></li>
|
||||
</ul>
|
||||
<h3>My favourites</h3>
|
||||
<ul>
|
||||
<li>
|
||||
@ -42,14 +48,25 @@
|
||||
<li>
|
||||
<a href="https://gitlab.com/kamkow1/knur" target="_blank">knur - heavily modified xv6 kernel</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/kamkow1/pamcam" target="_blank">pamcam - PAM module that takes a picture when you log in</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<img src="/bakedres/me.jpg" alt="literally me" />
|
||||
</section>
|
||||
<footer>
|
||||
<a href="/">HOME</a>
|
||||
<div style="float: left;">
|
||||
<a href="/">HOME</a>
|
||||
<span>COMMIT: <#COMMIT></span>
|
||||
<span>Running since: <#RUNNING_SINCE></span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<#if HOTRELOAD>
|
||||
<script src="/hotreload.js"></script>
|
||||
<#ifdef HOTRELOAD>
|
||||
<script src="/bakedres/hotreload.js"></script>
|
||||
<#endif>
|
||||
<script src="/bakedres/theme.js"></script>
|
||||
</body>
|
||||
</html>
|
26
tmpls/page-missing.html
Normal file
26
tmpls/page-missing.html
Normal file
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>404 - Page not found</title>
|
||||
<link rel="stylesheet" href="/bakedres/simple.css" />
|
||||
<#META_TAGS>
|
||||
</head>
|
||||
<body>
|
||||
<h1>The page you were looking for doesn't exist!</h1>
|
||||
<p>
|
||||
URL was: <#URL><br />
|
||||
</p>
|
||||
<footer>
|
||||
<div style="float: left;">
|
||||
<a href="/">HOME</a>
|
||||
<span>COMMIT: <#COMMIT></span>
|
||||
<span>Running since: <#RUNNING_SINCE></span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<#ifdef HOTRELOAD>
|
||||
<script src="/bakedres/hotreload.js"></script>
|
||||
<#endif>
|
||||
<script src="/bakedres/theme.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>404 - Page not found</title>
|
||||
<link rel="stylesheet" href="/simple.min.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>The page you were looking for doesn't exist!</h1>
|
||||
<p>
|
||||
URL was: <#URL><br />
|
||||
</p>
|
||||
<footer>
|
||||
<a href="/">HOME</a>
|
||||
</footer>
|
||||
|
||||
<#if HOTRELOAD>
|
||||
<script src="/hotreload.js"></script>
|
||||
<#endif>
|
||||
</body>
|
||||
</html>
|
31
tmpls/template-blog.html
Normal file
31
tmpls/template-blog.html
Normal file
@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title><#BLOG_POST_TITLE></title>
|
||||
<link rel="stylesheet" href="/bakedres/simple.css" />
|
||||
<#META_TAGS>
|
||||
<link rel="stylesheet" href="/bakedres/hljs-rainbow.css">
|
||||
<script src="/bakedres/highlight.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content"></div>
|
||||
<footer>
|
||||
<div style="float: left;">
|
||||
<a href="/">HOME</a>
|
||||
<span>COMMIT: <#COMMIT></span>
|
||||
<span>Running since: <#RUNNING_SINCE></span>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/bakedres/marked.js"></script>
|
||||
<script>
|
||||
document.getElementById("content").innerHTML = marked.parse(`<#BLOG_POST_MARKDOWN>`);
|
||||
</script>
|
||||
|
||||
<#ifdef HOTRELOAD>
|
||||
<script src="/bakedres/hotreload.js"></script>
|
||||
<#endif>
|
||||
<script src="/bakedres/theme.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
</body>
|
||||
</html>
|
@ -183,7 +183,7 @@ int main(int argc, char ** argv)
|
||||
|
||||
if (cmd_pid != -1) {
|
||||
LOGI("Killing %d\n", cmd_pid);
|
||||
kill(cmd_pid, SIGKILL);
|
||||
kill(cmd_pid, SIGINT);
|
||||
}
|
||||
|
||||
cmd_pid = cmd_run_async(&cmd);
|
||||
|
1
zip
Submodule
1
zip
Submodule
Submodule zip added at 649a16c5ea
Reference in New Issue
Block a user