bchs_tuto/examples/example7.c

361 lines
7.8 KiB
C

/*
* Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#if defined(__APPLE__)
#include <sandbox.h>
#endif
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <kcgi.h>
#include <kcgihtml.h>
#include <sqlite3.h>
enum page {
PAGE_INDEX, /* /index or just / */
PAGE__MAX
};
static const char *const pages[PAGE__MAX] = {
"index", /* PAGE_INDEX */
};
/*
* Generic routine to prime the HTTP document.
*/
static void
resp_open(struct kreq *req, enum khttp http)
{
khttp_head(req, kresps[KRESP_STATUS],
"%s", khttps[http]);
khttp_head(req, kresps[KRESP_CONTENT_TYPE],
"%s", kmimetypes[req->mime]);
khttp_body(req);
}
/*
* When the database is busy or locked, sleep for a random amount of
* time before continuing.
* We could put all sorts of heuristics in here to back off, but we do
* something really simple.
*/
static void
db_sleep(size_t attempt)
{
if (attempt < 10)
usleep(arc4random_uniform(100000));
else
usleep(arc4random_uniform(500000));
}
/*
* Keep trying to open the database forever.
*/
static sqlite3 *
db_open(void)
{
size_t attempt;
sqlite3 *db;
int rc;
attempt = 0;
again:
rc = sqlite3_open(DATADIR "/example7.db", &db);
if (SQLITE_BUSY == rc) {
db_sleep(attempt++);
goto again;
} else if (SQLITE_LOCKED == rc) {
fprintf(stderr, "sqlite3_open: %s\n",
sqlite3_errmsg(db));
db_sleep(attempt++);
goto again;
} else if (SQLITE_PROTOCOL == rc) {
fprintf(stderr, "sqlite3_open: %s\n",
sqlite3_errmsg(db));
db_sleep(attempt++);
goto again;
} else if (SQLITE_OK == rc) {
sqlite3_busy_timeout(db, 500);
return(db);
}
fprintf(stderr, "sqlite3_open: %s\n", sqlite3_errmsg(db));
return(NULL);
}
/*
* Close the database.
*/
static void
db_close(sqlite3 *db)
{
if (SQLITE_OK == sqlite3_close(db))
return;
fprintf(stderr, "sqlite3_close: %s\n", sqlite3_errmsg(db));
}
/*
* Step through the result set of a database query.
*/
static int
db_step(sqlite3 *db, sqlite3_stmt *stmt)
{
int rc;
size_t attempt = 0;
again:
rc = sqlite3_step(stmt);
if (SQLITE_BUSY == rc) {
db_sleep(attempt++);
goto again;
} else if (SQLITE_LOCKED == rc) {
fprintf(stderr, "sqlite3_step: %s\n",
sqlite3_errmsg(db));
db_sleep(attempt++);
goto again;
} else if (SQLITE_PROTOCOL == rc) {
fprintf(stderr, "sqlite3_step: %s\n",
sqlite3_errmsg(db));
db_sleep(attempt++);
goto again;
}
if (SQLITE_DONE == rc || SQLITE_ROW == rc)
return(rc);
fprintf(stderr, "sqlite3_step: %s\n", sqlite3_errmsg(db));
return(rc);
}
/*
* Execute a database query.
*/
static int
db_exec(sqlite3 *db, const char *sql)
{
size_t attempt = 0;
int rc;
again:
rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
if (SQLITE_BUSY == rc) {
db_sleep(attempt++);
goto again;
} else if (SQLITE_LOCKED == rc) {
fprintf(stderr, "sqlite3_exec: %s\n",
sqlite3_errmsg(db));
db_sleep(attempt++);
goto again;
} else if (SQLITE_PROTOCOL == rc) {
fprintf(stderr, "sqlite3_exec: %s\n",
sqlite3_errmsg(db));
db_sleep(attempt++);
goto again;
} else if (SQLITE_OK == rc)
return(1);
fprintf(stderr, "sqlite3_exec: %s (%s)\n",
sqlite3_errmsg(db), sql);
return(0);
}
/*
* Prepare a database statement.
* If we fail, try to populate the database: it might not have been
* initialised yet.
*/
static sqlite3_stmt *
db_stmt(sqlite3 *db, const char *sql, int recurse)
{
sqlite3_stmt *stmt;
const char *esql;
size_t attempt = 0;
int rc, bailed;
bailed = 0;
again:
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (SQLITE_BUSY == rc) {
db_sleep(attempt++);
goto again;
} else if (SQLITE_LOCKED == rc) {
fprintf(stderr, "sqlite3_prepare_v2: %s\n",
sqlite3_errmsg(db));
db_sleep(attempt++);
goto again;
} else if (SQLITE_PROTOCOL == rc) {
fprintf(stderr, "sqlite3_prepare_v2: %s\n",
sqlite3_errmsg(db));
db_sleep(attempt++);
goto again;
} else if (SQLITE_OK == rc)
return(stmt);
fprintf(stderr, "sqlite3_prepare_v2: %s (%s) (%d)\n",
sqlite3_errmsg(db), sql, sqlite3_errcode(db));
sqlite3_finalize(stmt);
/*
* Only attempt to do this if (1) we have a certain error, (2)
* we haven't already bailed out, and (3) we haven't recursed
* into the db_stmt() function.
*/
if (SQLITE_ERROR == rc && 0 == bailed && recurse) {
esql = "CREATE TABLE record "
"(count INTEGER NOT NULL DEFAULT(0))";
if (db_exec(db, esql)) {
esql = "INSERT INTO record DEFAULT VALUES";
if (NULL != (stmt = db_stmt(db, esql, 0))) {
db_step(db, stmt);
sqlite3_finalize(stmt);
}
}
esql = "PRAGMA journal_mode=WAL";
(void)db_exec(db, esql);
bailed = 1;
goto again;
}
return(NULL);
}
static void
sendindex(struct kreq *req)
{
struct khtmlreq r;
int rc;
sqlite3 *db;
sqlite3_stmt *stmt;
resp_open(req, KHTTP_200);
khtml_open(&r, req);
khtml_elem(&r, KELEM_DOCTYPE);
khtml_elem(&r, KELEM_HTML);
khtml_elem(&r, KELEM_HEAD);
khtml_elem(&r, KELEM_TITLE);
khtml_puts(&r, "Test");
khtml_closeelem(&r, 2);
khtml_elem(&r, KELEM_BODY);
if (NULL != (db = db_open())) {
stmt = db_stmt(db, "SELECT count FROM record", 1);
if (NULL != stmt) {
rc = db_step(db, stmt);
if (SQLITE_ROW == rc)
khtml_puts(&r, (char *)
sqlite3_column_text(stmt, 0));
else if (SQLITE_DONE == rc)
khtml_puts(&r, "No value!?");
else
khtml_puts(&r, "Error getting value.");
sqlite3_finalize(stmt);
} else
khtml_puts(&r, "Error getting statement.");
stmt = db_stmt(db, "UPDATE record SET count=count+1", 0);
(void)db_step(db, stmt);
sqlite3_finalize(stmt);
db_close(db);
} else
khtml_puts(&r, "Error opening database.");
khtml_close(&r);
}
int
main(void)
{
struct kreq r;
#if defined(__APPLE__)
char *ep;
int rc;
#endif
/*
* Set up our main HTTP context.
*/
if (KCGI_OK != khttp_parse
(&r, NULL, 0, pages, PAGE__MAX, PAGE_INDEX))
return(EXIT_FAILURE);
/*
* Set up a sandbox allowing for file-system access.
* We need this for the database, which is WAL and will thus use
* shared memory primitives.
*/
#if defined(__APPLE__)
rc = sandbox_init
(kSBXProfileNoNetwork,
SANDBOX_NAMED, &ep);
if (-1 == rc) {
fprintf(stderr, "sandbox_init: %s\n", ep);
sandbox_free_error(ep);
khttp_free(&r);
return(EXIT_FAILURE);
}
#elif defined(__OpenBSD__)
if (-1 == pledge("stdio rpath cpath wpath flock", NULL)) {
perror("pledge");
khttp_free(&r);
return(EXIT_FAILURE);
}
#endif
if (KMETHOD_OPTIONS == r.method) {
/*
* Indicate that we accept GET and POST methods.
* This isn't really needed.
*/
khttp_head(&r, kresps[KRESP_STATUS],
"%s", khttps[KHTTP_200]);
khttp_head(&r, kresps[KRESP_ALLOW],
"OPTIONS GET POST");
khttp_body(&r);
} else if (KMETHOD_GET != r.method) {
/*
* Don't accept non-GET and non-POST methods.
*/
resp_open(&r, KHTTP_405);
} else if (PAGE__MAX == r.page ||
KMIME_TEXT_HTML != r.mime) {
/*
* We've been asked for an unknown page or something
* with an unknown extension.
*/
resp_open(&r, KHTTP_404);
khttp_puts(&r, "Page not found.");
} else
/*
* Route to page handler.
*/
sendindex(&r);
/*
* Clean up our context.
*/
khttp_free(&r);
return(EXIT_SUCCESS);
}